[{"content":"","date":null,"permalink":"https://harmstyler.me/","section":"Tyler Harms","summary":"","title":"Tyler Harms"},{"content":"I\u0026rsquo;m a software developer based in Sioux Falls, SD. I\u0026rsquo;m taking on a limited number of consulting and contract projects. If you have something that needs building, fixing, or a second look, I\u0026rsquo;d like to hear about it.\nWhat I help with # Finishing and hardening AI-built apps. Taking something built with AI tools, or a prototype, and making it production-ready: auth, payments, deployment, data handling, and debugging. Code review and technical advisory. A few hours a month for business owners or small teams building their own tools who want an experienced second opinion. Custom web apps, integrations, and automation for small businesses. Replacing spreadsheet-and-email workflows, connecting systems, and building internal tools. Background #I do server-side development in PHP, Ruby, and Python. Since 2019 my focus has been Python and Docker. I handle containerization, orchestration, and deployment with Docker and Kubernetes. I was an eZ Publish expert in the past. I studied at the University of South Dakota and Southeast Technical Institute.\nI built and maintain sfdevs.com, the Sioux Falls developer community site, and the 2018 South Dakota Code Camp site. You can see both on the Portfolio page.\nA few things from my resume:\nImplemented Apache Solr search for a large publishing company, indexing about 350,000 documents across 4 Solr cores and 5 websites. Built an e-commerce MVP for a large retail company using Symfony/Sylius with Stripe payments for a subscription-based food delivery service. How I work # A free intro call to talk through your project. Hourly work or small, fixed-scope projects. Retainers for ongoing work. Clear communication throughout. You own the code. How I think about AI-assisted development #I wrote about the risk of shipping AI-generated code without understanding how it works, and that thinking shapes how I approach AI-built apps: The Prompt-Engineered Plateau.\nGet in touch #Have a project in mind? Get in touch.\n","date":null,"permalink":"https://harmstyler.me/work-with-me/","section":"Tyler Harms","summary":"","title":"Work with me"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/ai/","section":"Categories","summary":"","title":"Ai"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/","section":"Categories","summary":"","title":"Categories"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/claude-code/","section":"Tags","summary":"","title":"Claude Code"},{"content":"Claude Code gives you two different ways to pull extra context into a session automatically, and I assumed they composed. They don\u0026rsquo;t.\nThe Setup # @ imports - reference a file with @path/to/file.md, and Claude auto-loads the full contents of that file into context. Path-scoped rules - a rule file with frontmatter that targets specific paths, so the rule only applies when you\u0026rsquo;re actually working in a file covered by that path. Combine the two and you\u0026rsquo;d expect to get the best of both: a rule that\u0026rsquo;s conditionally loaded based on where you\u0026rsquo;re working, that also pulls in a full reference doc via @ when it fires. That\u0026rsquo;s exactly what I wanted - a way to attach a complete doc to context, but only when it\u0026rsquo;s relevant.\nThe Gotcha #It doesn\u0026rsquo;t work that way. An @ import is unconditional - it loads every time, full stop, regardless of whether the path-scoped rule it\u0026rsquo;s sitting inside of would have matched or not. The path scoping on the rule doesn\u0026rsquo;t extend to anything you @ import inside it. So the moment you add an @ import to a path-scoped rule, you\u0026rsquo;ve quietly made that content load everywhere, all the time, which defeats the entire reason you scoped the rule in the first place.\nNothing warns you about this. The rule\u0026rsquo;s frontmatter still looks like it\u0026rsquo;s doing its job, and it\u0026rsquo;s easy to assume the @ import inherited the same scoping.\nThe Workaround #The only way I\u0026rsquo;ve found to get conditional, full-doc inclusion is to skip rules entirely and drop a CLAUDE.md file directly in the folder you want it scoped to, with the @ import inside it. Claude Code loads CLAUDE.md based on which directory you\u0026rsquo;re actually working in, so the import effectively becomes scoped by folder structure instead of by rule frontmatter.\nIt\u0026rsquo;s not quite as flexible as a glob-based path rule, but it\u0026rsquo;s the only version of \u0026ldquo;load this doc, but only in this part of the codebase\u0026rdquo; that currently works.\n","date":"Oct 22, 2026","permalink":"https://harmstyler.me/posts/2026/claude-code-at-imports-vs-path-rules/","section":"Posts","summary":"","title":"Claude Code's @ Imports and Path-Scoped Rules Don't Mix"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/context-management/","section":"Tags","summary":"","title":"Context Management"},{"content":"","date":null,"permalink":"https://harmstyler.me/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/expert-beginner/","section":"Tags","summary":"","title":"Expert Beginner"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/prompt-engineering/","section":"Tags","summary":"","title":"Prompt Engineering"},{"content":"If you’ve spent any time in software development circles, you’ve probably heard of the expert beginner. The concept, coined by Erik Dietrich,1 describes a developer who has hit a local peak of confidence: skilled enough to feel like an expert in their narrow context, but unaware of how much they don’t know. They’ve stopped growing because nothing around them is telling them they need to. Their code works. Their tickets close. Their PRs get merged.\nIt’s an expansion of the Dunning-Kruger effect2, but with a key twist: the expert beginner isn’t just temporarily overconfident early in their career. They get stuck there. Success reinforces their habits. Seniority insulates them from pushback. Years pass and they never realize their ceiling was a false one.\nI’ve been thinking about this concept a lot lately, and I’m worried we’re about to create an entire generation of expert beginners. Except this time, we won’t even call it a problem. We’ll call it a feature.\nThe Floor Is Gone #To become an expert beginner the old way, you still had to build something. You had to write enough code, ship enough features, and accumulate enough working solutions that you felt justified in your confidence. There was a floor of foundational knowledge required to even reach the plateau.\nPrompt development removes that floor.\nToday, a developer can get working software: software that ships, that users touch, and that stakeholders sign off on, all without ever developing the mental models that would let them know it’s brittle, unscalable, or fundamentally wrong. The feedback loop that once said this is good enough now fires much earlier, and with far more social reinforcement around it.\nThe danger isn’t that AI makes developers lazy. The danger is that AI makes a lack of depth look like mastery. Ai hides rot well.\nThe Celebration Problem #Here’s what worries me most: this won’t feel like a problem. It will feel like progress.\nA junior developer in 2026 who can ship a feature in a day using AI prompts will be praised. Their velocity is real. Their output is tangible. The fact that they couldn’t debug it without AI, couldn’t reason about its performance characteristics, and couldn’t anticipate how it fails under load: none of that surfaces in a sprint review. None of that shows up in a performance review.\nBy the time it does surface, the attribution is murky. It’s not your code that failed. It’s just a bug.\nThe expert beginner of old at least owned their code enough to defend it. The new version may not understand it well enough to know there’s something to defend.\nI want to be fair here. Raising the floor has real value. More people shipping more things is genuinely good; but I think we’re underestimating the cost of doing it at the expense of the ceiling.\nJunior vs. Senior: The Divide Widens #For senior developers, AI is a genuine force multiplier. They bring the judgment to know when the output is wrong, when the architecture is naive, when the abstraction is leaking. They can prompt toward something good because they already have a model of what good looks like. AI compresses their execution time without compressing their wisdom.\nFor junior developers, the risk is that AI short-circuits the struggle that builds that wisdom in the first place.\nA lot of what makes a senior developer isn’t knowledge they were handed in a book. It’s scar tissue. Debugging a production outage at 2am. Inheriting a nightmare codebase and having to make sense of it. Watching a clever solution become a maintenance burden six months later. These experiences build the intuition that makes AI output actually useful rather than just plausible.\nIf AI removes those experiences, or delays them long enough that the junior never connects cause and effect, you get developers who are fluent in prompting but have no internal model of the system they’re building. They know how to ask for the answer. They don’t know how to question it.\nThe “Good Enough” Horizon #“Good enough” is self-sealing.\nIn most business contexts, software doesn’t need to be good. It needs to work today, satisfy a stakeholder, and not visibly break. AI is extraordinarily good at producing exactly that. The gap between “AI output” and “good software” is often invisible to everyone in the room except the person with the experience to see it.\nAnd if that person is increasingly rare, or increasingly drowned out by the speed of AI- assisted delivery, the standard itself drifts. “Good enough” doesn’t just become acceptable; it becomes the definition of good. There’s no longer a reference point for better.\nThis is the part that keeps me up at night. Not that individual developers will stagnate; that’s happened in every era. But that the shared standard of the industry could erode quietly, and we won’t notice because everything still appears to work.\nWhat Might Push Back #I don’t want to be all doom here. There’s a reasonable counterpoint: mediocre developers have always existed, and the industry has survived. Frameworks, IDEs, and code generators have each been accused of dumbing things down. Each time, the developers who cared about craft found a way forward.\nAnd there’s something worth noting about who engages with this question seriously. The developers who are asking “am I becoming an expert beginner?” are almost certainly not. That kind of self-reflection is itself evidence of a growth mindset. The trap is, by definition, most dangerous for those who aren’t asking.\nThe developers who will thrive long-term are probably those who use AI as a mirror, constantly asking why it generated this, is this actually right, and what assumptions it is making, rather than treating it as an oracle. But that disposition requires a kind of epistemic humility3 that’s hard to teach and even harder to sustain when everything around you is saying you’re doing great.\nWhere That Leaves Us #I don’t think the answer is to reject AI tooling. That ship has sailed and it was never really the point. The tools are real, the productivity gains are real, and pretending otherwise isn’t useful.\nBut I do think the senior developers and engineering leaders reading this have a responsibility to think about what we’re building for. Are we building teams of people who can reason about software, or teams of people who can ship software? Those used to be more closely related than they are today.\nAnd if you’re earlier in your career: seek out the hard problems on purpose. Don’t let AI remove the friction that would have taught you something. The scar tissue matters. The 2am debugging session matters. The bad abstraction you built and had to live with: that matters too.\nGood enough has always been the enemy of great. The new version of that enemy just gives you a lot more confidence while it’s working.\nHow Developers Stop Learning: Rise of the Expert Beginner\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDunning-Kruger effect - Wikipedia\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nEpistemic humility - Wikipedia\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"Mar 15, 2026","permalink":"https://harmstyler.me/posts/2026/prompt-engineered-problem/","section":"Posts","summary":"Thinking about the risk of using AI to ship code without actually learning how any of it works. Unlike with the Expert Beginner, we won\u0026rsquo;t call it a problem. We\u0026rsquo;ll call it a feature.","title":"The Prompt-Engineered Plateau"},{"content":"","date":null,"permalink":"https://harmstyler.me/resume/","section":"Tyler Harms","summary":"","title":"Resume"},{"content":"If you want to reach me, drop a note here and I will get back to you.\nDon't fill this out if you're human: Don't fill this out if you're human: Name\nEmail\nMessage\nSend ","date":null,"permalink":"https://harmstyler.me/contact/","section":"Tyler Harms","summary":"","title":"Contact"},{"content":"Thanks for reaching out. Your message is on its way and I will reply soon.\n","date":null,"permalink":"https://harmstyler.me/contact-success/","section":"Tyler Harms","summary":"","title":"Message Sent"},{"content":" One of the things I love about Hugo is its flexibility when it comes to Markdown rendering. While the default behavior for images is usually enough, I found myself wanting more control. Specifically the ability to add CSS classes or wrap images in links without resorting to raw HTML in my Markdown files (as hugo raises warnings about this).\nTo solve this, I started using a custom render-image.html template. It allows me to stay in \u0026ldquo;Markdown mode\u0026rdquo; while still getting the benefits of optimized, flexible image output.\nThe Problem #Standard Markdown image syntax is simple: ![Alt text](image.jpg \u0026quot;Title text\u0026quot;). However, it doesn\u0026rsquo;t provide a native way to:\nAdd a CSS class for styling (e.g., centering an image or adding a border). Link the image to another page or a larger version of itself. Automatically handle Hugo\u0026rsquo;s Image Processing for local resources. The Solution: A Custom Render Hook #Hugo allows you to override how Markdown elements are rendered by creating a \u0026ldquo;render hook\u0026rdquo;. By placing a file at layouts/_default/_markup/render-image.html, we can intercept every image in our Markdown and process it through our own logic.\nAdvanced Syntax #My template extends the \u0026ldquo;Title\u0026rdquo; part of the image syntax to support extra attributes. I use a # separator to define classes and a | to define a link.\nBasic Image: ![Alt](image.jpg \u0026quot;My Title\u0026quot;)\nImage with a CSS Class: ![Alt](image.jpg \u0026quot;My Title#featured-image\u0026quot;)\nImage with a Class and a Link: ![Alt](image.jpg \u0026quot;My Title#center-img|https://example.com\u0026quot;)\nHow It Works #The template does a few smart things:\nResource Resolution: It first checks if the image is a Page Resource. If not, it looks in the global assets via resources.Get. If it\u0026rsquo;s still not found (like an external URL), it falls back to the provided destination. Title Parsing: it splits the .Title string by # and | to extract the actual title, the CSS class, and the link URL. Performance: It automatically adds loading=\u0026quot;lazy\u0026quot; and decoding=\u0026quot;async\u0026quot; to every image, which is great for Core Web Vitals. Dimensions: If the image is found as a Hugo resource, it automatically populates the width and height attributes to prevent layout shifts. The Template #Here is the full render-image.html template. You can drop this into your Hugo project at layouts/_default/_markup/render-image.html.\n1{{- $dest := .Destination -}} 2{{- $class := .Attributes.class -}} 3{{- $link := .Attributes.link -}} 4 5{{- if (strings.Contains .Title \u0026#34;#\u0026#34;) -}} 6{{- $parts := split .Title \u0026#34;#\u0026#34; -}} 7{{- .Page.Scratch.Set \u0026#34;title\u0026#34; (index $parts 0) -}} 8{{- $fragment := (index $parts 1) -}} 9{{- if (strings.Contains $fragment \u0026#34;|\u0026#34;) -}} 10{{- $fragParts := split $fragment \u0026#34;|\u0026#34; -}} 11{{- $class = (index $fragParts 0) -}} 12{{- $link = (index $fragParts 1) -}} 13{{- else -}} 14{{- $class = $fragment -}} 15{{- end -}} 16{{- else -}} 17{{- .Page.Scratch.Set \u0026#34;title\u0026#34; .Title -}} 18{{- end -}} 19{{- $title := .Page.Scratch.Get \u0026#34;title\u0026#34; -}} 20 21{{- $img := .Page.Resources.GetMatch $dest -}} 22{{- if not $img -}} 23{{- $img = resources.Get $dest -}} 24{{- end -}} 25 26{{- if $img -}} 27{{- $processed := $img -}} 28{{- if $link -}} 29\u0026lt;a href=\u0026#34;{{ $link | safeURL }}\u0026#34;\u0026gt; 30 {{- end -}} 31 \u0026lt;img src=\u0026#34;{{ $processed.RelPermalink }}\u0026#34; 32 width=\u0026#34;{{ $processed.Width }}\u0026#34; 33 height=\u0026#34;{{ $processed.Height }}\u0026#34; 34 {{ with .Text }}alt=\u0026#34;{{ . }}\u0026#34; {{ else }}alt=\u0026#34;\u0026#34; {{ end }} 35 {{ with $title }}title=\u0026#34;{{ . }}\u0026#34; {{ end }} 36 {{ with $class }}class=\u0026#34;{{ . }}\u0026#34; {{ end }} 37 loading=\u0026#34;lazy\u0026#34; 38 decoding=\u0026#34;async\u0026#34; 39 \u0026gt; 40 {{- if $link -}} 41\u0026lt;/a\u0026gt; 42{{- end -}} 43{{- else -}} 44{{- if $link -}} 45\u0026lt;a href=\u0026#34;{{ $link | safeURL }}\u0026#34;\u0026gt; 46 {{- end -}} 47 \u0026lt;img src=\u0026#34;{{ $dest | safeURL }}\u0026#34; 48 {{ with .Text }}alt=\u0026#34;{{ . }}\u0026#34; {{ else }}alt=\u0026#34;\u0026#34; {{ end }} 49 {{ with $title }}title=\u0026#34;{{ . }}\u0026#34; {{ end }} 50 {{ with $class }}class=\u0026#34;{{ . }}\u0026#34; {{ end }} 51 loading=\u0026#34;lazy\u0026#34; 52 decoding=\u0026#34;async\u0026#34; 53 \u0026gt; 54 {{- if $link -}} 55\u0026lt;/a\u0026gt; 56{{- end -}} 57{{- end -}} Hope you find it useful!\n","date":"Jan 19, 2026","permalink":"https://harmstyler.me/posts/2026/hugo-render-image/","section":"Posts","summary":"","title":"Enhanced Image Rendering in Hugo"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/hugo/","section":"Categories","summary":"","title":"Hugo"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/web-development/","section":"Categories","summary":"","title":"Web Development"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/learning/","section":"Categories","summary":"","title":"Learning"},{"content":"In the past year I left my position as a Web Developer to work as a Software Developer. Not only did I leave web development behind, the core languages I use every day changed. I switched from a primarily PHP/Javascript developer to a Python/C++ developer. Changing a tech stacks created a need for me to learn a lot about a new language very quickly, I’ve decided to write down some of what I learned.\nWhy learn something new? #Unlike me, there’s a good chance you didn’t change jobs recently, so why learn something new? The answer varies from person to person. You don’t need to change jobs to learn a new language. Best practices can differ between languages, so learning a new language might make you more proficient at your current language. Changes in code structure, language features, testing, compilers, etc all make you think differently about your work. Training yourself to think differently can make your old languages new again. Sometimes it’s just good to break up the monotony of your day to day, a new language can be that break. You don’t necessarily need a good reason to learn something new, sometimes its the the motivation to learn something new.\nAlways be learning. Our industry evolves incredibly fast. So fast, in fact, that keeping up with trends can feel impossible. Learning something new will expose you to trends you’ve never heard of before. Staying ahead of the curve in programming trends can pay dividends. If a day comes when you decide to make a career change, having a leg up on the competition will go a long way.\nSo, what’s the best way to pick up a new programming language?\nBooks #Almost every language out there has a book (or many) written about it. Whether it be learning the basics of a language or picking up some best practices, there’s a very good chance that a book exists somewhere on the subject. Books can vary greatly in quality, so I like to choose from publishers I trust (PragProg, O’Reilly, and Manning are some of my favorites).\nDoing a little bit of research before picking a book up can help in choosing the right book. Is the author an expert in their field (they don’t always need to be)? Have you read anything from the author before? What do other people say about the book? Who published the book (editorial quality can make a break a book for me)? These are questions I like to ask myself when picking new books up.\nRead some code #If you want to know how to write in a new language the right way, look at how others write code in that language. Look at code from major projects in your language. Pick out some popular devs on GitHub to follow to see how they commit code.\nCode is code, all languages ultimately run on a computer. Once you learn a language, learning a new language is considerably easier. There are drawbacks, however. I’ve found that breaking habits can be hard. For example, in PHP I coded almost exclusively in an object oriented fashion. Whereas in python I focus on namespaces and functions, limiting classes and objects due to features of the language.\nDon\u0026rsquo;t know what\u0026rsquo;s popular in your new language? Check out GitHub\u0026rsquo;s trending page to see what\u0026rsquo;s trending in your language. Also be sure to check out the topic page for your new language. Remember, want to know how to write quality code? Then you need to read quality code.\nKoans #One of the neatest ways to learn a language I’ve seen is to find a Koans project in that language.\nA kōan is a story, dialogue, question, or statement which is used in Zen practice to provoke the \u0026ldquo;great doubt\u0026rdquo; and to practice or test a student\u0026rsquo;s progress in Zen.1\nA programming koan is basically a set of unit tests you’re handed that you need to make pass. You’re provided the answers, you just need to fill in the blanks with your code. If you’re the type of person who learns by doing, I’d highly recommend finding a koans project in your language.\nSome popular Koans projects:\nRuby: http://rubykoans.com/ Python: https://github.com/gregmalcolm/python_koans Clojure: http://clojurekoans.com/ Mentorship #One of the best things about my new position was that from day one I was assigned a mentor on my dev team. Not every company provides this luxury, but there are some other solutions. You may need to take matters into your own hands. Finding a great community of developers can lead to great mentorship opportunities.\nFind a mentor in the community. #Many dev communities have a way for helping mentors and mentees find each other. You usually just need to ask. My local dev community, SFDevs, maintains a #mentorship channel within our slack group to help facilitate a productive mentorship community. Figure out where the best place to ask is in your dev community and ask, I think you’ll be surprised at the number of people who are eager to help others.\nFind a mentor online #For one reason or another, finding a mentor locally may not be an option. Luckily, there are many great tools to help facilitate mentorships remotely. Interact with developers on Twitter and Reddit, ask questions. Leveraging your network can open doors to find a mentor online. Finally, a quick google search will return multiple formal online mentorship programs available to you.\nAsk an expert #Figuring out who an expert is in a field is key. Many developers are active an approachable online. Finding the right community or forum to ask experts questions can make learning your next language easier.\nIt’s important to remember not to abuse this. While many are willing to help, it’s up to you to learn. Respecting other people’s time and energy will go a long way to ensuring others will help you in the future.\nMeetups #If you’re not attending your local developer meetups, you should be. Meetups are generally free and open to area devs. These events are being put on because people want to share with others. Taking advantage of a resource like this is low effort and can have huge returns. Not only will you stay abreast of trends in tech, the people leading these meetups are often senior devs in the community. Looking for your next job is way easier if you already know the best devs in the community.\nContribute #Another option if you’re the type of person who learns by doing is to contribute to open source. Find a project you think you could be passionate about and help. Still not sure how to jump in? There are tons of great resources available for getting started with open source contributions. Here’s a list of a few I like:\nUp for grabs: https://up-for-grabs.net/ CodeTriage: https://www.codetriage.com/ GitHub: https://github.com/showcases/great-for-new-contributors Dive In #As others have noted before, once you learn one programming language, others come more quickly. One of the best pieces of advice I’ve received over my career is to go all in when learning something new. When you fully commit, it’ll be easier to overcome the hard parts. Count your small wins, there will be lots early on, so build on those early successes when learning gets tough.\nFor me, finding the motivation to learn something new has always been my biggest hurdle. I had done some python work in the past, I had never worked with c++ before, so I had to pick up the basics in a hurry. What worked for me may not be what works for you. I hope this helps you with your profesisonal development. Good luck!\nWikipedia\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"Feb 23, 2020","permalink":"https://harmstyler.me/posts/2020/learning-a-new-language/","section":"Posts","summary":"","title":"Learning a New Programming Language"},{"content":"As a developer, I want to keep my code as DRY as is feasible for the given project. In a Hugo project, that\u0026rsquo;s where the Partial templates come in.\nPartials are smaller, context-aware components in your list and page templates that can be used economically to keep your templating DRY.\nPartials, however, have one serious limitation, they\u0026rsquo;re cached during the build of a given page (or scope). Once a partial has been created, no outside logic can adjust what the partial returns (for the page). This makes the build very fast. But, prevents modifications to the partial if so desired. Imagine a scenario where a partial defines a DOM ID and you want to include the partial twice on the page. In that scenario you\u0026rsquo;ll end up with a duplicated id on the page.\nPartials are cached for each page during the build.\nNow, knowing why we can\u0026rsquo;t just define a variable and expect it to exist inside the partial, the solution makes a lot of sense. The partialCached is usually used to use a partial template that is cached outside of the current page scope. This function is usually used to optimize builds to be more performant.\npartialCached allows for caching of partials that do not need to be re-rendered on every invocation.\nThe partialCached function can take extra parameters (unlimited) in order to vary the cache for the partial. That means that we can call the partial on a template, and call the partialCached (with a cache variant) and the partials will be rendered independently of each other.\nFinally, we\u0026rsquo;ll use the Hugo .Scratch function to tie it all together.\n.Scratch acts as a “scratchpad” to allow for writable page- or shortcode-scoped variables.\nUsing the scratchpad we can set a variable outide the template and get it inside the rendered partial.\nexample.html:\n1{{ define \u0026#34;content\u0026#34; }} 2 {{ partial \u0026#34;page-single/contact_form.html\u0026#34; . }} 3 \u0026lt;!-- ... --\u0026gt; 4 {{ .Scratch.Set \u0026#34;form_id\u0026#34; \u0026#34;-2\u0026#34; }} 5 {{ partialCached \u0026#34;page-single/contact_form.html\u0026#34; . \u0026#34;footer\u0026#34; }} 6{{ end }} page-single/contact_form.html:\n1\u0026lt;form id=\u0026#34;submit-form{{ .Scratch.Get \u0026#34;form_id\u0026#34; }}\u0026#34;\u0026gt; 2 \u0026lt;label for=\u0026#34;name\u0026#34;\u0026gt;Name\u0026lt;/label\u0026gt; 3 \u0026lt;input type=\u0026#34;text\u0026#34; value=\u0026#34;\u0026#34; placeholder=\u0026#34;name\u0026#34; id=\u0026#34;name{{ .Scratch.Get \u0026#34;form_id\u0026#34; }}\u0026#34;\u0026gt; 4\u0026lt;/form\u0026gt; Using this strategy you can pass variables into partials and get around the built in partial caching that Hugo employs.\n","date":"Apr 13, 2019","permalink":"https://harmstyler.me/posts/2019/how-to-pass-variables-to-a-partial-template-in-hugo/","section":"Posts","summary":"","title":"How to Pass Variables to a Partial Template in Hugo"},{"content":"South Dakota Code Camp is The Mount Rushmore State’s largest developer conference. The site lists all necessary information for conference attendees. Design by Marshall Crosby - Blend Interactive, implemented in Hugo.\n","date":"Apr 6, 2019","permalink":"https://harmstyler.me/portfolio/sdcc2018/","section":"Portfolio","summary":"","title":"2018 South Dakota Code Camp"},{"content":"sfdevs.com is designed to be a resource for the SFDevs community. Whether it\u0026rsquo;s finding information about upcoming meetups or joining in the Slack conversations, sfdevs.com is the resource. Design by TEMPLATED, implemented in Hugo.\n","date":"Apr 6, 2019","permalink":"https://harmstyler.me/portfolio/sfdevs/","section":"Portfolio","summary":"","title":"SFDevs"},{"content":"","date":null,"permalink":"https://harmstyler.me/portfolio/","section":"Portfolio","summary":"","title":"Portfolio"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/a11y/","section":"Categories","summary":"","title":"A11y"},{"content":"Making sure webistes are accessible is a big deal these days. Websites should be mindful of their audience to make sure people with disabilities are not excluded from the site content. Color contrasts between foregrounds and backgrounds is an easy miss when checking that a site is accessible, but one that is vital to hard of sight users.\nFor more information about website contrast ratios, visit WebAIM\u0026rsquo;s Contrast and Color Accessibility page.\nMaking it easier #Using the Contrast Ratio Calculator library, calculating contrast ratios can be automatic in any site.\nColor Objects #Colors can be made into their object representation by using the Color class. The color objects can be created by either the hexidecimal representation of the color or the RGB representation of the color.\n1 2 3 4 5 6 7 8 9 10 \u0026lt;?php namespace HarmsTyler\\ContrastRatioCalculator; class Color { //... public static function fromRgb(array $rgb): self {} public static function fromHex(string $hex): self {} //... } 1 2 3 4 \u0026lt;?php $color1 = Color::fromHex(\u0026#39;ffffff\u0026#39;); $color2 = Color::fromRgb([255,255,255]); // $color1-\u0026gt;getHex() === $color2-\u0026gt;getHex() Calculating the Contrast #Once color objects have been created, the Contrast Ratio class can be used. The Contrast Ratio class requires two color objects on insantiation, a primary and a secondary color (typically foreground and background colors). The contrast ratio calculation is made immediately upon object instantiation, so it can be retrieved from the class immediately.\n1 2 3 4 5 6 7 8 \u0026lt;?php namespace HarmsTyler\\ContrastRatioCalculator; class ContrastRatio { public function __construct(Color $primaryColor, Color $secondaryColor) {} public function getRatio() {}; } 1 2 3 4 \u0026lt;?php $contrastRatio = new ContrastRatio($color1, $color2); $ratio = $contrastRatio-\u0026gt;getRatio(); Making the grade #A ratio alone is just a number. Without knowing how to grade a raio, it\u0026rsquo;s actually useless. That\u0026rsquo;s where the WCAGContrastRating class comes in to play. The WCAGContrastRating can return a grade by either passing a set of Color objects, or by passing an already created ContrastRatio object. The Web Content Accessibility Guidelines (WCAG) state what contrast ratios are a pass/fail. Specifically WCAG guidelines give grades of AAA, AA, AA Large, and FAIL (there are variations of these grades, but if text has a grade of AAA at normal text size, it can be assumed that it will receive an AAA at large text sizes as well, so those variations are ommitted from the grading scale).\n1 2 3 4 5 6 7 8 \u0026lt;?php namespace HarmsTyler\\ContrastRatioCalculator; class WCAGContrastRating { public function rateColors(Color $primaryColor, Color $secondaryColor) {} public function rateContrastRatio(ContrastRatio $contrastRatio): string {} } 1 2 3 4 \u0026lt;?php $rating = new WCAGContrastRating(); $grade1 = $rating-\u0026gt;rateContrastRatio($contrastRatio); $grade2 = $rating-\u0026gt;rateColors($color1, $color2); Grades will be returned as one of four constants: AA_LARGE, AA, AAA, and FAIL;\nA real world example #I help manage a large website installation where a small set of editors can manage \u0026gt;160 websites from a single admin. These editors can choose a site\u0026rsquo;s primary and secondary colors for the site, providing a more individualized look and feel for the various sites. Using the Contrast Ratio Calculator library, I am able to grade the primary/secondary color contrast ratios, as well as the font colors appearing in with the theme colors. By grading them automatically on the server, editors do not need to check each site individually. Editors do not need to worry about this feature of their websites. By automating these contrast ratios, editors are able to focus on other site content needs.\n","date":"Feb 17, 2019","permalink":"https://harmstyler.me/posts/2019/contrast-ratio-calculator/","section":"Posts","summary":"\u003cp\u003eMaking sure webistes are accessible is a big deal these days. Websites should be mindful of their audience to make sure people with disabilities are not excluded from the site content. Color contrasts between foregrounds and backgrounds is an easy miss when checking that a site is accessible, but one that is vital to hard of sight users.\u003c/p\u003e","title":"Contrast Ratio Calculator"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/php/","section":"Categories","summary":"","title":"PHP"},{"content":"eZ Platform boils down to a set of Symfony bundles that give you a nice way of managing content for your users. With these bundles, eZ reserves some routing paths for internal or admin use, paths that you might want for your own app. If you ever run across a path you want to use for your app, and not break an internal eZ feature, there is a simple solution: change the path config.\nChanging the path #Looking at the eZ Platform search route, we see that eZ has reserved /search for their internal use. I want to use use /search for my custom search endpoint. To fix, we\u0026rsquo;ll just redefine the route.\nvendor/ezsystems/ezplatform-admin-ui/src/bundle/Resources/config/routing.yml #The definition for the search route found in the admin bundle:\n1ezplatform.search: 2 path: /search 3 methods: [\u0026#39;GET\u0026#39;] 4 defaults: 5 _controller: \u0026#39;EzPlatformAdminUiBundle:Search:search\u0026#39; app/config/routing.yml #In your routing file use the same name with a different path:\n1ezplatform.search: 2 path: /adminsearch 3 methods: [\u0026#39;GET\u0026#39;] 4 defaults: 5 _controller: \u0026#39;EzPlatformAdminUiBundle:Search:search\u0026#39; And you\u0026rsquo;re done! eZ is very good at ensuring they use internal Symony routing when generating urls for a page, so you\u0026rsquo;re safe to simply redefine the route to avoid route collosion in eZ Platform.\n","date":"Dec 19, 2018","permalink":"https://harmstyler.me/posts/2018/avoiding-route-collision-in-ez-platform/","section":"Posts","summary":"","title":"Avoiding Route Collision in eZ Platform"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/ez-platform/","section":"Categories","summary":"","title":"EZ Platform"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/symfony/","section":"Categories","summary":"","title":"Symfony"},{"content":"Hi, I\u0026rsquo;m Tyler Harms. I am a professional software developer, father, and husband from the great state of South Dakota.\nI\u0026rsquo;m available for custom website development and consulting work. See how I can help on the Work with me page, or get in touch.\nI make things for the web and write high performance software. While I was an eZ Publish expert in the past, with extensive php experience; since 2019 my focus has been on Python and Docker. I have extensive experience with serverside development, including PHP, Ruby, and Python. I have experience with containerization, orchestration, and deployment of applications with docker and kubernetes.\nFor more information on my professional background, you can view my resume.\n","date":null,"permalink":"https://harmstyler.me/about/","section":"Tyler Harms","summary":"","title":"About"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/ez-publish/","section":"Categories","summary":"","title":"EZ Publish"},{"content":"Managing media in an eZ Publish 4 install was always a pain point for me. There were a number of problems that I had with media in eZ Publish 4 that are now addressed thanks to Symfony. The media I am referring to in this post are any content types placed in the media category, traditionally images, files, audio files, and video files.\nProblem Number 1, the content download #Anyone familiar with eZ Publish 4 knows that in order force a download of a media content object you needed to build a route to the content/download module/view. The content/download was very handy in its day because it did its job well, it forced a download on whatever file it was given. content/download even handled permissions for the file. It also, though, had a number of problems. The first problem with the content/download has to do with how developers were forced to build downloads.\nAn example download url built for eZ Publish 4: #1{def $download_url = 2 concat( \u0026#34;content/download/\u0026#34;, $attribute.contentobject_id, \u0026#34;/\u0026#34;, $attribute.content.contentobject_attribute_id, \u0026#34;/\u0026#34;, $attribute.content.original_filename )|ezurl} Hopefully the problems are obvious, not only is the url a pain to build, but the url is subject to change. That means that whenever an editor changes the uploaded file attached to the content, a new download url will be generated. This means that these urls can never be used outside the generated site pages (no emails, etc), unless you are OK with possible dead links from time to time.\nThe second problem I have with the content/download method is that the developer gets no control over the title of the downloaded file. The downloaded file will always have the name of the uploaded file. Most of the time this is fine, but when fine tuning is needed, the content/download is not flexible.\nProblem Number 2, the media full view #I have yet to have a client want the full view of a media content object include the site chrome. In eZ Publish 4, this was a pain point we just had to live with. The generated URL Alias for media content went to a full view of the content, by default with the site chrome but this can be changed. What could not be changed in eZ Publish 4 were the headers when viewing full view media. So, though I could adjust the chrome of a media full view, I could never do anything but display the content to the user using the default site headers.\nThe Solution, A Symfony media controller #I am left with two problems to solve, luckily the same solution fixes both. In eZ Publish 5 (and eZ Platform) developers can set custom controllers when matching content to templates. Providing a custom Symfony controller and action gives developers the control needed to set the necessary headers to force a download while providing a consistent path to file downloads.\nFirst, set the full view to use a new MediaController #1system: 2 global: 3 location_view: 4 full: 5 media: 6 controller: \u0026#34;AppBundle:Media:media\u0026#34; 7 match: 8 Identifier\\ContentType: [ file, image, video ] Second, create the MediaController #What this does is lets a user pass a dl query parameter to the eZ generated url alias. The query parameter tells then tells Symfony which header generate for the response, either download or view the media. This means that the generated eZ url alias will be the url to the media file and not the one subject to change, it also gets rid of the chrome around the image since we are serving the image directly.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 \u0026lt;?php namespace AppBundle\\Controller; use eZ\\Bundle\\EzPublishCoreBundle\\Controller; use Symfony\\Component\\HttpFoundation\\BinaryFileResponse; use Symfony\\Component\\HttpFoundation\\Response; use Symfony\\Component\\HttpFoundation\\ResponseHeaderBag; use eZ\\Publish\\SPI\\FieldType\\Value; class MediaController extends Controller { /** * @param $locationId * @param $viewType * @param bool $layout * @param array $params * * @return Response */ public function mediaAction($locationId, $viewType, $layout = false, array $params = array()) { $disposition = $this-\u0026gt;getDisposition(); $file = $this-\u0026gt;getFile($locationId); $response = $this-\u0026gt;getBinaryFileResponse($file, $disposition); return $response; } /** * @return ResponseHeaderBag::DISPOSITION_ATTACHMENT|ResponseHeaderBag::DISPOSITION_INLINE */ public function getDisposition() { $request = $this-\u0026gt;get(\u0026#39;request_stack\u0026#39;)-\u0026gt;getCurrentRequest(); $dl = $request-\u0026gt;get(\u0026#39;dl\u0026#39;); return $dl == 1 ? ResponseHeaderBag::DISPOSITION_ATTACHMENT : ResponseHeaderBag::DISPOSITION_INLINE; } /** * @param $locationId * @param string $fileName * * @return mixed */ private function getFile($locationId, $fileName = \u0026#39;file\u0026#39;) { $location = $this-\u0026gt;getRepository()-\u0026gt;getLocationService()-\u0026gt;loadLocation($locationId); $content = $this-\u0026gt;getRepository()-\u0026gt;getContentService()-\u0026gt;loadContent($location-\u0026gt;contentId); $file = $content-\u0026gt;getFieldValue($fileName); return $file; } /** * @param $file * @param string $dispositionType * * @return BinaryFileResponse */ private function getBinaryFileResponse(Value $file, $dispositionType = ResponseHeaderBag::DISPOSITION_ATTACHMENT) { $fileUri = $this-\u0026gt;container-\u0026gt;getParameter(\u0026#39;kernel.root_dir\u0026#39;) . \u0026#39;/../web\u0026#39; . $file-\u0026gt;uri; $response = new BinaryFileResponse($fileUri); $response-\u0026gt;setContentDisposition($dispositionType, $file-\u0026gt;fileName); return $response; } } Conclusion #I am sure there are other options for handling media that I haven\u0026rsquo;t though of, please share your ideas as I, for one, would love to hear them. With Symfony there is no one size fits all solution, so this MediaController may not work as designed. That said, I do hope this helps with managing media inside eZ Publish/Platform in the future. Please note that I do not show any permissions customizations in the MediaController, some permissions will still be automatically managed by eZ, but you will probably have to include some permissions checks in your actions.\n","date":"Jan 31, 2016","permalink":"https://harmstyler.me/posts/2016/managing-media-in-ez-publish/","section":"Posts","summary":"\u003cp\u003eManaging media in an eZ Publish 4 install was always a pain point for me. There were a number of problems\nthat I had with media in eZ Publish 4 that are now addressed thanks to Symfony. The media I am referring to\nin this post are any content types placed in the media category, traditionally images, files, audio files, and video\nfiles.\u003c/p\u003e","title":"Managing Media in eZ Publish 5"},{"content":"KNP Menu is a library by the guys at KNP Labs for building menus with php. The KNP menu library offers great features like automatically adding branch classes, marking the first and last item in a menu, and provides features for to automatically figure out the current menu item. Adding raw html into a label (eg \u0026lt;br\u0026gt;) can be a bit of a pain, however.\nThe KNP menu library offers all the necessary tooling to add raw html into the labels out of the box. There are, however, no examples on the doc pages. Firstly, the menu renderer needs to be told that the menu can have raw html in the labels. Secondly, the menu label needs to be identified as a safe label before the renderer will allow raw html.\nTo tell the renderer that the menu can contain raw html, or safe labels as the KNP menu library prefers to call it, the allow_safe_labels renderer option needs to be set to true.\nTo set the allow_safe_labels option using the KNPMenu Library:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 \u0026lt;?php use Knp\\Menu\\Matcher\\Matcher; use Knp\\Menu\\MenuFactory; use Knp\\Menu\\Renderer\\ListRenderer; // Create the new menu $factory = new MenuFactory(); $menu = $factory-\u0026gt;createItem(\u0026#39;My menu\u0026#39;); $menu-\u0026gt;addChild(\u0026#39;Home\u0026#39;, array(\u0026#39;uri\u0026#39; =\u0026gt; \u0026#39;/\u0026#39;)); $menu-\u0026gt;addChild(\u0026#39;Comments\u0026#39;, array(\u0026#39;uri\u0026#39; =\u0026gt; \u0026#39;#comments\u0026#39;)); $menu-\u0026gt;addChild(\u0026#39;Symfony2\u0026#39;, array(\u0026#39;uri\u0026#39; =\u0026gt; \u0026#39;http://symfony-reloaded.org/\u0026#39;)); // Render the menu with the necessary renderer options $rendererOptions = [\u0026#39;allow_safe_labels\u0026#39; =\u0026gt; true]; $renderer = new ListRenderer(new Matcher(), $rendererOptions); echo $renderer-\u0026gt;render($menu); Because rendering a menu is done differently using the KNPMenuBundle, the allow_safe_labels option is a is set quite differently:\n1{% raw %} 2{{ knp_menu_render(\u0026#39;primary_menu\u0026#39;, {\u0026#39;allow_safe_labels\u0026#39;: true}) }} 3{% endraw %} To tell the renderer that menu item contains a safe label, and can thereby be rendered unescaped, an extra parameter needs to be passed.\n1 2 3 4 5 \u0026lt;?php $menu -\u0026gt;addChild(\u0026#39;Foo\u0026lt;br\u0026gt;Bar\u0026#39;, array(\u0026#39;uri\u0026#39; =\u0026gt; \u0026#39;/\u0026#39;)) -\u0026gt;setExtra(\u0026#39;safe_label\u0026#39;, true); Options set via the setExtra() method end up as options on the menu item, accessible by a getExtra() method. The default twig template for KNPMenuBundle takes advantage of this parameter out of the box.\nIn summary, rendering menus using the KNP menu library with raw html in the label is possible without much work. I hope these examples help should you ever find yourself trying put raw html in a label in the future. Know that the barriers are in place for good reason, however, as allowing raw html in your labels could allow some XSS holes to pop up in your application should the menu labels be created by a user.\n","date":"Jul 20, 2015","permalink":"https://harmstyler.me/posts/2015/raw-html-in-knp-menu-labels/","section":"Posts","summary":"\u003cp\u003e\u003ca href=\"https://github.com/knplabs/KnpMenu\" target=\"_blank\" rel=\"noreferrer\"\u003eKNP Menu\u003c/a\u003e is a library by the guys at \u003ca href=\"http://knplabs.com\" target=\"_blank\" rel=\"noreferrer\"\u003eKNP Labs\u003c/a\u003e for building menus with php. The KNP menu library offers great features like automatically adding branch classes, marking the first and last item in a menu, and provides features for to automatically figure out the current menu item. Adding raw html into a label (eg \u003ccode\u003e\u0026lt;br\u0026gt;\u003c/code\u003e) can be a bit of a pain, however.\u003c/p\u003e","title":"Allowing Raw HTML in KNP Menu Labels"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/knp-menu/","section":"Tags","summary":"","title":"KNP Menu"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/angular/","section":"Categories","summary":"","title":"Angular"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/javascript/","section":"Categories","summary":"","title":"Javascript"},{"content":"I enjoy being a PHP Developer. PHP is a very flexible language and Symfony2 is a modern framework that forces many SOLID habits on developers. I also enjoy working with AngularJS. AngularJS makes Javascript fun and much easier to work with. Moreover, much like Symfony2, it forces good habits on developers. Getting the two to work together, however, can be a little bit of a chore if not careful.\nThe Problem #For starters, when the AngularJS $http service POSTs data the header application/x-www-form-urlencoded is never set (unlike jQuery\u0026rsquo;s $.ajax()). Also, the $http data is not serialized when sent. Both of these facts mean that the $_POST variable is never set properly by php. Without the $_POST variable Symfony\u0026rsquo;s built in form handling cannot be used.\nIt should be noted that Symfony\u0026rsquo;s Request object allows for access to raw post content via the getContent() method; meaning POST data can be processed manually if needed.\nWhen creating a FormType in Symfony, a name is required. By default that name will be called something like bundle_path_entityname, and all post content will need to be in a multidimensional array with the FormType name as the key. This means that the posted data we send with Angular will need to follow this standard.\nConsider shortening the FormType name into something more meaningful.\nThe Fix #The fix is actually pretty simple:\nangular needs to forced into setting a header the data needs to be serialized and the data needs to be normalized into a multidimensional array. On all $http requests we can set a headers object. That means that the Content-Type header can be set on the $http request.\n1headers: { 2 \u0026#39;Content-Type\u0026#39;: \u0026#39;application/x-www-form-urlencoded\u0026#39; 3} To serialize the data being sent, jQuery works well. Just use $.param() on the data and be done with it.\n1data: $.param({ 2 postData 3}) Normalizing the data into a proper serialized string is as simple as creating a javascript object. Remember that the FormType name needs to be the key or parent attribute on the object.\n1var postData = { 2 formtype_name: { 3 id: some_id, 4 name: some_name 5 } 6} Putting it all together, the javascript should look something like this:\n1var postData = { 2 formtype_name: { 3 id: some_id, 4 name: some_name 5 } 6}; 7 8$http({ 9 method: \u0026#34;POST\u0026#34;, 10 url: url, 11 headers: { 12 \u0026#39;Content-Type\u0026#39;: \u0026#39;application/x-www-form-urlencoded\u0026#39; 13 }, 14 data: $.param(postData) 15}); Doing all this work on the frontend will allow for use of the normal form processing built into Symfony.\n","date":"Sep 6, 2014","permalink":"https://harmstyler.me/posts/2014/mixing-angular-and-symfony/","section":"Posts","summary":"\u003cp\u003eI enjoy being a PHP Developer. PHP is a very flexible language and Symfony2 is\na modern framework that forces many SOLID habits on developers. I also enjoy\nworking with AngularJS. AngularJS makes Javascript fun and much easier to work\nwith. Moreover, much like Symfony2, it forces good habits on developers. Getting\nthe two to work together, however, can be a little bit of a chore if not careful.\u003c/p\u003e","title":"Mixing AngularJS and Symfony"},{"content":"One of my favorite features of Ruby on Rails is that all models are timestamped by default. Ruby on Rails, of course, opts for convention over configuration. Symfony2, on the other hand, opts for configuration over convention. Anyone who has worked with Symfony2 can speak to the mass of config files that exist in a project. I can understand, therefore, why timestamped models (entities) are not a feature in Symfony2. In this post I will show how to add timestamp all your Symfony2 entities. For this post, I chose to use Doctrine as my ORM, so all code show directly relates to Doctrine and Symfony2.\nLuckily, adding and updated and created fields to an entity in Symfony2 with Doctrine is quite easy:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 \u0026lt;?php /** * @var \\DateTime * * @ORM\\Column(name=\u0026#34;created\u0026#34;, type=\u0026#34;datetime\u0026#34;) */ private $created; /** * @var \\DateTime * * @ORM\\Column(name=\u0026#34;updated\u0026#34;, type=\u0026#34;datetime\u0026#34;) */ private $updated; public function __construct() { $this-\u0026gt;setCreated(new \\DateTime()); $this-\u0026gt;setUpdated(new \\DateTime()); } // getters \u0026amp; setters /** * Auto set the updated date * * @ORM\\PreUpdate */ public function setUpdatedValue() { $this-\u0026gt;setUpdated(new \\DateTime()); } The code should be pretty self explanitory, we have two DateTime field types that get initially set on entity creation in the contstructor. I then add a setUpdatedValue() function that runs when on PreUpdate of the entity.\nThe above code works very well, but it is not an ideal solution for working with multiple models. Of course, we could add the updated/created values to every entity, but that wouldn\u0026rsquo;t be very DRY of us.\nThe solution is to use an Abstract Class that our entity can extend. The abstract class, TimeStampedEntity, will have two entity variables $updated and $created and will look nearly identical to the code above when it is complete. The key that will make this work with Doctrine and Symfony2 is Doctrine\u0026rsquo;s MappedSuperclass declaration that tells Symfony and Doctrine that the abstract class is a Superclass with declared entity attributes. What we are left with is a Superclass that can extend any model to add updated and created attributes to your data.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 \u0026lt;?php namespace HarmsTyler\\Common\\Entity; use Doctrine\\ORM\\Mapping as ORM; /** * @ORM\\MappedSuperclass * @ORM\\HasLifecycleCallbacks */ abstract class TimeStampedEntity { /** * @var \\DateTime * * @ORM\\Column(name=\u0026#34;created\u0026#34;, type=\u0026#34;datetime\u0026#34;) */ private $created; /** * @var \\DateTime * * @ORM\\Column(name=\u0026#34;updated\u0026#34;, type=\u0026#34;datetime\u0026#34;) */ private $updated; /** * Set created * * @param \\DateTime $created * @return self */ public function setCreated($created) { $this-\u0026gt;created = $created; return $this; } /** * Get created * * @return \\DateTime */ public function getCreated() { return $this-\u0026gt;created; } /** * Set updated * * @param \\DateTime $updated * @return self */ public function setUpdated($updated) { $this-\u0026gt;updated = $updated; return $this; } /** * Get updated * * @return \\DateTime */ public function getUpdated() { return $this-\u0026gt;updated; } /** * Auto set the updated date * * @ORM\\PreUpdate */ public function setUpdatedValue() { $this-\u0026gt;setUpdated(new \\DateTime()); } /** * Set initial value for created/updated values * * @ORM\\PrePersist */ public function setCreatedValues() { $this-\u0026gt;setCreated(new \\DateTime()); $this-\u0026gt;setUpdated(new \\DateTime()); } } An example in use:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \u0026lt;?php namespace Blend\\Iterate\\BlogBundle\\Entity; use HarmsTyler\\Common\\Entity\\TimeStampedEntity; use Doctrine\\ORM\\Mapping as ORM; /** * Post * * @ORM\\Table() * @ORM\\Entity */ class Post extends TimeStampedEntity { edited to add setCreatedValues()\n","date":"Feb 7, 2014","permalink":"https://harmstyler.me/posts/2014/abstract-time-stamped-entity-in-symfony2/","section":"Posts","summary":"\u003cp\u003eOne of my favorite features of Ruby on Rails is that all models are timestamped by default. Ruby on Rails, of course, opts for convention over configuration. Symfony2, on the other hand, opts for configuration over convention. Anyone who has worked with Symfony2 can speak to the mass of config files that exist in a project. I can understand, therefore, why timestamped models (entities) are not a feature in Symfony2. In this post I will show how to add timestamp all your Symfony2 entities. For this post, I chose to use Doctrine as my ORM, so all code show directly relates to Doctrine and Symfony2.\u003c/p\u003e","title":"Abstract Time Stamped Entity in Symfony2"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/doctrine/","section":"Tags","summary":"","title":"Doctrine"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/symfony2/","section":"Categories","summary":"","title":"Symfony2"},{"content":" Administrating Solr is for developers and Solr administrators who have basic knowledge of Solr and are looking for ways to keep their Solr server healthy and well maintained. Basic working knowledge on Apache Lucene is recommended, but is not mandatory.\nPact Publishing released a new book in October 2013 called Administrating Solr. In the book readers can find out all sorts of information about, you guessed it, administrating your Solr installation. Whether it be Searching Solr, Monitoring Solr, Managing Solr, or Optimizing Solr the book has your bases covered.\nAt the start, Administrating Solr gets readers familiar with Solr terminology. The Solr Request, Response and RequestHandler are rightfully given their due explanation, as they are the foundation of the Solr configuration. Throughout the rest of Chapter 1 readers can find a plethora of information about searching Solr; whether by query, filter, facet, geospacial, or any combination of the various searching methods, Administrating Solr has readers covered. Chapter 1: Searching Solr reads like a Solr Cookbook when it comes to the different ways to search Solr.\nChapter 2: Monitoring Solr is all about how to set up your own Solr monitoring; monitoring that goes beyond what is presented to users of the Solr Admin. All of what is described is easy to enable/install for those who want deaper Solr monitoring. The chapter finishes with an overview of some of the more popular monitoring tools, intended for those who don\u0026rsquo;t want to do all the monitoring setup themselves and would prefer to rely on some third party service. Coverered in the Monitoring Tools section are: Opsview, New Relic, and SPM. The author seems partial to SPM and I have only ever used New Relic\u0026rsquo;s integration, but it seems fair to say that you will be provided very high quality Solr monitoring with any of the listed tools.\nThe 3rd Chapter: Magaging Solr is all about various scripts you can create to help you manage your Solr isntall better. This chapter again reads like a cookbook, which is very handy if you are looking for infomation about backing up Solr, Solr Replication. Also covered in the chapter is Solr Logging. Solr logging was refigured in Solr 4.3 to be more flexible, so included are some ways to get better logging in your install.\nThe final chapter, Optimizing Solr Tools and Scripts is by far the best chapter. Before this book I had never heard of such as tool as Drools. This book not only gives readers an understanging of what Drools is, but it also provides an overview of how to use it with real examples. Also covered is setting up Language Detection inside Solr so that the right fields are searched depending on language. Sentence detection and splitting, Tokinization, Part-Of-Speech tagging, and Named entity recognition are all given minor coverage. Each of these topics could be deserving of there own book, so it is understandable that they receive only a paragraph telling the reader what the topic is. The chapter finishes with a case study in Drupal, though the author\u0026rsquo;s suggestions could be implemented into any platform.\nAll in all, this book is very useful to those wishing to get their feet wet with Solr. Solr is an incredibly useful search engine capable of some pretty amazing feats. The book is worth it for the last chapter alone. This book was short and to the point and oh so useful. Check it out at Pact Publishing\u0026rsquo;s store.\n","date":"Nov 29, 2013","permalink":"https://harmstyler.me/posts/2013/administrating-solr-a-review/","section":"Posts","summary":"Pact Publishing released a new book in October 2013 called Administrating Solr. In the book readers can find out all sorts of information about, you guessed it, administrating your Solr installation. Whether it be Searching Solr, Monitoring Solr, Managing Solr, or Optimizing Solr the book has your bases covered.","title":"Administrating Solr: A Review"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/book-review/","section":"Tags","summary":"","title":"Book Review"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/solr/","section":"Categories","summary":"","title":"Solr"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/solr/","section":"Tags","summary":"","title":"Solr"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/django/","section":"Categories","summary":"","title":"Django"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/python/","section":"Categories","summary":"","title":"Python"},{"content":"I would say that South Dakota Code Camp 2013 was another huge success. The organizers were once again able to find some great speakers that cover a wide array of topics. Moreover, this year I had the opportunity to speak. I chose to speak about Django, one of my favorite web frameworks. Anyway, here is a link to my slides.\nAn Introduction to Django / Tyler Harms ","date":"Nov 11, 2013","permalink":"https://harmstyler.me/posts/2013/south-dakota-code-camp-2013/","section":"Posts","summary":"I would say that South Dakota Code Camp 2013 was another huge success. The organizers were once again able to find some great speakers that cover a wide array of topics. Moreover, this year I had the opportunity to speak. I chose to speak about Django, one of my favorite web frameworks.","title":"South Dakota Code Camp 2013"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/jquery/","section":"Tags","summary":"","title":"JQuery"},{"content":"It turns out that when trying to use jQuery\u0026rsquo;s animate to scrollTop on the main body of an html document different browsers require different targets to scroll to. Webkit requires targeting the body of the document while all other browsers that I know of use html. The typical solution is to target both in the same call:\n1$(\u0026#39;html, body\u0026#39;).animate({ 2 scrollTop: \u0026#39;0px\u0026#39;,\u0026#39;fast\u0026#39;, function(){ 3 // Code to be fired when complete goes here (fired twice) 4 } 5}); This solution is problematic, however, because even though the scrollTop will only work once, the callback is fired twice. That means that if we were relying on the callback to tell us that the function is complete, our complete function would fire twice. Luckily, there is a relatively simple solution to this problem. Using jQuery\u0026rsquo;s browser plugin we can have webkit browsers target the body and all others target the html.\n1// If using a newer version (\u0026gt;= 1.9) of jQuery a separate browser 2// plugin is required 3$(jQuery.browser.webkit ? \u0026#34;body\u0026#34;: \u0026#34;html\u0026#34;).animate({ 4 scrollTop: \u0026#39;0px\u0026#39;,\u0026#39;fast\u0026#39;, function(){ 5 // Code to be fired when complete goes here 6 } 7}); This solution worked well for me. I am open to different ideas or solutions if you know of any.\n","date":"Aug 9, 2013","permalink":"https://harmstyler.me/posts/2013/jquery-animate-body-for-all-browsers/","section":"Posts","summary":"It turns out that when trying to use jQuery\u0026rsquo;s \u003ccode\u003eanimate\u003c/code\u003e to \u003ccode\u003escrollTop\u003c/code\u003e on the main body of an html document different browsers require different targets to scroll to. Webkit requires targeting the \u003ccode\u003ebody\u003c/code\u003e of the document while all other browsers that I know of use \u003ccode\u003ehtml\u003c/code\u003e.","title":"jQuery Animate Body Scroll For All Browsers"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/search/","section":"Tags","summary":"","title":"Search"},{"content":"I recently faced a strange problem. I was faceting off of multiple facet.fields and I found the need to sort them independently of each other. As far as I knew up to this point facet.sort could only be set once. When I read the doc, however, it was claimed that the facet.sort could be set on a per field basis; sadly no examples were provided nor was it explained any further. Luckily, after some digging, I found out that setting the sort (index or count) on a per field is very easy.\nA typical solr fetch with facets might look like this (assuming type_s is indexed):\n1/solr/directory/select?q=*:*\u0026amp;wt=json\u0026amp;indent=true\u0026amp;facet=true\u0026amp;facet.field=type_s Solr sets facet.sort to count by default, so that can be left undeclared or can be explicitly declared, it does not matter. A problem arises, however, when you have multiple facet.fields and you want them sorted differently. To solve this you need to follow this format for the declared sort: f.\u0026lt;facetted_field\u0026gt;.facet.sort=\u0026lt;index|count\u0026gt;. Using the same example as before, but with an extra field it would look like this:\n1/solr/directory/select?q=*:*\u0026amp;wt=json\u0026amp;indent=true\u0026amp;facet=true\u0026amp;facet.field=type_s\u0026amp;facet.field=location_s\u0026amp;f.location_s.facet.sort=index This would sort type_s by count (default) and sort location_s by index. The opposite would also work.\n1/solr/directory/select?q=*:*\u0026amp;wt=json\u0026amp;indent=true\u0026amp;facet=true\u0026amp;facet.field=type_s\u0026amp;facet.field=location_s\u0026amp;facet.sort=index\u0026amp;f.location_s.facet.sort=count This would change the default sort order to use index for sort and location_s would use count for sorting.\n","date":"Jul 29, 2013","permalink":"https://harmstyler.me/posts/2013/sort-solr-facets-on-per-field-basis/","section":"Posts","summary":"","title":"Sort Solr Facets on Per Field Basis"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/chrome/","section":"Categories","summary":"","title":"Chrome"},{"content":"As a web developer whose browser of choice is Google Chome, I use the Chrome console a lot. Chrome is already the best browser out there and always getting better. More than the browsing experience though, I maintain that it has the best dev environment available. It is the little things that really make Chrome so much better than its compeditors (docking to the right for instance). As if you needed any more reasons to like Chrome, the Chrome team just pushed an update to \u0026lsquo;clean up the console.\u0026rsquo; This was a fix that did nothing for end user usablity, but fixes that annoying little message telling you about your font mimetypes. This was not a fix that I needed for development, just a nicity that the Chome team provided.\nhttps://twitter.com/paul_irish/status/351783000154992641\nThe fix: https://chromiumcodereview.appspot.com/18308003/patch/1/2\n","date":"Jul 1, 2013","permalink":"https://harmstyler.me/posts/2013/chrome-webfont-mimetypes/","section":"Posts","summary":"","title":"Chrome Webfont Mimetypes"},{"content":"Let me start by saying that I was very excited by IE10. Internet Explorer 10 was supposed to be the premier IE web browser. Microsoft was so confident that they created a browser that could meet modern standards that they dropped conditional comments in IE10. If you don\u0026rsquo;t know anything about developing for IE, just know that it sucks; it was, however, aleviated some by conditional comments that allowed for custom styles/js/whatever for IE versions. My first time working with IE10 I was made harshly aware that conditional comments no longer work. What\u0026rsquo;s worse, my site that functioned and looked great in IE9 no longer functioned in IE10. How does this happen?\nThe more I work with IE10 the more issues I find with it, Microsoft needs to bring conditional comments back. Until then, however, there is a simple script you can stick in your javascripts that will identify when IE10 is being used so that you can target it in your css or javascript.\nThe javascript:\n1// Add IE10 class to IE10 2// $.browser requires jquery \u0026lt; 1.9 3// $.browser can be used in jquery \u0026gt; 1.9 with plugin 4if ($.browser.msie \u0026amp;\u0026amp; $.browser.version === 10) { 5 $(\u0026#34;html\u0026#34;).addClass(\u0026#34;ie10\u0026#34;); 6} It is sad that we must do this for a supposed modern browser; nevertheless, I hope this helps for when you have to work with IE10.\n","date":"Jun 29, 2013","permalink":"https://harmstyler.me/posts/2013/ie10-conditional-comments/","section":"Posts","summary":"","title":"IE10 Conditional Comments"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/internet-explorer/","section":"Tags","summary":"","title":"Internet Explorer"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/php-codesniffer/","section":"Categories","summary":"","title":"PHP CodeSniffer"},{"content":"With eZ Publish moving to Symfony2, I found that I was in need of learning some new coding standards. I never really looked into PHP CodeSniffer while doing eZ Publish development (most of my code was in the templates), so I never really thought about PHP CodeSniffer as an option until starting work with Symphony2. To the point, I just wanted to share the Symfony2 PHP CodeSniffer Coding Standard on Github. It is really easy to install and because phpcs works with PHPStorm and Sublime Text 2, sniffing out php files couldn\u0026rsquo;t be easier.\n","date":"Apr 30, 2013","permalink":"https://harmstyler.me/posts/2013/symfony2-coding-standards/","section":"Posts","summary":"","title":"Symfony2 Coding Standards"},{"content":"I have been working on getting a new site theme up and running for a while now. My hold up was that I could never commit to a platform. I knew that I wanted off Wordpress and onto something more developer friendly. Originally I was going to put this site on eZ Publish, but that just seemed like overkill. Next I thought about creating a Symfony2 blog, but I wanted to cheapen my hosting options so I opted against Symfony2. I finally decided upon Rails, hosted on the free tier of Heroku.\nI was in the middle of development when I discovered Octopress. How I had never heard of this platform before, I don\u0026rsquo;t know. In under 4 hours total I had my Wordpress site migrated off of Wordpress and onto a Github Pages site generated by Octopress; it was a very smooth transition. Because development was so fast, I opted to stick with Octopress and GH-Pages.\nMy site is now running statically using Octopress as a site generator and Github as a free host. Octopress is built on Jekyll, so I can make plugins easily (in Ruby) as well as pull in any existing plugin with ease. Octopress seems like it is built to be the developer blogging platform. The code blocks, the post generation, and the fact that it is built on Ruby all helped me settle on Octopress as my new blogging platform. I hope you enjoy the new site!\n","date":"Apr 15, 2013","permalink":"https://harmstyler.me/posts/2013/new-site-theme/","section":"Posts","summary":"","title":"New Site Theme"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/octopress/","section":"Categories","summary":"","title":"Octopress"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/wordpress/","section":"Categories","summary":"","title":"Wordpress"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/solarium/","section":"Tags","summary":"","title":"Solarium"},{"content":" Solr has quickly become one of the most popular search engines available. Due to Solr\u0026rsquo;s many features and its low barrier to entry it can be an ideal candidate when seeking a search engine. Solarium, a PHP Solr Client, takes Solr one step further by giving developers easier access to the Solr API. 1{ 2 \u0026#34;require\u0026#34;: { 3 \u0026#34;solarium/solarium\u0026#34;: \u0026#34;3.0.0\u0026#34; 4 } 5} 1\u0026lt;?php 2$config = array( 3 \u0026#39;endpoint\u0026#39; =\u0026gt; array( 4 \u0026#39;localhost\u0026#39; =\u0026gt; array( 5 \u0026#39;host\u0026#39; =\u0026gt; \u0026#39;127.0.0.1\u0026#39;, \u0026#39;port\u0026#39; =\u0026gt; \u0026#39;8983\u0026#39;, \u0026#39;path\u0026#39; =\u0026gt; \u0026#39;/solr/\u0026#39; 6 ) 7 ) 8); 9 10// new Solarium Client object 11$client = new Solarium\\Client($config); 1\u0026lt;field name=\u0026#34;id\u0026#34; type=\u0026#34;int\u0026#34; indexed=\u0026#34;true\u0026#34; stored=\u0026#34;true\u0026#34; /\u0026gt; 2\u0026lt;field name=\u0026#34;name\u0026#34; type=\u0026#34;string\u0026#34; indexed=\u0026#34;true\u0026#34; stored=\u0026#34;true\u0026#34; multiValued=\u0026#34;true\u0026#34; /\u0026gt; 3\u0026lt;field name=\u0026#34;text\u0026#34; type=\u0026#34;text\u0026#34; indexed=\u0026#34;true\u0026#34; stored=\u0026#34;true\u0026#34; /\u0026gt; Introduction to Solr: #Search is an important part of most web projects and should be given much care. Throughout the years, making content searchable has changed a great deal. Obviously, exposing SQL queries to users through search is dangerous. Modern search implementations require a secure approach, such as a stand-alone search server, separate from site data. There are many search options to choose from. Your search list might include: Sphinx, Flax, ElasticSearch, Google, and Solr. Solr’s scalability, speed, built in features and community make it an ideal platform for any project. Luckily, integrating Solr into your PHP project has been made easy by the Solarium Project.\nIn Apache’s own words, \u0026ldquo;Solr is an open source enterprise search server based on the Lucene Java search library, with XML/HTTP and JSON, Ruby, and Python APIs, hit highlighting, faceted search, caching, replication, and a web administration interface\u0026rdquo;1. Apache Solr is an open source, extensible, stand-alone search engine, built on Lucene, managed by The Apache Software Foundation. There are currently two stable versions of Solr available for use; Solr 3.6.2 and Solr 4.0. Either version may be used with Solarium; Solr 4 ships with added features, however, so use Solr 4 if possible.\nSolr is designed to run as a stand-alone Java web application (app). The documentation for downloading, installing, and running Solr can be found at http://lucene.apache.org/solr/tutorial.html. Because Solr is meant to be separate from the primary project, it can be installed anywhere. All interactions with Solr happen over HTTP through a REST-like API. Solarium utilizes both PHP and JSON for its interactions with Solr.\nOne of the greatest features of Solr is its scalability. Replication can be set up to run multiple Solr instances in a master/slave setup, just like MySQL replication.The documentation for setting up a master/slave setup is very thorough, so it will not be discussed further here. Another option for scaling Solr is to search multiple search cores. Solr can run multiple search cores at a time. Solr can search multiple cores at a time through its distributed search and sharding capabilities. Large search indexes can be split into multiple smaller indexes; then through a distributed search multiple cores are searched. The best part of a multicore search setup is that not all cores need to exist on the same server. This allows for very flexible search architecture.\nSolarium acts as a bridge #Solr has a REST-like API, so interactions with it are very simple and happen over HTTP. The Solarium Project is \u0026ldquo;an open source Solr client library for PHP applications\u0026rdquo; 2, which makes interacting with Solr even simpler. Solarium exists to expose the Solr API through an easy to use PHP library.\nInstallation of Solarium has been made very easy through the use of Composer (Listing 1). Installing through Composer will automatically add the library to the PHP autoload path. To install Solarium into a project not utilizing Composer, the files will need to be downloaded from GitHub (https://github.com/basdenooijer/solarium/tags). Next, add the solarium library to the PHP autoload path manually. After Solarium is installed, Solr can be accessed through sending a Solarium Client ($client) object to a configuration array. The configuration array should be made up of the Solr host, port and path (Listing 2).\nAdding content to Solr #Before anything can be searched in Solr, documents (docs) must be added to the Solr index. Solr maintains a collection of docs in its index. A doc is a collection of fields and values. Solr fields must be defined in the Solr schema. A field can occur multiple times in a doc.\nTo make an update to Solr using Solarium, start with the $client object. Using the $client object, create an $update instance then make a doc:\n1$update = $client-\u0026gt;createUpdate(); 2$doc1 = $update-\u0026gt;createDocument(); With the newly created $doc, begin adding content to Solr fields as instances of the $doc:\n1$doc1-\u0026gt;id = 123; 2$doc1-\u0026gt;name = \u0026#39;First Document\u0026#39;; 3$doc1-\u0026gt;text = \u0026#39;This is the first document\\\u0026#39;s content\u0026#39;; 4 5// Create another doc 6$doc2 = $update-\u0026gt;createDocument(); 7$doc2-\u0026gt;id = 234; 8$doc2-\u0026gt;name = \u0026#39;Next Document\u0026#39;; 9$doc2-\u0026gt;text = \u0026#39;This is the next document\\\u0026#39;s content\u0026#39;; In this example, the Solr schema would have to be set up to accept the id, name and text fields. Solr ships with an example search app with a good demo of a Solr schema; each field will need to be added to the schema in order for Solr to add the docs to the search index (Listing 3). Next, add each $doc created to an array so that addDocuments() can be called on the array. This will run the Solr add command for every document in the array:\n1$update-\u0026gt;addDocuments(array($doc1, $doc2)); The newly-added docs will not actually become a permanent part of the Solr index until Solr is told to commit all added docs to its index. To do this with Solarium, call addCommit() to generate a commit message for Solr, then a call to update() with the $update will send the commit:\n1$update-\u0026gt;addCommit(); $client-\u0026gt;update($update); The new docs should now exist and be searchable using Solr.\nSearching Solr through Solarium #Solarium offers a robust PHP API for searching Solr. To run a basic query, start with the $client object. Next, call the createSelect() instance of the $client object to generate the query for execution:\n1$query = $client-\u0026gt;createSelect(); 2 3// *:* is equivalent to telling solr to return all docs 4$query-\u0026gt;setQuery(\u0026#39;*:*\u0026#39;); Because the createSelect() function is being used to generate the query, the select() function should be used when executing the query:\n1$resultSet = $client-\u0026gt;select($query); Use the $resultSet array to iterate over the array and display the results:\n1echo \u0026#39;\u0026lt;div class=\u0026#34;search-results\u0026#34;\u0026gt;\u0026#39;; 2foreach ($resultSet as $result) { 3 echo \u0026#39;\u0026lt;div class=\u0026#34;search-result\u0026#34;\u0026gt;\u0026#39;; 4 echo \u0026#39;\u0026lt;p\u0026gt;\u0026#39; . $result-\u0026gt;id . \u0026#39;\u0026lt;/p\u0026gt;\u0026#39;; 5 echo \u0026#39;\u0026lt;p\u0026gt;\u0026#39; . $result-\u0026gt;name . \u0026#39;\u0026lt;/p\u0026gt;\u0026#39;; 6 echo \u0026#39;\u0026lt;p\u0026gt;\u0026#39; . $result-\u0026gt;text . \u0026#39;\u0026lt;/p\u0026gt;\u0026#39;; 7 echo \u0026#39;\u0026lt;/div\u0026gt;\u0026#39;; 8} 9echo \u0026#39;\u0026lt;/div\u0026gt;\u0026#39;; The $resultSet can also be refined. Setting up pagination, for instance, is very easy:\n1$query-\u0026gt;setStart(0)-\u0026gt;setRows(10); Sorting is also very easy (note that Solr will sort by score if no sort is set):\n1$query-\u0026gt;addSort(\u0026#39;name\u0026#39;, Solarium_Query_Select::SORT_ASC); Filtering, setting query fields, boosting, and faceting are also possible ways of searching Solr. Filtering is another way of narrowing search results without using a query. Query fields allow for control over what fields are searched. Boosting gives certain field’s higher precedence in the Search. Facets give users a way to better navigate search results. Solarium gives easy access to all the various ways of searching Solr content.\nRemoving content from Solr #Solr does not have a command called update; however, updates can be performed by re-adding content. Solr uses a unique identifier for each doc and only allows for one instance of the identifier in the search index. To update content, call the add command on an already existent Solr doc id. Sometimes updating Solr with new content isn’t enough. Sometimes the need to delete content from Solr arises. Doing this is similar to when adding docs, create an $update instance:\n1$update = $client-\u0026gt;createUpdate(); Using the $update instance, documents can be deleted by id or by query. This offers a great deal of flexibility for deleting content. Deleting by id is used more often because it offers very precise deletes:\n1$update-\u0026gt;addDeleteById(234); 2$update-\u0026gt;addCommit(); 3$client-\u0026gt;update($update); Deleting by query, however, can be incredibly powerful and useful. When starting development of a new search project with test content, for instance, deleting by query offers the ability to remove all test content. If all the test content starts with the word test, deleting the content is simple:\n1$update-\u0026gt;addDeleteQuery(\u0026#39;name:test*\u0026#39;); 2$update-\u0026gt;addCommit(); 3$client-\u0026gt;update($update); Another common practice is to wipe the index and start fresh. Using delete by query, this task is again made very simple:\n1$update-\u0026gt;addDeleteQuery(\u0026#39;*:*\u0026#39;); 2$update-\u0026gt;addCommit(); 3$client-\u0026gt;update($update); Solarium’s delete doc API is very useful in maintaining an up-to-date search index.\nConclusion #Solr is an incredibly scalable, powerful and easy-to-use search engine that should be considered for any search project. Solr is designed to be language agnostic through its REST-like API, and thanks to Solarium it fits very easily into any PHP project. This article covers only three use cases for Solarium; the examples shipped with Solarium offer a great deal more. Search highlighting, debugging, optimizing, and ‘more like this’ functionality are all possible with Solr and Solarium.\nReferences # Apache Solr: http://projects.apache.org/projects/solr.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nThe Solarium Project: http://www.solarium-project.org/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"Mar 15, 2013","permalink":"https://harmstyler.me/posts/2013/start-search-with-solr-integrating-solr-into-any-php-project-is-easy-with-solarium/","section":"Posts","summary":"","title":"Start Searching with Solr - Integrating Solr into any PHP project is easy with Solarium"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/authorization/","section":"Tags","summary":"","title":"Authorization"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/symfony-access-control/","section":"Tags","summary":"","title":"Symfony Access Control"},{"content":"I am very new to Symfony development. While developing a side project app I found I needed to control access to a certain area to logged in users only. Based on my previous development history I was expecting to have to determine if a user was logged in via the controller. I figured I would need to find an is_logged_in boolean and use it to determine user access control. What I found while doing my development is that Symfony2 takes care of access control in a much more eloquent way. Rather than having to determine if a user is logged in inside each controller, Symfony2 controls access using a system similar to its routing system. Inside a Symfony2 app there is, by default, a security.yml file that is used to control access. Inside the security.yml file locate the access_control declaration. By simply declaring a route inside the access_control yml array we can control which users have access to which routes, like so:\n1access_control: 2 - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } 3 - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } 4 - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } 5 - { path: ^/admin/, role: ROLE_ADMIN } 6 - { path: ^/new, role: ROLE_USER } As you can see with my code, for anything beyond the /admin/ route the ROLE_ADMIN is required. And for anyone accessing the /new route the ROLE_USER is required.\nThis is obviously pretty trivial stuff, but for somebody new to Symfony2 development like myself, I found it an incredibly simple and powerful way of controlling access to my app.\n","date":"Mar 2, 2013","permalink":"https://harmstyler.me/posts/2013/symfony2-access-conrol/","section":"Posts","summary":"","title":"Symfony2 Access Control"},{"content":"I finally decided on my first eZ Publish 5 project\u0026hellip; my site of course. I am currently in the middle of upgrading my wordpress install to eZ Publish. Honestly, I don\u0026rsquo;t really need anything more than a simple Wordpress install, but it will be nice to get an eZ5 site under my belt.\nSeeing as I am in the middle of development, there have been a few bumps in the development road that I have found and would like to share. Firstly, I can find no way to run eZ Publish 5 completely in legacy mode, no matter what I do my pagelayout template needs to be a twig template. I posed the question to the eZ Community and it sounds like this should be possible, but it just wouldn\u0026rsquo;t work for me (see the discussion here). I actually don\u0026rsquo;t have a problem with this, it\u0026rsquo;s not like being forced to use Symfony is a bad thing.\nSecond, I do not see a strait-forward way to run multiple sites in one eZ Publish 5 install right now. Site settings are declared in the ezpublish.yml file and are grouped appropriately. My problem is that the parameters.yml file only allows for the declaration of one default template. In my opinion the default template declaration should be tied to a siteaccess or a siteaccess group.\nThird, the index_dev.php file doesn\u0026rsquo;t work for some reason. It would be nice to be able to use the Symfony dev toolbar. So far my only workaround has been to add the dev params to the index.php file. Either fixing this or allowing DebugOutput on a Symfony generated page would suffice.\nIt wouldn\u0026rsquo;t be fair to simply criticize eZ5, because it does work. The eZ Team did a hell of a job with eZ5, far more than I would have expected for a first iteration. Honestly I was expecting eZ5 to switch to the Symfony Components for round 1 and work on the rest later.\nI think that far and above any other nicety of eZ5 is the ability to generate custom routes. Creating a custom module in eZ 4 was not exactly easy, but now with Symfony, creating a custom route/controller/view is extremely straight-forward and borderline easy. If a person has ever worked with Symfony in the past, creating custom \u0026lsquo;modules\u0026rsquo; will be a snap.\nAnother awesome part of eZ5 is the full REST API. Although I have yet to use it, I know it is there and that the eZ Team put a lot of work into it, so I have to give it props.\nFinally, twig is awesome to work with.\n","date":"Jan 19, 2013","permalink":"https://harmstyler.me/posts/2013/ez-publish-5-first-thoughts/","section":"Posts","summary":"","title":"eZ Publish 5 - First Thoughts"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/ez-find/","section":"Tags","summary":"","title":"EZ Find"},{"content":"I am finally taking the time to write about optimizing the Solr schema file. In my last post I showed that eZ Find already ships with a default search field called ezf_df_text. The downside of searching one field is that field needs to be controlled, and the only way it can be controlled is by manually editing the Solr schema file. As you might expect, eZ Find does not control this file very well out of the box.\n1\u0026lt;copyField source=\u0026#34;*_lk\u0026#34; dest=\u0026#34;ezf_df_text\u0026#34;/\u0026gt; 2\u0026lt;copyField source=\u0026#34;*_k\u0026#34; dest=\u0026#34;ezf_df_text\u0026#34;/\u0026gt; 3\u0026lt;copyField source=\u0026#34;*_t\u0026#34; dest=\u0026#34;ezf_df_text\u0026#34;/\u0026gt; 4\u0026lt;copyField source=\u0026#34;*_s\u0026#34; dest=\u0026#34;ezf_df_text\u0026#34;/\u0026gt; A copyField will take any field you tell it to and copy it into another field. The code above is what ships with eZ Find, every single *_lk (lckeyword), *_k (keyword), *_t (text), and *_s (string) is added to the ezf_df_text field. For obvious reasons the average installation probably doesn\u0026rsquo;t need every piece of content searchable. The first step is to delete the default copyFields (e.g. your site users probably shouldn\u0026rsquo;t be searched by default).\nLuckily, there is only one other step! Unfortunately, this one step can be very time consuming. Visit the luke output for your Solr install and make a list of all the fields you need to search. Make a copyField for every field you want to search, adding each field to the ezf_df_text field.\nRemember, this can offer major speed improvements in your searches, especially if you are managing a large amount of content. There are still other optimizations (stored/not stored, indexed/not indexed, term vectors, etc), but that is for another time.\nFinally, I did submit a pull request to the eZ Find project. It adds a new search parameter allowing users to declare custom search fields. https://github.com/ezsystems/ezfind/pull/93\n","date":"Dec 7, 2012","permalink":"https://harmstyler.me/posts/2012/optimizing-solr-to-fit-your-needs-part-2/","section":"Posts","summary":"\u003cp\u003eI am finally taking the time to write about optimizing the Solr schema file. In my last post I showed that eZ Find already ships with a default search field called \u003ccode\u003eezf_df_text\u003c/code\u003e. The downside of searching one field is that field needs to be controlled, and the only way it can be controlled is by manually editing the Solr schema file. As you might expect, eZ Find does not control this file very well out of the box.\u003c/p\u003e","title":"Optimizing Solr To Fit Your Needs Part 2"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/composer/","section":"Tags","summary":"","title":"Composer"},{"content":"I have spent a good deal of my freetime this past month or two learning as much about about the php framework Symfony as possible. The moment that eZ Systems announced that they were adopting the Symfony framework I knew that I would be be spending some time with it. I have found that Symfony doesn\u0026rsquo;t have the lowest barrier to entry so it has been a process, to say the least. I have a blog that has helped matters a great deal and so I wanted to give it some props here. If you are like me, wanting to learn Symfony but lacking time and resources, you should really check this site out: Symblog: Creating a Blog in Symfony2\nThe blog starts with the basics and explores a good deal of Symfony components in detail. In the end you will be left with a barley usable blog, but that is alright, it leaves you with a project to fix. It is a tutorial for Symfony 2.0, and because Symfony is currently on 2.1.3 you will notice some part don\u0026rsquo;t work the way you expect (but I think that forces you to learn more anyway). The tutorial also doesn\u0026rsquo;t use Composer at all; so remember, when the blog tells you to install something, use Composer and skip over the way they tell you how to do it. I hope you enjoy the tutorial, I know I did.\n","date":"Nov 21, 2012","permalink":"https://harmstyler.me/posts/2012/learning-symfony/","section":"Posts","summary":"","title":"Learning Symfony"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/skype/","section":"Tags","summary":"","title":"Skype"},{"content":"I recently updated to Skype 6 and found something interesting out. My style package that worked in Skype 5 would not work in Skype 6. With some digging, we found out that in order to make your Skype 5 style work in Skype 6 you need to update the MessageViewVersion to version 7 (I have no idea why). So, with that, have fun updating your Skype to the latest version while still getting use out of your favorite Skype theme.\n","date":"Nov 1, 2012","permalink":"https://harmstyler.me/posts/2012/skype-version-6/","section":"Posts","summary":"","title":"Skype version 6"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/technology/","section":"Categories","summary":"","title":"Technology"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/technology/","section":"Tags","summary":"","title":"Technology"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/amazon/","section":"Tags","summary":"","title":"Amazon"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/aws/","section":"Tags","summary":"","title":"AWS"},{"content":"Just had to share this post since I helped a little bit with it. Very handy little tip about authorizing security groups across AWS accounts.\nAWS authorize security groups across acounts « ShellRunner.\nIt is a post by @jacksonmurtha so we can trust that it is good.\n","date":"Sep 28, 2012","permalink":"https://harmstyler.me/posts/2012/aws-authorize-security-groups-across-acounts-shellrunner/","section":"Posts","summary":"","title":"AWS authorize security groups across acounts « ShellRunner"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/cloud/","section":"Tags","summary":"","title":"Cloud"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/development/","section":"Tags","summary":"","title":"Development"},{"content":"I did a lot of playing around with solr to get this right. Unfortunately there is not a lot of documentation on searching within a certain field. With the SQL language you can search for multiple terms in a given field using the IN operator. In Solr you have the option of specifying a field using the qf operator, but what if you have more search terms searching other fields? The problem becomes far more complex. Given the following SQL statement how would we query Solr?\n1SELECT * 2FROM table_name 3WHERE column_name IN (value1,value2,...) 4AND other_column=\u0026#39;foo\u0026#39; There are couple of ways to do this, but I think there is one very good and much easier way. The first obvious way to query solr is to match the fields to columns manually.\n1/select?q=*:*\u0026amp;fq=column_name:value1 AND column_name:value2 AND other_colum:\u0026#39;foo\u0026#39; This is all fine and dandy but what if I want to be able to add a list of content to the query? Adding the column name every time can become a chore and make the query unnecessarily large. To do an IN query try this:\n1/select?q=*:*\u0026amp;fq=column_name:+(value1 value2) AND other_colum:\u0026#39;foo\u0026#39; Having used this one before, I can tell you it works for me using Solr 3.6 and that the placement of the + symbol is key.\nAnd what about a NOT IN query?\n1SELECT * 2FROM table_name 3WHERE column_name NOT IN (value1,value2,...) 4AND other_column=\u0026#39;foo\u0026#39; The \u0026lsquo;bad\u0026rsquo; way:\n1/select?q=*:*\u0026amp;fq=-column_namevalue1 AND -column_name:value2 AND other_colum:\u0026#39;foo\u0026#39; This one I found from working with eZ Publish and eZ Find, and I happen to like it a lot. It is a clever little Solr query that selects all from the column then filters out what we don\u0026rsquo;t want.\n1/select?q=*:*\u0026amp;fq=other_colum:\u0026#39;foo\u0026#39; AND column_name:[* TO *] -column_name:(value1 value2) column_name:[* TO *] is like saying select all from the column, but then we add -column_name:(value1 value2) to filter out what we don\u0026rsquo;t want.\nCheers and good luck!\n","date":"Sep 13, 2012","permalink":"https://harmstyler.me/posts/2012/solr-query-for-mimicking-sql-in-operator-functionality/","section":"Posts","summary":"","title":"Solr Query for Mimicking SQL IN Operator Functionality"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/sql/","section":"Tags","summary":"","title":"SQL"},{"content":"Let me start by saying that eZ Find is a fantastic implementation of Solr functionality. With eZ Find you get a fantastic search, out of the box. eZ Find gathers all the content in your system and organizes it for you into strings, text, and keywords (there are more but those the types I use most often). It can look at an eZ Publish keyword and know that it should thereby be a Solr keyword, again, all out of the box. There are however some serious limitations when it comes time to customize it to meet your system\u0026rsquo;s needs. Do not underestimate the power found in customizing Solr to meet your website\u0026rsquo;s needs. The default setup is great, it\u0026rsquo;s made to fit into any system with very little effort; but, just because it\u0026rsquo;s made to fit in any system does not mean that it should be used for every system.\nOne of the best things we could have ever done for a large Solr install was to customize a default search field. You might say, \u0026ldquo;But eZ Find already creates a default search field for you.\u0026rdquo; And you\u0026rsquo;d be right.\n1\u0026lt;!-- field for the QueryParser to use when an explicit fieldname is absent --\u0026gt; 2\u0026lt;defaultSearchField\u0026gt;ezf_df_text\u0026lt;/defaultSearchField\u0026gt; The thing that the default search field in eZ Find does not do, however, is get used. There is no way in eZ Find to tell it to search just one field (at least as far as I know). I did some testing in our environment, these numbers will change from system to system, but in our\u0026rsquo;s each search took almost 10 times as long to search all fields individually vs searching just 1 field. We gave eZ Find the option to search one given field, if defined, and it vastly improved our search experience.\neZ Find by default looks at all \u0026lsquo;searchable\u0026rsquo; fields and adds them to either the qf (if you are using the dismax search like you should be) or it searches every searchable field individually (attr_title_t:(*:*), attr_title_s:(*:*), etc, etc, for all searchable fields). All the \u0026lsquo;searchable\u0026rsquo; fields are by default added to the ezf_df_text field, so you should really do yourself a favor and search just one field, it is a noticable speed improvement, especially in large systems.\nIn Part 2 I will talk about how to better optimize the Solr schema.xml file using the ezf_df_text field.\n","date":"Aug 25, 2012","permalink":"https://harmstyler.me/posts/2012/optimizing-solr-to-fit-your-needs-part-1/","section":"Posts","summary":"","title":"Optimizing Solr to fit your needs Part 1"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/git/","section":"Categories","summary":"","title":"Git"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/gitignore/","section":"Tags","summary":"","title":"Gitignore"},{"content":"{.pull-right}\nI have been a devout git user for as long as I have been programming. I was lucky enough to bypass subversion entirely, at least that is what my friends who\u0026rsquo;ve used svn have told me.\nThe cool thing about git is that it tracks changes, not files or directories. When I create a new empty directory that directory is not tracked by git because it has no file changes inside, hence, the directory will never make it into git until I add a file to that directory. For this example I am going to use the logs directory of a virtual site. In my logs directory I have two files, an error_log and an access_log. Apache error logs and access logs should never be tracked by version control because they are logs that are specific to the given system the site is installed on. However, we should try to track the logs directory in version control. As hinted at before, we can do this in one of two ways.\nFirst, the .gitignore file. We can add as my .gitignore files as we need. You can place these files in any directory you want tracked that wouldn\u0026rsquo;t be already (technically you can put them anywhere you want and have as many as you want, but don\u0026rsquo;t go excessive). .gitignore files are technically the preferred way to go about tracking empty directories because these files are actually read by git. The .gitignore file structure is pretty simple, for defining how to ignore the contents of the logs file you just need to tell git the relative path (relative to the path of the .gitignore file). .gitignore file:\n1# log files 2logs/access_log 3logs/error_log How to track the empty directory with .gitignore\n{.pull-right}\nSo, as you can see this is pretty simple, but I think that multiple .gitignore files can become confusing.\nSecond, the .gitkeep file. This is a git hack that was first adopted by the Rails community. .gitkeep files are not an official file recognized by git. That said, because they start with a '.' they will be a hidden file in any system as well as have it\u0026rsquo;s changes tracked by git. Technically our file name could be .foobar and it would still work. The reason I like the .gitkeep files is because it\u0026rsquo;s purpose is directly implied by the name of the file (keep the directory in git even if it\u0026rsquo;s directory is empty). Another reason I like the use of .gitkeep files over .gitignore is I like to keep all ignored files in one spot, it can be hard to track what is ignored if there are multiple .gitignore files at work in a system. The .gitkeep file at work:\n{.pull-right}\nPretty simple right? So, I have my preference, what is yours?\n","date":"Aug 18, 2012","permalink":"https://harmstyler.me/posts/2012/gitignore-vs-gitkeep/","section":"Posts","summary":"","title":"Multiple .gitignore files versus .gitkeep files"},{"content":"A while back I fell in love with Sublime Text 2 and its\u0026rsquo; many advantages over TextMate. What it lacked however was any real support for the eZ Publish templating language (no different than TextMate). What I wanted was a simple editor that offered variable and method highlighting equivalent to a nice IDE like PHPStorm. I was originally using the Smarty package for TextMate (you can use any TextMate package in Sublime), but that does not offer a lot of method highlighting support for eZ. So what I decided to do was use the Smarty package as a template for adding more language support for eZ Publish. What I have is far from complete; I usually only add to it when I use a method that is not already supported. You can find it out on GitHub. It is my hope that there are some eZ Publish devs out there that really enjoy Sublime as I do and want to help me grow the package. Remember, it is far from complete, but nonetheless, I really hope that you enjoy the language highlighting, and if you do feel free to contribute.\n","date":"Jun 30, 2012","permalink":"https://harmstyler.me/posts/2012/ez-publish-with-sublime/","section":"Posts","summary":"","title":"eZ Publish with Sublime"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/sublime-text/","section":"Categories","summary":"","title":"Sublime Text"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/syntax-highlighting/","section":"Tags","summary":"","title":"Syntax Highlighting"},{"content":"I am way overdue on my promise to show you how to make shards work. As I said before, shards in eZ Find do not work out of the box. In the code the $shardQuery is built properly but the variable is never actually used after it is created. What I did to make this work is a simple if/else block inside the $queryHandler switch. I am not submitting this as a pull request to eZ Find at the moment just because I do not like the way I made this work, I feel like there is a better way to do it. Nevertheless here is my code to make Shards work, I hope you enjoy and moreover I hope you suggest a better way to make the code work ;)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 \u0026lt;?php \\\\ezfezpsolrquerybuilder.php switch ( $queryHandler ) { case \u0026#39;standard\u0026#39;: // @todo: this is more complicated // build the query against all \u0026#34;text\u0026#34; like fields // should take into account all the filter fields and class filters to shorten the query // need to build: Solr q if ( array_key_exists( \u0026#39;fields\u0026#39;, $boostFunctions ) ) { if (false != $shardQuery) { $handlerParameters = array ( \u0026#39;q\u0026#39; =\u0026gt; $this-\u0026gt;buildMultiFieldQuery( $searchText, array_merge( $queryFields, $extraFieldsToSearch ), $boostFunctions[\u0026#39;fields\u0026#39;] ), \u0026#39;qt\u0026#39; =\u0026gt; \u0026#39;standard\u0026#39;, \u0026#39;shards\u0026#39; =\u0026gt; $shardQuery); } else { $handlerParameters = array ( \u0026#39;q\u0026#39; =\u0026gt; $this-\u0026gt;buildMultiFieldQuery( $searchText, array_merge( $queryFields, $extraFieldsToSearch ), $boostFunctions[\u0026#39;fields\u0026#39;] ), \u0026#39;qt\u0026#39; =\u0026gt; \u0026#39;standard\u0026#39;, ); } } else { if (false != $shardQuery) { $handlerParameters = array ( \u0026#39;q\u0026#39; =\u0026gt; $this-\u0026gt;buildMultiFieldQuery( $searchText, array_merge( $queryFields, $extraFieldsToSearch ) ), \u0026#39;qt\u0026#39; =\u0026gt; \u0026#39;standard\u0026#39;, \u0026#39;shards\u0026#39; =\u0026gt; $shardQuery); } else{ $handlerParameters = array ( \u0026#39;q\u0026#39; =\u0026gt; $this-\u0026gt;buildMultiFieldQuery( $searchText, array_merge( $queryFields, $extraFieldsToSearch ) ), \u0026#39;qt\u0026#39; =\u0026gt; \u0026#39;standard\u0026#39; ); } } break; case \u0026#39;simplestandard\u0026#39;: // not to do much, searching is against the default aggregated field // only highlightfields $highLightFields = array ( \u0026#39;ezf_df_text\u0026#39; ); $handlerParameters = array ( \u0026#39;q\u0026#39; =\u0026gt; $searchText, \u0026#39;qt\u0026#39; =\u0026gt; \u0026#39;standard\u0026#39;, \u0026#39;hl.usePhraseHighlighter\u0026#39; =\u0026gt; \u0026#39;true\u0026#39;, \u0026#39;hl.highlightMultiTerm\u0026#39; =\u0026gt; \u0026#39;true\u0026#39; ); break; case \u0026#39;ezpublish\u0026#39;: // the dismax based handler, just keywordss input, most useful for ordinary queries by users // need to build: Solr q, qf, dismax specific parameters default: // ezpublish of course, this to not break BC and is the most \u0026#34;general\u0026#34; // if another value is specified, it is supposed to be a dismax like handler // with possible other tuning variables then the stock provided \u0026#39;ezpublish\u0026#39; in solrconfi.xml // remark it should be lowercase in solrconfig.xml! if (false != $shardQuery) { $handlerParameters = array ( \u0026#39;q\u0026#39; =\u0026gt; $searchText, \u0026#39;qf\u0026#39; =\u0026gt; implode( \u0026#39; \u0026#39;, array_merge( $queryFields, $extraFieldsToSearch ) ), \u0026#39;qt\u0026#39; =\u0026gt; $queryHandler, \u0026#39;shards\u0026#39; =\u0026gt; $shardQuery ); } else { $handlerParameters = array ( \u0026#39;q\u0026#39; =\u0026gt; $searchText, \u0026#39;qf\u0026#39; =\u0026gt; implode( \u0026#39; \u0026#39;, array_merge( $queryFields, $extraFieldsToSearch ) ), \u0026#39;qt\u0026#39; =\u0026gt; $queryHandler ); } } ","date":"Jun 29, 2012","permalink":"https://harmstyler.me/posts/2012/fixing-shards-in-ez-find/","section":"Posts","summary":"\u003cp\u003eI am way overdue on my promise to show you how to make shards work. As I said before, shards in eZ Find do not work out of the box. In the code the \u003ccode\u003e$shardQuery\u003c/code\u003e is built properly but the variable is never actually used after it is created. What I did to make this work is a simple if/else block inside the \u003ccode\u003e$queryHandler\u003c/code\u003e switch. I am not submitting this as a pull request to eZ Find at the moment just because I do not like the way I made this work, I feel like there is a better way to do it. Nevertheless here is my code to make Shards work, I hope you enjoy and moreover I hope you suggest a better way to make the code work ;)\u003c/p\u003e","title":"Fixing Shards in eZ Find"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/shards/","section":"Tags","summary":"","title":"Shards"},{"content":"Turns out that when using shards to search multiple cores (shards in general actually) binary is not returned properly. I found an update to the trunk (4.x) version of Solr and was successfully able to update my version of Solr 3.6. I am submitting a pull request of the code update to Solr as well a new war file to eZ Find, but in the meantime you can download the solr.war file on GitHub (it is in my eZ Find branch). Enjoy!\n","date":"May 14, 2012","permalink":"https://harmstyler.me/posts/2012/solr-3-6-update/","section":"Posts","summary":"","title":"Solr 3.6 Update"},{"content":" 1 2 3 4 5 6 7 8 9 \u0026lt;?php if ( isset( $distributedSearch[\u0026#39;shards\u0026#39;] ) ) { foreach ( $distributedSearch[\u0026#39;shards\u0026#39;] as $shard ) { $shardUrls[] = $iniShards[$shard]; } $shardQuery = implode( \u0026#39;,\u0026#39;, $shardUrls ); } Prerequisites:\neZ Publish with eZ Find installed The following post is based on the option in the eZ Find solr.ini referring to Shards.\n1[SolrBase] 2#Shards mapping, can be to multicores in one servlet or even a crosss servers 3#typical use is multilingual setups, but also for external index support 4#the keys are used as shorthands in template functions 5#Shards[] 6#Shards[eng-GB]=http://localhost:8983/solr/eng-GB 7#Shards[fre-FR]=http://localhost:8983/solr/fre-FR 8#Shards[myforeignindex]=http://myotherhost:8983/solr What this means for eZ Find developers: we can do a distributed search in our standard eZ Find fetch (with a little work).\nFirst things first, we need to tell each siteaccess what its Search Server URI is. Within the settings/siteaccess/\u0026lt;siteaccess-name\u0026gt;/ directory we need to create a new solr.ini.append.php file (duplicate the file in the admin version). Within each siteaccess we need to input our Solr core name; for harmssite it would be SearchServerURI=http://localhost:8983/solr/harmssite and for the counterhelix siteaccess it would be SearchServerURI=http://localhost:8983/solr/counterhelix.\nNext, make sure that you have your cores installed into solr. In order to do this just copy the default eng-GB from the solr.multicore/ directory and then rename it for each core you wish to have, in my case I have a harmssite and a counterhelix core. There is also a solr.xml that we need to edit. Here we declare each core that we created.\nMine:\n1\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; 2\u0026lt;solr persistent=\u0026#34;true\u0026#34; sharedLib=\u0026#34;lib\u0026#34;\u0026gt; 3 \u0026lt;cores adminPath=\u0026#34;/admin/cores\u0026#34;\u0026gt; 4 \u0026lt;core name=\u0026#34;harmssite\u0026#34; instanceDir=\u0026#34;harmssite\u0026#34; /\u0026gt; 5 \u0026lt;core name=\u0026#34;counterhelix\u0026#34; instanceDir=\u0026#34;counterhelix\u0026#34; /\u0026gt; 6 \u0026lt;/cores\u0026gt; 7\u0026lt;/solr\u0026gt; Finally, we need to start Solr, while giving it a new home parameter.\n1java -Dsolr.solr.home=solr.multicore -jar start.jar There is decent documentation on this up to this point located here.\nNext, we need to add a list of our shards into our override solr.ini.append.php file. Use the demo from above, just put in the url each core. Something to note here. I believe the documentation is wrong here. We should not include the http:// in this list, Solr will add this automatically for us when it runs the search. Thus, mine looks like this:\n1[SolrBase] 2Shards[] 3Shards[harmssite]=localhost:8983/solr/harmssite 4Shards[counterhelix]=localhost:8983/solr/counterhelix I am assuming here that you know how to index your content, so update your search index to follow these settings. The next step is to set up a distributed search in our eZ Find fetch.\nexample:\n1{set $search=fetch( \u0026#39;ezfind\u0026#39;, \u0026#39;search\u0026#39;, 2 hash( \u0026#39;query\u0026#39;, $query, 3 \u0026#39;sort_by\u0026#39;, $sort_by, 4 \u0026#39;facet\u0026#39;, $defaultSearchFacets, 5 \u0026#39;filter\u0026#39;, $filterParameters, 6 \u0026#39;publish_date\u0026#39;, $search_date, 7 \u0026#39;offset\u0026#39;, $view_parameters.offset, 8 \u0026#39;limit\u0026#39;, $page_limit, 9 \u0026#39;as_objects\u0026#39;, false(), 10 \u0026#39;distributed_search\u0026#39;, hash( 11 \u0026#39;shards\u0026#39;,array(\u0026#39;harmssite\u0026#39;, \u0026#39;counterhelix\u0026#39;) 12 ) 13 ))} Note that we have to send the distributed_search parameter as a hashed array. In this case we are telling eZ Find to search both shards. We can have eZ Find search whatever core we want it to at this point, just harmssite, just counterhelix, both, or even new cores that we might add later. The advantages are obvious, separated indexes allowing for much larger sets of data is a huge upgrade. The downside is that searching multiple cores at once is a little slower, so really you should not use this option unless you are in need of scaling Solr out more; this would be a touch overkill for my site.\nIf you have followed me up to this point and have tried running this, you may have noticed that it does not work out of the box. The ezfezpsolrquerybuilder.php is still a work in process and has not been set up to handle shards just yet. Out of the box, should you send the distributed search shards array to eZ Find, eZ Find will take it, generate the shard urls and then do nothing with them. The task I leave you is to try and fix this problem for eZ Find. I have a fix for this, but would love to see what other people come up with (I will post my code here next week). The file in question. Remember, all we need to do is send the shards parameter to the search plugin should one be sent to eZ Find; the search plugin is already setup to handle shards.\n","date":"May 8, 2012","permalink":"https://harmstyler.me/posts/2012/ezfind-shards/","section":"Posts","summary":"\u003c!--\n    ![Distributed Search](/img/distributed_search.png)\n--\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e9\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-php\" data-lang=\"php\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e\u0026lt;?\u003c/span\u003e\u003cspan class=\"nx\"\u003ephp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e \u003cspan class=\"nx\"\u003eisset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e \u003cspan class=\"nv\"\u003e$distributedSearch\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;shards\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e  \u003cspan class=\"p\"\u003e(\u003c/span\u003e \u003cspan class=\"nv\"\u003e$distributedSearch\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;shards\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nv\"\u003e$shard\u003c/span\u003e \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nv\"\u003e$shardUrls\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nv\"\u003e$iniShards\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nv\"\u003e$shard\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003e$shardQuery\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eimplode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;,\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$shardUrls\u003c/span\u003e \u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cp\u003ePrerequisites:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eeZ Publish with eZ Find installed\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe following post is based on the option in the eZ Find \u003ccode\u003esolr.ini\u003c/code\u003e referring to Shards.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-ini\" data-lang=\"ini\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e1\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e[SolrBase]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e2\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#Shards mapping, can be to multicores in one servlet or even a crosss servers\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e3\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#typical use is multilingual setups, but also for external index support\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e4\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#the keys are used as shorthands in template functions\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e5\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#Shards[]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e6\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#Shards[eng-GB]=http://localhost:8983/solr/eng-GB\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e7\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#Shards[fre-FR]=http://localhost:8983/solr/fre-FR\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"ln\"\u003e8\u003c/span\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#Shards[myforeignindex]=http://myotherhost:8983/solr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhat this means for eZ Find developers: we can do a distributed search in our standard eZ Find fetch (with a little work).\u003c/p\u003e","title":"Searching Multiple Solr Cores using Shards and eZ Find"},{"content":" 1# Enable Solr to store all the basic information of a content object 2# Allows Solr to be used as a cross-site DB 3[IndexOptions] 4EnableSolrAttributeStorage=true 5OptimizeOnCommit=disabled When using eZ Find to search a single local site, it does a really good job at building out the result object to be used. What becomes a problem, however, is when a cross-site search is necessary. When the search fetch includes 'as_objects', true() eZ Find tries to build out an object by fetching from the database after the Solr search is completed and the necessary content is returned.\nAnother option, however, is to return all the information from Solr, no more object building from the DB. To do this all you have to do is enable EnableSolrAttributeStorage in the ezfind.ini. That way, if you are searching on a site that does not have access to the local database (cross-site search for instance) you can return all the content you need from Solr, because it is all stored.\nSo remember, set: 'as_objects', false() in the fetch.\nand in the ezfind.ini\n1[IndexOptions] 2EnableSolrAttributeStorage=true Then just remember to re-index your content to get all the new attributes in there.\n","date":"Apr 26, 2012","permalink":"https://harmstyler.me/posts/2012/ez-find-solr-attribute-storage/","section":"Posts","summary":"","title":"eZ Find: Enable Attribute Storage"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/ez-find/","section":"Categories","summary":"","title":"EZ Find"},{"content":"One of the greatest things about eZ Publish as a CMS is its ability to host many websites (siteaccesses) in one eZ Publish install. Recently I had the privilege of working on an eZ install that hosted 4 websites, giving the illusion of a single site experience across multiple urls. We used eZ Find extensively, specifically its multi-core functionality, to get information across the sites with ease. That way, all the sites can keep and maintain their own databases, and what is shared is shared through Solr.\nSo, to start. Let\u0026rsquo;s assume that if you are reading this, you already read through my previous eZ Find tutorial (Installing eZ Find). Since we already have our sever up and running there won\u0026rsquo;t be a lot of work to do there. But, we need to move our config files to the Solr multicore directory. Last time we used the Solr directory, this time we need to use the directory solr.multicore. Inside the multicore directory there are a couple of demo cores for languages. What I did is duplicated the english directory a couple times and renamed them for each site.\nI think this is the time for a nice FYI. We can go about this next part in a couple of different ways. The way we choose depends on the desired consequence. So, ask yourself this question, \u0026lsquo;Do I want the siteaccesses to share content?\u0026rsquo; If you answered yes, you only need 1 core for you sites to share off of, if you answered no, then create a core for each site that you wish to keep separate from the others. Another note, this effect can also be achieved by adjusting an ini setting, but if you know you want your stuff separate, then create multiple cores, it is safer that way. The way I set mine up is I have 2 cores. 1 core has 3 siteaccess sharing content, the other has just 1 site on it (that way its index content will not get mixed by accident).\nNow that we are using Solr in a multi-core environment, starting Solr is a bit different.\n1java -Dsolr.solr.home=solr.multicore -jar start.jar Notice, we just sent the start.jar a new place for solr to call its\u0026rsquo; home directory solr.multicore.\nNow that we have Solr re-configured for multi-core work we have to set up our solr.ini files. Inside the siteaccess specific override folders we need to drop in and edit a solr.ini.append.php file. Inside the ini file we need to point eZ Find at the right Solr cores.\n1[SolrBase] 2SearchServerURI=http://localhost:8983/solr/\u0026lt;core-name\u0026gt; With that one ini setting set we can fully update our index with the same command as before (the update script inside the eZ Fnd extension). So, after we have that set there is 1 last setting that we need to set, it is inside the ezfind.ini.\n1[SiteSettings] 2#IndexPubliclyAvailable=disabled 3#SearchOtherInstallations=disabled Notice that I have the settings commented it out here. I set it in the siteaccess specific override files, and if it is not commented out it overrides my content. Remember, this setting only applies to content that shares a core, if the content is not sharing a core it will not be able to search the other installations.\nWith this simple set of settings, we gain a lot of power over search. Combinations work here as well, if you don\u0026rsquo;t want to share the index, but want to search other available indexes, go for it.\nBest of luck to you, if I missed something don\u0026rsquo;t be afraid to let me know. Also, if you have questions be sure to ask.\n","date":"Apr 20, 2012","permalink":"https://harmstyler.me/posts/2012/ez-find-multi-core/","section":"Posts","summary":"\u003cp\u003eOne of the greatest things about eZ Publish as a CMS is its ability to host many websites (siteaccesses) in one eZ Publish install. Recently I had the privilege of working on an eZ install that hosted 4 websites, giving the illusion of a single site experience across multiple urls. We used eZ Find extensively, specifically its multi-core functionality, to get information across the sites with ease. That way, all the sites can keep and maintain their own databases, and what is shared is shared through Solr.\u003c/p\u003e\n\u003cimg src=\"/img/solr-dir.png\"\n    alt=\"Solr Directory\"\n    title=\"solr-dir\"\n    class=\"pull-right\"\n    loading=\"lazy\"\n    decoding=\"async\"\n  \u003e\u003cp\u003eSo, to start. Let\u0026rsquo;s assume that if you are reading this, you already read through my previous eZ Find tutorial (\u003ca href=\"/2012/03/installing-ez-find/\" title=\"Installing eZ Find\"\u003eInstalling eZ Find\u003c/a\u003e). Since we already have our sever up and running there won\u0026rsquo;t be a lot of work to do there. But, we need to move our config files to the Solr multicore directory. Last time we used the Solr directory, this time we need to use the directory \u003ccode\u003esolr.multicore\u003c/code\u003e. Inside the multicore directory there are a couple of demo cores for languages. What I did is duplicated the english directory a couple times and renamed them for each site.\u003c/p\u003e","title":"eZ Find Multi-Core"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/ez-publish/","section":"Tags","summary":"","title":"EZ Publish"},{"content":"As an eZ Publish developer I know that whenever creating an eZ Publish site the cache can be my best friend in production and worst enemy in development. eZpedia has a great article all about clearing the cache.\nClear Cache in eZ Publish / Solution / Root - eZpedia\nMy favorite is the manually clearing of the cache. For some reason the clear-cache script sometimes misses some files; so the only way you are going to see your changes is by dumping those files manually.\n1cd /path/to/ezpublish; 2rm -vrf var/cache; 3rm -vrf var/ezwebin_site/cache rm -vrf is very scary to run (remove all?!?), but so long as you are doing this to the specified directory you should be safe (but be careful).\nEDIT: Its worth noting that you should never clear the cache this way if running distributed eZ Publish installation.\n","date":"Apr 12, 2012","permalink":"https://harmstyler.me/posts/2012/clear-cache-in-ez-publish-ezpedia/","section":"Posts","summary":"","title":"Clear Cache in eZ Publish - eZpedia"},{"content":"Very interesting, Bing is on the rise but it appears to be at the expense of Yahoo! not Google.\nDespite Bings Rise, Google Still Dominates Search [INFOGRAPHIC].\n","date":"Mar 26, 2012","permalink":"https://harmstyler.me/posts/2012/despite-bings-rise-google-still-dominates-search-infographic/","section":"Posts","summary":"","title":"Despite Bings Rise, Google Still Dominates Search [INFOGRAPHIC]"},{"content":"Alright, so lets begin installing eZ Find by going out and downloading it from the Github =\u0026gt; https://github.com/ezsystems/ezfind. eZ Find is an eZ Publish extension so: unzip the download, rename the fold \u0026ldquo;ezfind,\u0026rdquo; and then move the ezfind folder to the extensions directory of your eZ Publish install.\nSo, as with any extension, we have to activate it. Go into your override site.ini.append.php (settings/override/site.ini.append.php) and add ezfind to your active extension list (ActiveExtensions[]=ezfind). I added mine just after my main site extension. Easy enough so far right?\nAs you had probably already guessed, eZ Find will not without first setting up Solr. Luckily eZ Find comes with the recommended version of Solr for eZ Find. Within the eZ Find extension you should see a java directory (remember Solr is written in Java?). So what we need to do is copy the java directory and move it to the htdocs level of your eZ Publish install and rename the folder \u0026ldquo;Solr\u0026rdquo; (see the image on the right). There is a lot in the solr directory that we will not need to mess with right now, specifically I will not cover the multicore options right now, just know that eZ Find can run multiple sites/cores on one Solr install. Also note that I just installed Solr locally, but it can also be installed on remote servers.\nNow that we have Solr installed we need to start it. Just open up terminal, navigate to your Solr install and from that directory run the following command.\n1java -Dezfind -jar start.jar Eventually you want to have a job run Solr in the background, but for now running it in your console is more than satisfactory, plus it helps during development to see errors pop-up from time to time.\nNext step, we need to create two files within our main site settings folder:\nezfind.ini.append.php solr.ini.append.php The ezfind ini can be left blank for now. Just know that you can modify a lot of eZ Find settings from within that ini. The solr ini however must edited now in order for search to work properly. Add the following lines to your Solr ini:\n1[SolrBase] 2# Base URI of the Solr server 3SearchServerURI=http://localhost:8983/solr The localhost search server is actually already in the default solr ini file but I like to make sure I am controlling this by putting the line in my own ini file. Also, should your solr server be anything other than local, just change it\u0026rsquo;s location here.\nSo, we have eZ Find installed and activated, Solr installed and started, now all we need is an index to search. eZ Find comes with a nice script for updating the search index already so we just need to run it (run the command from the root install level just like you would the clear-cache script). Run the following script:\n1php extension/ezfind/bin/php/updatesearchindexsolr.php -s \u0026lt;admin siteaccess\u0026gt; --php-exec=php --conc=2 The script is pretty advanced. You need to specify the admin version of your siteaccess, mine would be harmssite_admin. You need to specify the number of cores to use, my local computer is a quad-core so I set typically conc=4. Setting the php-exec is optional. There is another optional parameter to send, clean or clean-all. Clean will clean the index of any prior content before updating it (i recommend using clean when developing). Clean-all is only used when you have multiple Solr cores going and you want to clean them all at once, I don\u0026rsquo;t use clean-all often.\nAssuming everything went well you should have seen something similar to my screencap when updating the index.\nDon\u0026rsquo;t worry if it didn\u0026rsquo;t work at first, just keep with it. Also, leave me a comment and I will respond.\nHope you enjoyed the post, I will try and do a post about a multi-core install sometime soon.\n","date":"Mar 22, 2012","permalink":"https://harmstyler.me/posts/2012/installing-ez-find/","section":"Posts","summary":"\u003cp\u003eAlright, so lets begin installing eZ Find by going out and downloading it from the Github =\u0026gt; \u003ca href=\"http://bit.ly/GEVOte\" title=\"eZ Find on GitHub\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://github.com/ezsystems/ezfind\u003c/a\u003e.\neZ Find is an eZ Publish extension so: unzip the download, rename the fold \u0026ldquo;ezfind,\u0026rdquo; and then move the \u003cem\u003eezfind\u003c/em\u003e folder to the \u003cem\u003eextensions\u003c/em\u003e directory of your eZ Publish install.\u003c/p\u003e\n\u003cp\u003eSo, as with any extension, we have to activate it. Go into your override \u003ccode\u003esite.ini.append.php\u003c/code\u003e (settings/override/site.ini.append.php) and add ezfind to your active extension list (\u003ccode\u003eActiveExtensions[]=ezfind\u003c/code\u003e). I added mine just after my main site extension. Easy enough so far right?\u003c/p\u003e\n\u003ca href=\"/img/solr-structure.png\"\u003e\u003cimg src=\"/img/small/solr-structure-small.png\"\n    alt=\"Solr installed locally\"\n    title=\"solr-structure\"\n    class=\"pull-left\"\n    loading=\"lazy\"\n    decoding=\"async\"\n  \u003e\u003c/a\u003e\u003cp\u003eAs you had probably already guessed, eZ Find will not without first setting up Solr. Luckily eZ Find comes with the recommended version of Solr for eZ Find. Within the eZ Find extension you should see a \u003cem\u003ejava\u003c/em\u003e directory (remember Solr is written in Java?). So what we need to do is copy the java directory and move it to the htdocs level of your eZ Publish install and rename the folder \u0026ldquo;Solr\u0026rdquo; (see the image on the right). There is a lot in the solr directory that we will not need to mess with right now, specifically I will not cover the multicore options right now, just know that eZ Find can run multiple sites/cores on one Solr install. Also note that I just installed Solr locally, but it can also be installed on remote servers.\u003c/p\u003e","title":"Installing eZ Find"},{"content":"I have been thinking a long time about different topics I could use this blog for. I have settled for the moment on an eZ Publish extension that I have used extensively for a while, eZ Find. eZ Find is an ultra-powerful Solr powered search engine made for eZ Publish, authored by Paul Borgermans.\nFor starters, an introduction to eZ Find \u0026amp; Solr.\nSolr is an ultra-powerful search engine built in Java on top of the Apache Lucene project. It is highly scalable, just like eZ Publish, and can be adapted to work in almost any environment. eZ Find for instance uses a php interface with Solr to perform extremely fast and reliable searches.\neZ Find is a standalone extension that can be installed eZ Publish with great ease (my next post). eZ Find can be found in a couple of different places. First, on the eZ Publish Share site. Unfortunatly the \u0026ldquo;free\u0026rdquo; stable version (2.3) is a little behind, it still uses Solr 1.4. I recommend downloading the extension from github. Being open source the codebase gets updated often, even better it uses a more up to date version of Solr (3.1).\nIn conclusion, eZ Find is highly powerful and scalable. If you use eZ Publish I strongly suggest you look into eZ Find as an alternative search engine. Look forward to my next post about how to install eZ Find into your website.\n","date":"Mar 19, 2012","permalink":"https://harmstyler.me/posts/2012/ez-find-basics/","section":"Posts","summary":"","title":"eZ Find Basics"},{"content":"I have been meaning to make this post for a couple of months now. I came across this topic a while back when I was working on creating some custom facets for a website running eZ Find, powered by Solr. The Problem: I needed to bucket all the authors together by last name (eg. A-C, D-F and so on). I also wanted to do this all within Solr, since the book I was using for reference said it was possible and I did not want to use Javascript (I wanted to take advantage of Solr\u0026rsquo;s speed; let Solr do the work). Now, the book I was using, Apache Solr Enterprise Search Server, was very good at explaining how to do everything when it came to creating my new custom fieldType. Where it was lacking was in how to declare my new synonyms for the SynonymFilterFactory. So for starters lets look at the fieldType:\n1\u0026lt;fieldType name=\u0026#34;bucketFirstLetterGroup\u0026#34; class=\u0026#34;solr.TextField\u0026#34; sortMissingLast=\u0026#34;true\u0026#34; omitNorms=\u0026#34;true\u0026#34;\u0026gt; 2 \u0026lt;analyzer type=\u0026#34;index\u0026#34;\u0026gt; 3 \u0026lt;tokenizer class=\u0026#34;solr.PatternTokenizerFactory\u0026#34; pattern=\u0026#34;^([a-zA-Z]).*\u0026#34; group=\u0026#34;1\u0026#34; /\u0026gt; 4 \u0026lt;filter class=\u0026#34;solr.SynonymFilterFactory\u0026#34; synonyms=\u0026#34;mb_letterBuckets.txt\u0026#34; ignoreCase=\u0026#34;true\u0026#34; expand=\u0026#34;false\u0026#34;/\u0026gt; 5 \u0026lt;/analyzer\u0026gt; 6 \u0026lt;analyzer type=\u0026#34;query\u0026#34;\u0026gt; 7 \u0026lt;tokenizer class=\u0026#34;solr.KeywordTokenizerFactory\u0026#34; /\u0026gt; 8 \u0026lt;/analyzer\u0026gt; 9\u0026lt;/fieldType\u0026gt; It is actually pretty straight forward. It uses a PatternTokenizerFactory to ignore case but grab only letters and then the SynonymFilterFactory looks at a text file to see how to group all the letters. That is where the book got fuzzy. It told me to look at the default synonyms.txt file for reference and it sent me on my merry way. I scoured the internet for the answer to my dilemma and finally found my answer though trial and error. The default synonyms.txt file looks like this:\n1#some test synonym mappings unlikely to appear in real input text 2aaafoo =\u0026gt; aaabar 3bbbfoo =\u0026gt; bbbfoo bbbbar 4cccfoo =\u0026gt; cccbar cccbaz 5fooaaa,baraaa,bazaaa 6 7# Some synonym groups specific to this example 8GB,gib,gigabyte,gigabytes 9MB,mib,megabyte,megabytes 10Television, Televisions, TV, TVs 11#notice we use \u0026#34;gib\u0026#34; instead of \u0026#34;GiB\u0026#34; so any WordDelimiterFilter coming 12#after us won\u0026#39;t split it into two words. 13 14# Synonym mappings can be used for spelling correction too 15pixima =\u0026gt; pixma From this file I concluded that my buckets should be grouped as a-c=\u0026gt;a b c and so on. But that is not the case (it will actually appear to bucket the letters but also leave all the letters as their own bucket at the same time (A-C 150 results A 50 results and so on). It actually works opposite of the example. What is on the left is the solr fieldType and the right is the bucket/synonym. To work it must be a,b,c=\u0026gt;a-c.\n1a,b,c=\u0026gt;A-C 2d,e,f=\u0026gt;D-F 3g,h,i=\u0026gt;G-I 4j,k,l=\u0026gt;J-L 5m,n,o=\u0026gt;M-O 6p,q,r=\u0026gt;P-R 7s,t,u=\u0026gt;S-U 8v,w,x,y,z=\u0026gt;V-Z So yeah, I have been meaning to post this for a while as I could find no meaningful help on the matter elsewhere on the internet. Also, remember to reindex!\n","date":"Feb 25, 2012","permalink":"https://harmstyler.me/posts/2012/solr-buckets/","section":"Posts","summary":"","title":"Solr Buckets"},{"content":"I ran across a problem recently, I needed to get the contents of a page so that I could mimic a widget’s functionality without having access to the database that said page’s widget used (confused yet). Basically, one site had a widget that displayed an upcoming event (information with came from a database), I needed to copy that widget to another website. Moreover, I did not want to use JavaScript to accomplish my goal because I need the new site to be cached pretty heavily on the server. My first thought was to use cURL, but when I ran curl_exec my function would output the entire page contents onto my new site, something that I did not want, I only wanted a small portion of the website. My next option was to use file_get_contents(“my_url”), as was pointed out to me, this will return the full page as a string. With this I could have accomplished my goal, but not with as much ease as I would hope. I ran into the DOMDOcument() php object a while back and was curious about what I could do with it so I decided to do some research on that. This would eventually lead me to my answer. It turns out that the script is very simple and I can see myself using it quite often so I decided to share it. Here it is:\n1 2 3 4 5 6 7 8 9 10 11 12 13 \u0026lt;?php public function scrapePage($url,$id) { $d = new DOMDOcument(); libxml_use_internal_errors(true); $d-\u0026gt;loadHTMLFile($url); $widget = $d-\u0026gt;getElementById($id); if ($widget !== null) { return simplexml_import_dom($widget)-\u0026gt;asXML(); } } ","date":"Jan 24, 2012","permalink":"https://harmstyler.me/posts/2012/scraping-a-page-with-php/","section":"Posts","summary":"","title":"Scraping a page with php"},{"content":"","date":null,"permalink":"https://harmstyler.me/categories/css/","section":"Categories","summary":"","title":"CSS"},{"content":"I am well overdue for a new HarmsSite post. So today I am going to talk about one of my favorite new tools. But first, an intro. Building out gradients is easy enough, get your start color and your end color and then plug them into all the various browser specific gradient css elements (webkit, moz, etc\u0026hellip;) and you get a gradient. Like I said, simple enough but time consuming because each browser gets its own line.\nGradient App makes life much simpler. As a Gradient App user all I have to do use the color picker, or manually enter my colors, then just choose my gradient direction and any other options I would be interested in! Gradient App then gives me the CSS code to use, again, making my life much easier.\n1background-color: #69aece; 2background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(105, 174, 206)), to(rgb(201, 97, 103))); 3background-image: -webkit-linear-gradient(top, rgb(105, 174, 206), rgb(201, 97, 103)); 4background-image: -moz-linear-gradient(top, rgb(105, 174, 206), rgb(201, 97, 103)); 5background-image: -o-linear-gradient(top, rgb(105, 174, 206), rgb(201, 97, 103)); 6background-image: -ms-linear-gradient(top, rgb(105, 174, 206), rgb(201, 97, 103)); 7background-image: linear-gradient(top, rgb(105, 174, 206), rgb(201, 97, 103)); 8filter: progid:DXImageTransform.Microsoft.gradient(startColorStr=\u0026#39;#69aece\u0026#39;, EndColorStr=\u0026#39;#c96167\u0026#39;); Obviously, looking at the above code, simple gradients are fairly straight forward. Remember to cover all your browsers (the filter is for Internet Explorer versions older than IE10 as they do not recognize gradients). Also obvious, making your own gradients can be a bit tedious, which is why I used Gradient App to make the code you see above.\nGet Gradient App here: http://www.gradientapp.com/\n","date":"Oct 20, 2011","permalink":"https://harmstyler.me/posts/2011/gradient-app/","section":"Posts","summary":"","title":"Gradient App"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/gradients/","section":"Tags","summary":"","title":"Gradients"},{"content":"Finally, Path has come to Android. Path is one of those little known apps that I think a person would love, if they knew about it. Path is made to be built off of Facebook, but far more personal than that. Their slogan then makes sense, The Personal Network (vs the Social Network). At Path each person is only able to view a limited number of friends, making the social web experience more personal. They recognize the fact that each person in real life does not carry on relationships with 300 people, more likely 30 at the most. Because of this fact, they limit the web friends to the same number; this makes the social experience mean far more to the user. So, back to my original point, Path has been available for iPhone only since its inception. Path is not an internet app; unlike Facebook, I cannot upload photos online, I can only use my phone. Also, there are no status updates, Path is just you sharing photos with your closest friends. Path is a fantastic idea that I have been waiting to use for some time now, as I use Android. Path comes with it a simple and sleek UI, which I would expect being so limited on options and all. Just about the only bad part I can find is that not enough of my friends use it, yet. So my recommendation? Go out and get the app, it should be fun.\n","date":"Aug 26, 2011","permalink":"https://harmstyler.me/posts/2011/path-to-android/","section":"Posts","summary":"","title":"Path to Android!"},{"content":"Obviously this is a new site yet again. I just got sick of working with PyroCMS and decided to go to good \u0026lsquo;ol Wordpress, for reliability sake. Luckily I happen to have a licence for an awesome theme, hence the awesome site design. Currently it is not customized but the theme is designed to be customized. Also the site has full access to all the awesome add-ons that wordpress developers have created. With 15% of all created sites out there being Wordpress sites, chances are somebody has thought of it first. What that means to me and possible clients, I can create sites fast and there is a large support structure to back up any work done.\n","date":"Aug 24, 2011","permalink":"https://harmstyler.me/posts/2011/new-site/","section":"Posts","summary":"","title":"New Site"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/php/","section":"Tags","summary":"","title":"PHP"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/pyrocms/","section":"Tags","summary":"","title":"PyroCMS"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/web-development/","section":"Tags","summary":"","title":"Web Development"},{"content":"","date":null,"permalink":"https://harmstyler.me/tags/wordpress/","section":"Tags","summary":"","title":"Wordpress"}]