- PHPZola2019-09-06T00:00:00+00:00https://www.fullstackstanley.com/tags/php/atom.xmlDeploying a CraftCMS website with Dokku2019-09-06T00:00:00+00:002019-09-06T00:00:00+00:00https://www.fullstackstanley.com/articles/deploying-a-craftcms-website-with-dokku/<p>I recently set up my personal blog with Craft CMS and wanted to host it on my own server. To make the most of my own servers, I tend to run web applications using Dokku. This blog post goes through the details of the process of doing so for your own projects.</p>
<span id="continue-reading"></span><h2 id="what-is-dokku">What is Dokku?</h2>
<p>Dokku is a small PaaS service powered by Docker that you can run on your own servers. Think of it as a self-hosted command-line Heroku.</p>
<p>It's great when you want to host multiple small projects on one server for cheap. Although it's unlikely you'll beat Heroku's free tier on price, it definitely beats the $9 tier for small projects. Your website won't go to sleep when idle, you can set up SSL (quite easily, too), and as mentioned above you can you can host multiple websites all on the same server.</p>
<p>In general, the advantage of Dokku over a standard server is that if you're projects use different versions of the same dependences, e.g. one project is on PHP 7.3 and another on 7.2, Dokku (and Docker) makes this super easy to do.</p>
<p>The other great benefits of Dokku include:</p>
<ul>
<li>Heroku-like git push deployment.</li>
<li>Compatible with Heroku buildpacks.</li>
<li>Easy to setup and link databases, storage, cache, etc to your app.</li>
<li>Requires very little to get started - a $5 DO instance will work fine for multiple small applications.</li>
</ul>
<p>For more information and for installation see the <a href="dokku.viewdocs.io/dokku/">Dokku guides</a>. The rest of this tutorial assumes you have Dokku installed on a server and are able to SSH into the server to run commands. Also, that you have a CraftCMS application in a Git repo ready to deploy.</p>
<h2 id="what-is-craftcms">What is CraftCMS?</h2>
<p>If you're reading this theres a good chance you already know what CraftCMS is. If you've come accross this article by chance and don't know: CraftCMS is a fantastic Content Management System. It's built with modern tools including PHP 7, Twig, and the Yii framework. It's easy to make plugins, and it's ridiculously effective for scaffolding admin areas quickly. </p>
<p>All you really need to concentrate on is integrating your design into the front end. And even then, with Twig, it's a really enjoyable developer experience. </p>
<h2 id="making-your-craftcms-project-dokku-ready">Making your CraftCMS project Dokku-ready</h2>
<p>Update your <code>composer.json</code> file and add the following.</p>
<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span> require: {
</span><span style="color:#a3be8c;">+ "php": "~7.2.0",
</span><span style="color:#a3be8c;">+ ext-gd: *,
</span><span> // ...
</span><span> },
</span><span> extra: {
</span><span style="color:#a3be8c;">+ heroku: {
</span><span style="color:#a3be8c;">+ "document-root": "web",
</span><span style="color:#a3be8c;">+ compile: [
</span><span style="color:#a3be8c;">+ chmod 755 storage
</span><span style="color:#a3be8c;">+ ]
</span><span style="color:#a3be8c;">+ }
</span><span> }
</span></code></pre>
<p>We need to specify the PHP version so Dokku knows which one to use. CraftCMS also requires the GD library or Imagick for image editing.</p>
<p>Set the document root to <code>web</code> and also set the correct permissions for any directories that deal with uploads.</p>
<p>Add a new file called <code>Procfile</code> in the root of your application.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>web: vendor/bin/heroku-php-nginx -C nginx.conf.d/nginx.conf web
</span></code></pre>
<p>This lets us add our own custom nginx configuration.</p>
<p>Add the nginx configuration file to <code>nginx.conf.d/nginx.conf</code></p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span> index index.html index.htm index.php;
</span><span>
</span><span> location ^~ /admin {
</span><span> try_files $uri $uri/ /index.php?$query_string;
</span><span> }
</span><span> location ^~ /cpresources {
</span><span> try_files $uri $uri/ /index.php?$query_string;
</span><span> }
</span><span> location / {
</span><span>
</span><span> gzip on;
</span><span> gzip_min_length 1100;
</span><span> gzip_buffers 4 32k;
</span><span> gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml;
</span><span> gzip_vary on;
</span><span> gzip_comp_level 6;
</span><span>
</span><span> try_files $uri $uri/ /index.php?$query_string;
</span><span> }
</span><span>
</span><span> error_page 400 401 402 403 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 426 428 429 431 444 449 450 451 /400-error.html;
</span><span> location /400-error.html {
</span><span> root /var/lib/dokku/data/nginx-vhosts/dokku-errors;
</span><span> internal;
</span><span> }
</span><span>
</span><span> error_page 404 /404-error.html;
</span><span> location /404-error.html {
</span><span> root /var/lib/dokku/data/nginx-vhosts/dokku-errors;
</span><span> internal;
</span><span> }
</span><span>
</span><span> error_page 500 501 502 503 504 505 506 507 508 509 510 511 /500-error.html;
</span><span> location /500-error.html {
</span><span> root /var/lib/dokku/data/nginx-vhosts/dokku-errors;
</span><span> internal;
</span></code></pre>
<p>Now to tell Dokku which buildpacks to use, create a <code>.buildpacks</code> file in the project root as well with the following content.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>https://github.com/heroku/heroku-buildpack-php
</span></code></pre>
<p>That's pretty much it for the CraftCMS side of things. I'd also advise setting up either S3 or Google Cloud Storage for media uploads, this takes out the pain of worrying about persistent storage.</p>
<h2 id="configuring-dokku">Configuring Dokku</h2>
<p>On your server, create the app with the domain name of your choice</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">dokku</span><span> apps:create <yourdomainname.com>
</span></code></pre>
<p>set up a Postgres Database (Or MySQL if you prefer) and link it to the app</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
</span><span style="color:#bf616a;">dokku</span><span> postgres:create <databasename>
</span><span style="color:#bf616a;">dokku</span><span> postgres:link <databasename> <yourdomainname.com>
</span></code></pre>
<p>Run <code>dokku postgres:info databasename</code> to get the database credentials. We'll need to parse the URL in the following format, and add environment variables for each.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>dokku postgres:info <databasename>
</span><span># Dsn: postgres://<USERNAME>:<PASSWORD>@<SERVER>:5432/<DATABASE>
</span><span>dokku config:set <yourdomainname.com> ENVIRONMENT=production
</span><span>dokku config:set <yourdomainname.com> SECURITY_KEY=YOUR_CRAFT_SECURITY_KEY
</span><span>dokku config:set <yourdomainname.com> DB_DRIVER=pgsql
</span><span>dokku config:set <yourdomainname.com> DB_SERVER=<databasename>
</span><span>dokku config:set <yourdomainname.com> DB_USER=<USERNAME>
</span><span>dokku config:set <yourdomainname.com> DB_PASSWORD=<PASSWORD>
</span><span>dokku config:set <yourdomainname.com> DB_DATABASE=<DATABASE>
</span><span>dokku config:set <yourdomainname.com> DB_PORT=5432
</span><span>dokku config:set <yourdomainname.com> DB_SCHEMA=public
</span><span>dokku config:set <yourdomainname.com> DB_TABLE_PREFIX=
</span></code></pre>
<p>Note that, you should double check that these env variables are called from within your Craft config files.</p>
<p>If you require permanent storage on the server (e.g. No S3 or GCS) then you can set that up using <code>storage:mount</code>.</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#65737e;"># optional permanent storage set up
</span><span style="color:#bf616a;">dokku</span><span> storage:mount <yourdomainname.com>/var/lib/dokku/data/storage/<anyuniqueidentifier>:/app/storage
</span></code></pre>
<h2 id="first-deploy">First deploy</h2>
<p>If you have content and users already set up on your local which you wish to migrate to production. You're best bet is to use a tool such as <a href="https://tableplus.com">TablePlus</a> or <a href="https://sequelpro.com/">Sequel Pro</a> to connect directly to the database (Use the Postgres Credentials you have found from the above commands and use SSH to connect directly to it). Dump your local tables and content and import the database to the remote database. Double check your <code>sites</code> table and if your domain is hardcoded to the <code>baseUrl</code>. If it is, then update it to the live domain.</p>
<p>If you don't want to do this and would rather start afresh, you can instead SSH into the app and run <code>./craft setup</code> from within the app after your first deploy. You can do this with <code>dokku run <yourdomainname.com> bash</code> and then <code>cd</code> into the <code>app</code> directory.</p>
<p>In your local project you need to add a new git remote location with the credentials for your server. After that you can push!</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">git</span><span> remote add dokku dokku@serverip:domainname
</span><span style="color:#bf616a;">git</span><span> push dokku master
</span></code></pre>
<p>Hopefully if all went well you should see the set up page for Craft (Or your website if you've imported the database) when you try to access the website. This is assuming you've pointed your domain to the server.</p>
<h2 id="syncing-updates">Syncing updates</h2>
<p>If, like me, you prefer to restrict settings changes on the live site so that they are kept in sync with your git repo, make sure your <code>config/app.php</code> has the following settings.</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>
</span><span>'*' => [
</span><span> // ...
</span><span> 'useProjectConfigFile' => true,
</span><span> // ...
</span><span>],
</span><span>'production' => [
</span><span> // ...
</span><span> 'allowAdminChanges' => false,
</span><span> // ...
</span><span>]
</span></code></pre>
<p>You'll also want to add this Dokku hook so that the Project YAML file is synced automatically. This prevents your site from going down everytime you push an update while it waits for you to sync settings from the admin.</p>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span style="color:#65737e;">// app.json
</span><span>{
</span><span> "</span><span style="color:#a3be8c;">scripts</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">dokku</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">postdeploy</span><span>": "</span><span style="color:#a3be8c;">./craft project-config/sync</span><span>"
</span><span> }
</span><span> }
</span><span>}
</span></code></pre>
<p>And that's it! Save, Commit, Push and updates should just work!</p>
<h2 id="ssl">SSL</h2>
<p>If you're using CloudFlare then SSL should just work out of the box. If not, Dokku has commands for setting up AutoSSL which you can find here: http://dokku.viewdocs.io/dokku/configuration/ssl/</p>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>It took quite some time for me to set this up this correctly. Now that I have the configuration for it deploying updates with Dokku is incredibly easy. I've been running this set up with Google Cloud Storage for media for a few weeks now with no issues. I hope this guide will help others who also want a similar set up!</p>
Using Eloquent, Twig and Slim PHP - Revisited2015-01-22T00:00:00+00:002015-01-22T00:00:00+00:00https://www.fullstackstanley.com/articles/using-eloquent-twig-and-slim-php-revisited/<p>It’s been about one and a half years since I made my blog post <a href="https://fullstackstanley.com/articles/using-eloquent-twig-and-slim-php">Using Eloquent, Twig and Slim PHP</a> and a lot’s changed since then but I still love using these three tools for small websites.</p>
<p>You can view all the code for this post <a href="https://github.com/acoustep/slim-twig-eloquent">here</a> or if you’re looking for the original code from 2013 please switch to the 1.0 release.</p>
<span id="continue-reading"></span><h2 id="what-s-changed">What’s changed?</h2>
<h3 id="migrations">Migrations</h3>
<p>The main update to the repo is migrations. I’ve added <a href="https://github.com/robmorgan/phinx">robmorgan/phinx
</a> because i’ve found nothing better outside of Laravel. I did use <a href="https://github.com/ruckus/ruckusing-migrations">ruckus/ruckusing-migrations</a> for the longest time but switched because I was unsatisfied with setting up a sensible and simple configuration. In fairness, it’s been a while since I’ve used it and it may have improved since then.</p>
<h3 id="gulp-sass-and-coffeescript">Gulp, SASS and Coffeescript</h3>
<p>I use SASS and Coffeescript for all of my new projects. They are easy to remove/ignore if you don’t wish to use them. I’ve also added LESS support just because Gulp makes it so simple.</p>
<h3 id="console-commands">Console commands</h3>
<p>I find myself creating commands for automation fairly often these days. Due to Symfony/Console already being a dependency I thought I may as well include it with a couple of commands out of the box.</p>
<p><code>php ste serve</code></p>
<p>This is just a wrapper around PHP’s built-in local server. I am always googling the arguments for it so I thought I’d just create a shorter command for convenience.</p>
<p><code>php ste model:make</code></p>
<p>This command takes one argument: your model’s name. It then generates the file for you and runs <code>composer dumpautoload</code> so you don’t have to worry about it.</p>
<h3 id="structure">Structure</h3>
<p>The structure is <em>mostly</em> the same. Obviously I’ve added directories for assets. But I’ve also removed <code>app/application.php</code> as the current version of Slim no longer requires the extra code. I’ve also made use of Phinx’s configuration file so you don’t have to update the same variables more than once.</p>
<p>There’s a new directory, <code>app/commands</code>, with the 2 commands mentioned above. This also lets you create your own commands fairly easily for your own automation needs.</p>
<h3 id="whoops">Whoops</h3>
<p><a href="https://github.com/filp/whoops">I really like this package</a>.</p>
<h2 id="issues">Issues</h2>
<p>The biggest issue I had was in fact rather simple to fix. I was getting no output from <code>writeln()</code> after requiring <code>vendor/autoload.php</code> in my console application.</p>
<p>If there’s one thing that’s worse than a convoluted error message it’s no error message at all.</p>
<p>Whoops ended up being the cause of my woes. After disabling Whoops for command line I could see exceptions again!</p>
<p>Another issue was unexpected Symfony/YAML behaviour. Turns out that when YAML parser can’t find the required file it just returns a string with the filename. This took me far longer than I care to admit to figure out.</p>
<h2 id="moving-forward">Moving forward</h2>
<p>As of writing this blog I’ve released an alpha version of the slim-twig-eloquent rewrite and I’m pretty happy with it. I have a couple of small changes to make and I’d like to write a few tests at least for the command line application.</p>
<p>If there’s one thing about Slim that bums me out it’s how difficult it is to test. I did find a nice package for testing but it’s not compatible with Slim 2.5. I’ve decided to hold off on adding testing for now.</p>
<p>I am also interested in the idea of a separate installer package which would allow you to run <code>ste new ProjectName</code> and generate a fresh install of the git repo. </p>
<p>I’ll leave that for another day and another blog post!</p>
Simple Search with Laravel and ElasticSearch2015-01-10T00:00:00+00:002015-01-10T00:00:00+00:00https://www.fullstackstanley.com/articles/simple-search-with-laravel-and-elasticsearch/<p>I was recently asked to make a search engine for a client's website. Normally I would go down the MySQL fulltext search route but I was feeling rather adventurous at the time. I had no experience with ElasticSearch, Apache Solr or any other search system prior to this so I decided to pick ElasticSearch and dive in head first. This tutorial is a result of some of the things I picked up while learning it.</p>
<p>I aim to show you how to set up the <a href="https://github.com/adamfairholm/Elasticquent">Elasticquent</a> Laravel package and some basic ways to fine tune your search engine.</p>
<span id="continue-reading"></span>
<p><strong>Note:</strong> This tutorial is aimed at developers who are already familiar with Laravel but are new to ElasticSearch and want some guidance on getting them to work together.</p>
<h2 id="installing-elasticsearch">Installing ElasticSearch</h2>
<p>If you haven't installed ElasticSearch then make sure you check the <a href="http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/_installing_elasticsearch.html">ElasticSearch documentation</a> for setting it up. Although not necessary it's worth running through the rest of the getting started guide to understand the basics of how ElasticSearch works.</p>
<p>I also recommend <a href="https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en">Postman App</a> if you use Chrome. Postman will let you run REST commands in a nice GUI rather than using command line.</p>
<p>Confirm that your ElasticSearch instance is running by the following command in your command line / Postman App.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>curl -XPOST 'http://localhost:9200/?pretty'
</span></code></pre>
<p>You should see a nice prettified json response if everything is working okay.</p>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> "</span><span style="color:#a3be8c;">status</span><span>": </span><span style="color:#d08770;">200</span><span>,
</span><span> "</span><span style="color:#a3be8c;">name</span><span>": "</span><span style="color:#a3be8c;">Mystique</span><span>",
</span><span> "</span><span style="color:#a3be8c;">cluster_name</span><span>": "</span><span style="color:#a3be8c;">elasticsearch_mitch</span><span>",
</span><span> "</span><span style="color:#a3be8c;">version</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">...</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">tagline</span><span>": "</span><span style="color:#a3be8c;">You Know, for Search</span><span>"
</span><span>}
</span></code></pre>
<h2 id="setting-up-laravel">Setting up Laravel</h2>
<p>I have a fresh Laravel App up and running with a MySQL database. If you are unfamiliar see the <a href="http://laravel.com/docs/4.2/installation">Laravel docs on installing</a>.</p>
<h2 id="packages">Packages</h2>
<p>We'll be using 2 packages in this tutorial, Elasticquent and Faker. You can ignore faker if you plan on importing your own data. For the sake of the tutorial I'll include it.</p>
<p>Open composer.json and add the following to the <code>require</code> object:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>"fairholm/elasticquent": "1.0.*",
</span><span>"fzaninotto/Faker": "1.4.*"
</span></code></pre>
<p>Run <code>composer update --prefer-dist</code> and we'll create our database table.</p>
<h2 id="generating-our-database-table">Generating our database table</h2>
<p>We're going to set up a "posts" table with 3 fields: title, content and tags.</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">php</span><span> artisan migrate:make create_posts_table
</span></code></pre>
<p>In your migration file (located in <code>app/db/migrations/<DATETIME>_create_posts_table.php</code>) use the following code:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>
</span><span style="color:#b48ead;">use </span><span>Illuminate\Database\Migrations\</span><span style="color:#ebcb8b;">Migration</span><span>;
</span><span style="color:#b48ead;">use </span><span>Illuminate\Database\Schema\</span><span style="color:#ebcb8b;">Blueprint</span><span>;
</span><span>
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">CreatePostsTable </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">Migration </span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#65737e;">/**
</span><span style="color:#65737e;"> * Run the migrations.
</span><span style="color:#65737e;"> *
</span><span style="color:#65737e;"> * </span><span style="color:#b48ead;">@return</span><span style="color:#65737e;"> void
</span><span style="color:#65737e;"> */
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">up</span><span style="color:#eff1f5;">()
</span><span style="color:#eff1f5;"> {
</span><span style="color:#eff1f5;"> </span><span style="color:#ebcb8b;">Schema</span><span style="color:#eff1f5;">::</span><span style="color:#bf616a;">create</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">posts</span><span>'</span><span style="color:#eff1f5;">, </span><span style="color:#b48ead;">function</span><span style="color:#eff1f5;">(</span><span style="color:#ebcb8b;">Blueprint </span><span>$</span><span style="color:#bf616a;">table</span><span style="color:#eff1f5;">)
</span><span style="color:#eff1f5;"> {
</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">table</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">increments</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">id</span><span>'</span><span style="color:#eff1f5;">);
</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">table</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">string</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">title</span><span>'</span><span style="color:#eff1f5;">);
</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">table</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">text</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">content</span><span>'</span><span style="color:#eff1f5;">);
</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">table</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">string</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">tags</span><span>'</span><span style="color:#eff1f5;">);
</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">table</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">timestamps</span><span style="color:#eff1f5;">();
</span><span style="color:#eff1f5;"> });
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#65737e;">/**
</span><span style="color:#65737e;"> * Reverse the migrations.
</span><span style="color:#65737e;"> *
</span><span style="color:#65737e;"> * </span><span style="color:#b48ead;">@return</span><span style="color:#65737e;"> void
</span><span style="color:#65737e;"> */
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">down</span><span style="color:#eff1f5;">()
</span><span style="color:#eff1f5;"> {
</span><span style="color:#eff1f5;"> </span><span style="color:#ebcb8b;">Schema</span><span style="color:#eff1f5;">::</span><span style="color:#bf616a;">drop</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">posts</span><span>'</span><span style="color:#eff1f5;">);
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;">}
</span></code></pre>
<p>Do a migration and then we'll move on to generating some dummy data.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>php artisan migrate
</span></code></pre>
<h2 id="dummy-data">Dummy data</h2>
<p>We're going to set up a bunch of test data in a seeder file. This will let us test that the search works without the hassle of inserting data manually.</p>
<p>Our initial post model should look like this <code>app/model/Post.php</code></p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Post </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">Eloquent </span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public </span><span>$</span><span style="color:#bf616a;">fillable </span><span>= </span><span style="color:#eff1f5;">[</span><span>'</span><span style="color:#a3be8c;">title</span><span>'</span><span style="color:#eff1f5;">, </span><span>'</span><span style="color:#a3be8c;">content</span><span>'</span><span style="color:#eff1f5;">, </span><span>'</span><span style="color:#a3be8c;">tags</span><span>'</span><span style="color:#eff1f5;">];
</span><span style="color:#eff1f5;">}
</span></code></pre>
<p>Create <code>app/database/seeds/PostsTableSeeder.php</code> and add the following code</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">PostsTableSeeder </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">Seeder </span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">run</span><span style="color:#eff1f5;">()
</span><span style="color:#eff1f5;"> {
</span><span style="color:#eff1f5;"> </span><span style="color:#65737e;">// Remove any existing data
</span><span style="color:#eff1f5;"> </span><span style="color:#ebcb8b;">DB</span><span style="color:#eff1f5;">::</span><span style="color:#bf616a;">table</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">pages</span><span>'</span><span style="color:#eff1f5;">)-></span><span style="color:#bf616a;">truncate</span><span style="color:#eff1f5;">();
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">faker </span><span>= </span><span style="color:#eff1f5;">Faker\</span><span style="color:#ebcb8b;">Factory</span><span style="color:#eff1f5;">::</span><span style="color:#bf616a;">create</span><span style="color:#eff1f5;">();
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#65737e;">// Generate some dummy data
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">for</span><span style="color:#eff1f5;">(</span><span>$</span><span style="color:#bf616a;">i</span><span>=</span><span style="color:#d08770;">0</span><span style="color:#eff1f5;">; </span><span>$</span><span style="color:#bf616a;">i</span><span><</span><span style="color:#d08770;">30</span><span style="color:#eff1f5;">; </span><span>$</span><span style="color:#bf616a;">i</span><span>++</span><span style="color:#eff1f5;">) {
</span><span style="color:#eff1f5;"> </span><span style="color:#ebcb8b;">Post</span><span style="color:#eff1f5;">::</span><span style="color:#bf616a;">create</span><span style="color:#eff1f5;">([
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">title</span><span>' => $</span><span style="color:#bf616a;">faker</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">sentence</span><span style="color:#eff1f5;">(</span><span style="color:#d08770;">3</span><span style="color:#eff1f5;">),
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">content</span><span>' => $</span><span style="color:#bf616a;">faker</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">paragraph</span><span style="color:#eff1f5;">(</span><span style="color:#d08770;">5</span><span style="color:#eff1f5;">),
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">tags</span><span>' => </span><span style="color:#96b5b4;">join</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">,</span><span>'</span><span style="color:#eff1f5;">, </span><span>$</span><span style="color:#bf616a;">faker</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">words</span><span style="color:#eff1f5;">(</span><span style="color:#d08770;">5</span><span style="color:#eff1f5;">))
</span><span style="color:#eff1f5;"> ]);
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;">}
</span></code></pre>
<p>After running <code>php artisan db:seed --class="PostsTableSeeder"</code> we should now have plenty of test data to work with!</p>
<h2 id="setting-up-elasticquent">Setting up Elasticquent</h2>
<p>Let's edit our model in <code>app/models/Post.php</code> and add the following:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span style="color:#b48ead;">use </span><span>Elasticquent\</span><span style="color:#ebcb8b;">ElasticquentTrait</span><span>;
</span><span>
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Post </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">\Eloquent </span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">use </span><span style="color:#a3be8c;">ElasticquentTrait</span><span style="color:#eff1f5;">;
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public </span><span>$</span><span style="color:#bf616a;">fillable </span><span>= </span><span style="color:#eff1f5;">[</span><span>'</span><span style="color:#a3be8c;">title</span><span>'</span><span style="color:#eff1f5;">, </span><span>'</span><span style="color:#a3be8c;">content</span><span>'</span><span style="color:#eff1f5;">, </span><span>'</span><span style="color:#a3be8c;">tags</span><span>'</span><span style="color:#eff1f5;">];
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">protected </span><span>$</span><span style="color:#bf616a;">mappingProperties </span><span>= </span><span style="color:#96b5b4;">array</span><span style="color:#eff1f5;">(
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">title</span><span>' => </span><span style="color:#eff1f5;">[
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">type</span><span>' => '</span><span style="color:#a3be8c;">string</span><span>'</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>"</span><span style="color:#a3be8c;">analyzer</span><span>" => "</span><span style="color:#a3be8c;">standard</span><span>"</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> ],
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">content</span><span>' => </span><span style="color:#eff1f5;">[
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">type</span><span>' => '</span><span style="color:#a3be8c;">string</span><span>'</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>"</span><span style="color:#a3be8c;">analyzer</span><span>" => "</span><span style="color:#a3be8c;">standard</span><span>"</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> ],
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">tags</span><span>' => </span><span style="color:#eff1f5;">[
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">type</span><span>' => '</span><span style="color:#a3be8c;">string</span><span>'</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>"</span><span style="color:#a3be8c;">analyzer</span><span>" => "</span><span style="color:#a3be8c;">stop</span><span>"</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>"</span><span style="color:#a3be8c;">stopwords</span><span>" => </span><span style="color:#eff1f5;">[</span><span>"</span><span style="color:#a3be8c;">,</span><span>"</span><span style="color:#eff1f5;">]
</span><span style="color:#eff1f5;"> ],
</span><span style="color:#eff1f5;"> );
</span><span style="color:#eff1f5;">}
</span><span>
</span></code></pre>
<p>On line 2 we create the Elasticquent Trait shortcut and on line 5 we include it in our class.</p>
<p>Line 9 we add our mapping configuration for ElasticSearch. You can read more about mappings <a href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping.html">here</a>.</p>
<p>Each mapping has a type and an analyzer. Type's can be various data types including strings, numbers and dates. For now we will stick to the string type but be aware that different types allow you to take advantage of different things. You can learn more about the types that ElasticSearch supports <a href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-types.html">here</a></p>
<p>The analyzer determines how ElasticSearch stores your data for searching. I've chosen <code>standard</code> for title and content and <code>stop</code> for tags. The standard analyzer will remove HTML and grammar and index each word separately. The stop analyzer can be set to choose which characters split the words for indexing.</p>
<p>As an example take this sentence:</p>
<blockquote>
<p>I love laravel, ElasticSearch and Laravel work well together.</p>
</blockquote>
<p>With a standard analyzer ElasticSearch will create a list like this:</p>
<ul>
<li>i</li>
<li>love</li>
<li>laravel,</li>
<li>elasticsearch</li>
<li>and</li>
<li>laravel</li>
<li>work</li>
<li>well</li>
<li>together</li>
</ul>
<p>With our settings the stop analyzer will group them like this:</p>
<ul>
<li>I love laravel</li>
<li>ElasticSearch and Laravel work well together.</li>
</ul>
<p>This can be advantagous if you want to prioritise certain phrases.</p>
<p>Now we've configured how we want our search to operate it's time to index our database!</p>
<p>Let's use Laravel's REPL to generate our ElasticSearch data. Go to your command line and type </p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">php</span><span> artisan tinker
</span></code></pre>
<p>Type the following commands</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>Post::createIndex($shards = null, $replicas = null);
</span><span>
</span><span>Post::putMapping($ignoreConflicts = true);
</span><span>
</span><span>Post::addAllToIndex();
</span></code></pre>
<p>The first command sets up our index. An index is sort of like a database table in the ElasticSearch world.</p>
<p><code>putMapping()</code> takes the mapping properties we set in the model so that ElasticSearch knows how to index all of our data.</p>
<p><code>addAllToIndex()</code> takes all the data from the database and puts it into ElasticSearch</p>
<h2 id="useful-elasticsearch-api-methods">Useful ElasticSearch API methods</h2>
<p>Elasticquent sets up our index as "default" by default. We can view our mappings by using the following curl request</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">curl</span><span> localhost:9200/default/_mapping?pretty
</span></code></pre>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> "</span><span style="color:#a3be8c;">default</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">mappings</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">posts</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">properties</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">content</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">type</span><span>" : "</span><span style="color:#a3be8c;">string</span><span>",
</span><span> "</span><span style="color:#a3be8c;">analyzer</span><span>" : "</span><span style="color:#a3be8c;">standard</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">created_at</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">type</span><span>" : "</span><span style="color:#a3be8c;">string</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">id</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">type</span><span>" : "</span><span style="color:#a3be8c;">long</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">tags</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">type</span><span>" : "</span><span style="color:#a3be8c;">string</span><span>",
</span><span> "</span><span style="color:#a3be8c;">analyzer</span><span>" : "</span><span style="color:#a3be8c;">stop</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">title</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">type</span><span>" : "</span><span style="color:#a3be8c;">string</span><span>",
</span><span> "</span><span style="color:#a3be8c;">analyzer</span><span>" : "</span><span style="color:#a3be8c;">standard</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">updated_at</span><span>" : {
</span><span> "</span><span style="color:#a3be8c;">type</span><span>" : "</span><span style="color:#a3be8c;">string</span><span>"
</span><span> }
</span><span> }
</span><span> }
</span><span> }
</span><span> }
</span><span>}
</span></code></pre>
<p>In ElasticSearch a table is called a type. We can view all of the documents in a specific type with this query:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>curl 'localhost:9200/default/posts/_search?pretty'
</span></code></pre>
<p>We can do a basic do a basic search by altering the above command slightly</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">curl </span><span>'</span><span style="color:#a3be8c;">localhost:9200/default/posts/_search?q=title:searchterm&pretty</span><span>'
</span></code></pre>
<p>And we can view a specific document with this</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">curl </span><span>'</span><span style="color:#a3be8c;">localhost:9200/default/posts/1?pretty</span><span>'
</span></code></pre>
<p>For more info on the ElasticSearch API check out the <a href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs.html">documentation</a></p>
<h2 id="creating-the-front-end">Creating the front end</h2>
<p>Add a route to <code>app/routes.php</code></p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span style="color:#ebcb8b;">Route</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">/</span><span>', ['</span><span style="color:#a3be8c;">as</span><span>' => '</span><span style="color:#a3be8c;">search</span><span>', '</span><span style="color:#a3be8c;">uses</span><span>' => </span><span style="color:#b48ead;">function</span><span>() {
</span><span>
</span><span> </span><span style="color:#65737e;">// Check if user has sent a search query
</span><span> </span><span style="color:#b48ead;">if</span><span>($</span><span style="color:#bf616a;">query </span><span>= </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', </span><span style="color:#d08770;">false</span><span>)) {
</span><span> </span><span style="color:#65737e;">// Use the Elasticquent search method to search ElasticSearch
</span><span> $</span><span style="color:#bf616a;">posts </span><span>= </span><span style="color:#ebcb8b;">Post</span><span>::</span><span style="color:#bf616a;">search</span><span>($</span><span style="color:#bf616a;">query</span><span>);
</span><span> } </span><span style="color:#b48ead;">else </span><span>{
</span><span> </span><span style="color:#65737e;">// Show all posts if no query is set
</span><span> $</span><span style="color:#bf616a;">posts </span><span>= </span><span style="color:#ebcb8b;">Post</span><span>::</span><span style="color:#bf616a;">all</span><span>();
</span><span> }
</span><span>
</span><span> </span><span style="color:#b48ead;">return </span><span style="color:#ebcb8b;">View</span><span>::</span><span style="color:#bf616a;">make</span><span>('</span><span style="color:#a3be8c;">home</span><span>', </span><span style="color:#96b5b4;">compact</span><span>('</span><span style="color:#a3be8c;">posts</span><span>'));
</span><span>
</span><span>}]);
</span></code></pre>
<p>Make a template in <code>app/views/home.blade.php</code></p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span><</span><span style="color:#bf616a;">html</span><span>>
</span><span><</span><span style="color:#bf616a;">body</span><span>>
</span><span>{{ Form::open(['method' => 'get', 'route' => 'search']) }}
</span><span>
</span><span> {{ Form::input('search', 'query', Input::get('query', ''))}}
</span><span> {{ Form::submit('Filter results') }}
</span><span>
</span><span>{{ Form:: close() }}
</span><span>
</span><span>@foreach($posts as $post)
</span><span> <</span><span style="color:#bf616a;">div</span><span>>
</span><span> <</span><span style="color:#bf616a;">h2</span><span>>{{{ $post->title }}}</</span><span style="color:#bf616a;">h2</span><span>>
</span><span> <</span><span style="color:#bf616a;">div</span><span>>{{{ $post->content }}}</</span><span style="color:#bf616a;">div</span><span>>
</span><span> <</span><span style="color:#bf616a;">div</span><span>><</span><span style="color:#bf616a;">small</span><span>>{{{ $post->tags }}}</</span><span style="color:#bf616a;">small</span><span>></</span><span style="color:#bf616a;">div</span><span>>
</span><span> </</span><span style="color:#bf616a;">div</span><span>>
</span><span>@endforeach
</span><span></</span><span style="color:#bf616a;">body</span><span>>
</span><span></</span><span style="color:#bf616a;">html</span><span>>
</span></code></pre>
<p>In the above snippet we create a form that allows us to type in a search term. Below the form we iterate either through all of the posts or all of the search results depending on whether the user has entered a search term.</p>
<p>Here's how it looks currently.</p>
<p><img src="/media/elastic-search-results.png" alt="ElasticSearch Results" /></p>
<p>We could stop now and the search would work fairly well. But where is the fun in that? Let's tinker and see how we can improve our search results.</p>
<h2 id="fine-tuning-your-search">Fine-tuning your search</h2>
<p>Elasticquent has another method called <code>searchByQuery()</code> which will allow us to specify more details on how we want ElasticSearch to query our data. Here's an example (taken and modified from the Elasticquent docs)</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>$</span><span style="color:#bf616a;">posts </span><span>= </span><span style="color:#ebcb8b;">Post</span><span>::</span><span style="color:#bf616a;">searchByQuery</span><span>(['</span><span style="color:#a3be8c;">match</span><span>' => ['</span><span style="color:#a3be8c;">title</span><span>' => </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', '')]]);
</span></code></pre>
<p>In the above example only the title is searched. How does this differ from the <code>search()</code> method behind the scenes? The <code>search()</code> query will match all parameters including our content and tags fields.</p>
<p>If we try searching our data now with text from the <code>content</code> field you will notice drastically different results. We may even notice different results when you take data from the title fields, too. This is because ElasticSearch generates a score from the data it searches. Any relevant text in the queried fields will improve that score.</p>
<p>Let's give our <code>title</code> priority so that searches that match our titles will appear above those that only appear in the content.</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>$</span><span style="color:#bf616a;">posts </span><span>= </span><span style="color:#ebcb8b;">Post</span><span>::</span><span style="color:#bf616a;">searchByQuery</span><span>([
</span><span> '</span><span style="color:#a3be8c;">multi_match</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">query</span><span>' => </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', ''),
</span><span> '</span><span style="color:#a3be8c;">fields</span><span>' => [ "</span><span style="color:#a3be8c;">title^5</span><span>", "</span><span style="color:#a3be8c;">content</span><span>"]
</span><span> ],
</span><span>]);
</span></code></pre>
<p>The caret symbol (^) lets ElasticSearch know we want the title field to have added weight to it by the number that follows it.</p>
<p>That's all well and good, but now we want to search our tags because they have specific keywords and phrases we want to match in the search results.</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>$</span><span style="color:#bf616a;">posts </span><span>= </span><span style="color:#ebcb8b;">Post</span><span>::</span><span style="color:#bf616a;">searchByQuery</span><span>([
</span><span> '</span><span style="color:#a3be8c;">match_phrase</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">tags</span><span>' => </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', '')
</span><span> ]
</span><span>]);
</span></code></pre>
<p>To make use of both searches we need to do a compound query. There are many types of compound query but the one we'll use is the <code>bool</code> query.</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>$</span><span style="color:#bf616a;">posts </span><span>= </span><span style="color:#ebcb8b;">Post</span><span>::</span><span style="color:#bf616a;">searchByQuery</span><span>([
</span><span> "</span><span style="color:#a3be8c;">bool</span><span>" => [
</span><span> '</span><span style="color:#a3be8c;">must</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">multi_match</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">query</span><span>' => </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', ''),
</span><span> '</span><span style="color:#a3be8c;">fields</span><span>' => [ "</span><span style="color:#a3be8c;">title^2</span><span>", "</span><span style="color:#a3be8c;">content</span><span>"]
</span><span> ],
</span><span> ],
</span><span> "</span><span style="color:#a3be8c;">should</span><span>" => [
</span><span> '</span><span style="color:#a3be8c;">match</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">tags</span><span>' => [
</span><span> "</span><span style="color:#a3be8c;">query</span><span>" => </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', ''),
</span><span> "</span><span style="color:#a3be8c;">type</span><span>" => "</span><span style="color:#a3be8c;">phrase</span><span>"
</span><span> ]
</span><span> ]
</span><span> ]
</span><span> ]
</span><span>]);
</span></code></pre>
<p>In a <code>bool</code> query we can specify three parameters: <code>must</code>, <code>should</code> and <code>must_not</code>. In ours we have specified we must get a match from the title or content field and that we can optionally also get a match from the tags field.</p>
<p>We can also completely filter out specific terms if they are irrelevent with a filter. Here we're using the <a href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-not-filter.html">not_filter</a>. You can read more on filters <a href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filters.html">here</a></p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>$</span><span style="color:#bf616a;">posts </span><span>= </span><span style="color:#ebcb8b;">Post</span><span>::</span><span style="color:#bf616a;">searchByQuery</span><span>([
</span><span> '</span><span style="color:#a3be8c;">filtered</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">filter</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">not</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">terms</span><span>' => ['</span><span style="color:#a3be8c;">title</span><span>' => ['</span><span style="color:#a3be8c;">impedit</span><span>', '</span><span style="color:#a3be8c;">voluptatem</span><span>']]
</span><span> ]
</span><span> ],
</span><span> '</span><span style="color:#a3be8c;">query</span><span>' => [
</span><span> "</span><span style="color:#a3be8c;">bool</span><span>" => [
</span><span> '</span><span style="color:#a3be8c;">must</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">multi_match</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">query</span><span>' => </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', ''),
</span><span> '</span><span style="color:#a3be8c;">fields</span><span>' => [ "</span><span style="color:#a3be8c;">title^2</span><span>", "</span><span style="color:#a3be8c;">content</span><span>"]
</span><span> ],
</span><span> ],
</span><span> "</span><span style="color:#a3be8c;">should</span><span>" => [
</span><span> '</span><span style="color:#a3be8c;">match</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">tags</span><span>' => [
</span><span> "</span><span style="color:#a3be8c;">query</span><span>" => </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', ''),
</span><span> "</span><span style="color:#a3be8c;">type</span><span>" => "</span><span style="color:#a3be8c;">phrase</span><span>"
</span><span> ]
</span><span> ]
</span><span> ]
</span><span> ]
</span><span> ],
</span><span> ],
</span><span>]);
</span></code></pre>
<p>Between lines 2-7 we're specifying that when 'impedit' or 'voluptatem' are not in the title. </p>
<p>If we had a <code>published</code> field in out database another useful filter would be to only search published posts.</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span>$</span><span style="color:#bf616a;">pages </span><span>= $</span><span style="color:#bf616a;">this</span><span>-></span><span style="color:#bf616a;">page</span><span>-></span><span style="color:#bf616a;">searchByQuery</span><span>([
</span><span> '</span><span style="color:#a3be8c;">filtered</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">filter</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">term</span><span>' => ['</span><span style="color:#a3be8c;">published</span><span>' => '</span><span style="color:#a3be8c;">1</span><span>']
</span><span> ],
</span><span> '</span><span style="color:#a3be8c;">query</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">multi_match</span><span>' => [
</span><span> '</span><span style="color:#a3be8c;">query</span><span>' => </span><span style="color:#ebcb8b;">Input</span><span>::</span><span style="color:#bf616a;">get</span><span>('</span><span style="color:#a3be8c;">query</span><span>', ''),
</span><span> '</span><span style="color:#a3be8c;">fields</span><span>' => [ "</span><span style="color:#a3be8c;">title^2</span><span>", "</span><span style="color:#a3be8c;">content</span><span>"]
</span><span> ],
</span><span> ],
</span><span> ],
</span><span>]);
</span></code></pre>
<h2 id="summary">Summary</h2>
<p>That's it for our search. We've looked at setting up Elasticquent with our model and looked several ways we can customise our search results. </p>
<p>We can use queries to order our search results by score, we can create compound queries for more complex search results and filters for simple boolean queries.</p>
<p>Although Elasticquent is great for a basic search engine there's also the official <a href="https://github.com/elasticsearch/elasticsearch-php">ElasticSearch client for PHP</a> for when you need something more advanced such as fragment highlighting or autocomplete. </p>
<p>I'm really enjoying what I've learned so far with ElasticSearch and I'm very glad that I decided to pick it up. I also really recommend <a href="http://www.amazon.co.uk/gp/product/B00JXLF7AK/ref=as_li_tl?ie=UTF8&camp=1634&creative=19450&creativeASIN=B00JXLF7AK&linkCode=as2&tag=fullstan-21&linkId=3UGJUFM7O7NQS4GJ">ElasticSearch Server - Second Edition</a> (Amazon referral link). I'm about 40% of the way through and I've learned a lot already.</p>
<img src="https://ir-uk.amazon-adsystem.com/e/ir?t=fullstan-21&l=as2&o=2&a=B00JXLF7AK" width="1" height="1">
PHP 5.4 Trait Qwerks2014-08-18T00:00:00+00:002014-08-18T00:00:00+00:00https://www.fullstackstanley.com/articles/php-5-4-trait-qwerks/<p>Although the title may lead you to think otherwise; I actually really like Traits. I wish I had made the switch to 5.4 sooner! Bundle them with the shorter array syntax and I'm a happy PHP dev.</p>
<p>I recently experimented with Traits in a Laravel package. You can find the package here <a href="https://github.com/acoustep/dat-controller">DatController</a></p>
<p>This blog is contains a couple of the not-so-obvious points of traits which I came accross.</p>
<h2 id="you-can-not-override-trait-properties">You can not override trait properties</h2>
<p>I think this is what I like the least about traits. You can not set a default property and have a class override it.</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span style="color:#b48ead;">trait </span><span>NewTrait {
</span><span> </span><span style="color:#b48ead;">public </span><span>$</span><span style="color:#bf616a;">variable </span><span>= '</span><span style="color:#a3be8c;">default value</span><span>';
</span><span>}
</span><span>
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">NewClass </span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">use </span><span style="color:#a3be8c;">NewTrait</span><span style="color:#eff1f5;">;
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public </span><span>$</span><span style="color:#bf616a;">variable </span><span>= '</span><span style="color:#a3be8c;">override value</span><span>'</span><span style="color:#eff1f5;">;
</span><span style="color:#eff1f5;">}
</span></code></pre>
<p>I would love to be able to override trait properties. However, you could argue that it's best to use inheritence for these situations.</p>
<span id="continue-reading"></span><h2 id="you-can-not-use-two-traits-that-both-require-the-use-the-same-trait">You can not use two traits that both require the use the same trait.</h2>
<p>This one is a little more tricky to explain.</p>
<p>As an example say you have the following set up:</p>
<ul>
<li>BaseTrait - a trait with core methods that you want included in all your other traits.</li>
<li>CreateTrait - a trait dedicated to a create method.</li>
<li>UpdateTrait - a trait dedicated to a update method.</li>
<li>ResourceTrait - a trait which includes CreateTrait, UpdateTrait, DestroyTrait etc so that a developer doesn't have to include them all seperately.</li>
</ul>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>trait BaseTrait {
</span><span> public function base_method()
</span><span> {
</span><span> }
</span><span>}
</span><span>
</span><span>trait CreateTrait {
</span><span> use BaseTrait;
</span><span> public function create()
</span><span> {
</span><span> }
</span><span>}
</span><span>
</span><span>trait UpdateTrait {
</span><span> use BaseTrait;
</span><span> public function update()
</span><span> {
</span><span> }
</span><span>}
</span><span>
</span><span>trait ResourceTrait {
</span><span> use CreateTrait;
</span><span> use UpdateTrait;
</span><span>}
</span></code></pre>
<p>Because both CreateTrait and UpdateTrait include the BaseTrait you will receive the following error:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>Trait method base_method has not been applied, because there are collisions with other trait methods on ResourceTrait
</span></code></pre>
<p>Even though it's the exact same method it will create a collision.</p>
<p>That's all for now. I definitely want to use traits more in PHP but I do feel they are quite restrictive in some situations.</p>
Laravel 4.1 one-to-one polymorphic relationships2014-01-20T00:00:00+00:002014-01-20T00:00:00+00:00https://www.fullstackstanley.com/articles/laravel-4-1-one-to-one-polymorphic-relationships/<p>To use one-to-one polymorphic relationships in Laravel 4.1 use the "morphOne" method in your models.</p>
<p>For example: I have two tables, pages and products. I want to be able to add one featured image to each of these.</p>
<p>The Product model would look like this:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Product </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">Eloquent </span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">image</span><span style="color:#eff1f5;">() {
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">return </span><span>$</span><span style="color:#bf616a;">this</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">morphOne</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">FeaturedImage</span><span>'</span><span style="color:#eff1f5;">, </span><span>'</span><span style="color:#a3be8c;">imageable</span><span>'</span><span style="color:#eff1f5;">);
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;">}
</span></code></pre>
<p>The Page model would be:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Page </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">Eloquent </span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">image</span><span style="color:#eff1f5;">() {
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">return </span><span>$</span><span style="color:#bf616a;">this</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">morphOne</span><span style="color:#eff1f5;">(</span><span>'</span><span style="color:#a3be8c;">FeaturedImage</span><span>'</span><span style="color:#eff1f5;">, </span><span>'</span><span style="color:#a3be8c;">imageable</span><span>'</span><span style="color:#eff1f5;">);
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;">}
</span></code></pre>
<span id="continue-reading"></span>
<p>For the FeaturedImage table you would need to columns <code>imageable_id:integer</code> and <code>imageable_type</code>.</p>
<p>Then in your FeaturedImage model you can have:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">FeaturedImage </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">Eloquent </span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">imageable</span><span style="color:#eff1f5;">()
</span><span style="color:#eff1f5;"> {
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">return </span><span>$</span><span style="color:#bf616a;">this</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">morphTo</span><span style="color:#eff1f5;">();
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;">}
</span></code></pre>
<p>This will allow you to use polymorphism on one-to-one relationships</p>
Creating a Twitter Feed in Laravel 42013-08-16T00:00:00+00:002013-08-16T00:00:00+00:00https://www.fullstackstanley.com/articles/creating-a-twitter-feed-in-laravel-4/<p><em>This tutorial is now out of date - checkout <a href="http://packalyst.com/packages/package/philo/laravel-twitter">philo/laravel-twitter</a> or <a href="http://packalyst.com/packages/package/thujohn/twitter">thujohn/twitter</a> for an easier and more up to date solution.</em></p>
<p>In this tutorial I aim to show three things</p>
<ul>
<li>How to get Twitteroath working with Laravel 4</li>
<li>How to create and access a new configuration file for your twitter settings</li>
<li>How to create a helper method for your tweets</li>
</ul>
<p>To start off install <a href="https://github.com/abraham/twitteroauth">twitteroath</a> via composer.</p>
<p>Put line 4 in your <code>composer.json</code> with the rest of the Laravel Composer set up and run <code>composer update</code> or <code>composer install</code> if you haven't already</p>
<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span>{
</span><span> "</span><span style="color:#a3be8c;">require</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">laravel/framework</span><span>": "</span><span style="color:#a3be8c;">4.0.*@dev</span><span>",
</span><span> "</span><span style="color:#a3be8c;">abraham/twitteroauth</span><span>": "</span><span style="color:#a3be8c;">dev-add-composer-json</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">autoload</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">classmap</span><span>": [
</span><span> "</span><span style="color:#a3be8c;">app/commands</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/controllers</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/models</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/database/migrations</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/database/seeds</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/tests/TestCase.php</span><span>"
</span><span> ]
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">scripts</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">post-update-cmd</span><span>": "</span><span style="color:#a3be8c;">php artisan optimize</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">minimum-stability</span><span>": "</span><span style="color:#a3be8c;">dev</span><span>"
</span><span>}
</span></code></pre>
<span id="continue-reading"></span>
<p>While that's running head over to <a href="http://dev.twitter.com">dev.twitter.com</a> and sign in with your Twitter account. If you haven't already made consumer tokens and access tokens now is the time to do so!</p>
<h2 id="making-access-and-consumer-tokens">Making access and consumer tokens</h2>
<p>Go to my applications, create a new application and fill in the details. After you create your application you will be asked if you want to create an access token from your Twitter account. Make sure you do it!</p>
<p>After that's done you can view your applications details by going to My applications.</p>
<p><img src="https://i.imgur.com/RyVJMq5.jpg" alt="My Applications" /></p>
<p>Click on your newly created application and take note of your Consumer key, Consumer secret, Access token and Access token secret.</p>
<p>Now in your Routes you should be able to do the following</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>Route::get('/', function()
</span><span>{
</span><span>$connection = new TwitterOAuth('consumer\_key', 'consumer\_secret', 'access\_token', 'access\_secret\_token');
</span><span> $tweets = $connection->get("https://api.twitter.com/1.1/statuses/user\_timeline.json?screen\_name=TWITTER\_ACCOUNT\_NAME&count=2");
</span><span>return json_encode($tweets);
</span><span>});
</span></code></pre>
<p>Going to your applications route should show your tweets in JSON format now - however, I hardly find this organization in the spirit of Laravel so let's clean it up a little!</p>
<h2 id="creating-and-accessing-a-new-configuration-file">Creating and accessing a new configuration file</h2>
<p>Within <code>app/config</code> create a new file called <code>twitter.php</code> with the following:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>return array(
</span><span> 'consumer_key' => 'consumer_key_here',
</span><span> 'consumer_secret' => 'consumer_secret_here',
</span><span> 'access_token' => 'access_token_here',
</span><span> 'access_secret_token' => 'access_secret_token_here',
</span><span> 'twitter_user' => 'mitchartemis'
</span><span>);
</span></code></pre>
<p>Now we can change our <code>route.php</code> to have the following:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>$connection = new TwitterOAuth(Config::get('twitter.consumer_key'), Config::get('twitter.consumer_secret'), Config::get('twitter.access_token'), Config::get('twitter.access_secret_token'));
</span><span>$tweets = $connection->get("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=".Config::get('twitter.twitter_user')."&count=5");
</span></code></pre>
<p>Now that's working you might feel that you need to use this multiple times within the application. Having it in a route or controller method isn't ideal for this.</p>
<h2 id="creating-a-helper-method-for-your-tweets">Creating a helper method for your tweets</h2>
<p>Create a new file called <code>helpers.php</code> in the<code>app/</code> folder.</p>
<p>Now we can put any functions we want to use more than once in here. So let's make a simple one for Twitter.</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>function twitterFeed()
</span><span>{
</span><span> $connection = new TwitterOAuth(Config::get('twitter.consumer_key'), Config::get('twitter.consumer_secret'), Config::get('twitter.access_token'), Config::get('twitter.access_secret_token'));
</span><span> $tweets = $connection->get("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=".Config::get('twitter.twitter_user')."&count=5");
</span><span>
</span><span> return $tweets;
</span><span>}
</span></code></pre>
<p>Inside of routes we can now remove the 2 lines of code and use</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>$tweets = twitterFeed();
</span></code></pre>
<p>At this point if you try the code you will most likely not be able to access the <code>twitterFeed()</code> funtion. To use we must add it to <code>composer.json</code> as shown below</p>
<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span>{
</span><span> "</span><span style="color:#a3be8c;">require</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">laravel/framework</span><span>": "</span><span style="color:#a3be8c;">4.0.*@dev</span><span>",
</span><span> "</span><span style="color:#a3be8c;">abraham/twitteroauth</span><span>": "</span><span style="color:#a3be8c;">dev-add-composer-json</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">autoload</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">classmap</span><span>": [
</span><span> "</span><span style="color:#a3be8c;">app/commands</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/controllers</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/models</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/database/migrations</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/database/seeds</span><span>",
</span><span> "</span><span style="color:#a3be8c;">app/tests/TestCase.php</span><span>"
</span><span> ],
</span><span> "</span><span style="color:#a3be8c;">files</span><span>": [
</span><span> "</span><span style="color:#a3be8c;">app/helpers.php</span><span>"
</span><span> ]
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">scripts</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">post-update-cmd</span><span>": "</span><span style="color:#a3be8c;">php artisan optimize</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">minimum-stability</span><span>": "</span><span style="color:#a3be8c;">dev</span><span>"
</span><span>}
</span></code></pre>
<p>Between lines 15-18 I have added the helpers.php to an array with the key of "files". Now run <code>composer update</code> and you're all set!</p>
Using Eloquent, Twig and Slim PHP2013-08-07T00:00:00+00:002013-08-07T00:00:00+00:00https://www.fullstackstanley.com/articles/using-eloquent-twig-and-slim-php/<p><em>Note</em>: See the <a href="/articles/using-eloquent-twig-and-slim-php-revisited">update to this post</a> where I've made several changes</p>
<p>Originally while I was writing this blog I intended to make a step by
step guide to putting together three of my favourite PHP tools. After a
few issues and finding out that <a href="https://github.com/codeguy/Slim-Views">Slim Views</a> is now a thing I decided
instead to put together a git repo so anyone can get started quickly. I
will use this blog post to quickly show how it works!</p>
<p>*Note that this tutorial doesn’t aim to teach you Slim, Twig or Eloquent</p>
<ul>
<li>It’s to show you how to use them together effectively.*</li>
</ul>
<p>You can find the repo here: <a href="https://github.com/acoustep/slim-twig-eloquent">https://github.com/acoustep/slim-twig-eloquent</a></p>
<p>After following the Installation instructions you should be able to use each component as well as Eloquent validation.</p>
<span id="continue-reading"></span><h3 id="folder-structure">Folder Structure</h3>
<ul>
<li>app/
<ul>
<li>config/</li>
<li>models/</li>
<li>views/</li>
<li>routes.php</li>
<li>application.php</li>
<li>Validator.php</li>
</ul>
</li>
<li>public/ (or public_html/)
<ul>
<li>css/</li>
<li>js/</li>
<li>images/</li>
<li>.htaccess</li>
<li>index.php</li>
</ul>
</li>
<li>vendor/</li>
<li>config.php</li>
<li>index.php</li>
</ul>
<p>As the <code>public/index.php</code> calls <code>/index.php</code> from the root of the application which then includes <code>config.php</code>, <code>app/application.php</code> and <code>app/routes.php</code>.</p>
<p><code>app/application.php</code> sets Slim, Twig and Eloquent up so that you can create all of your Slim routes in <code>app/routes.php</code>.</p>
<h3 id="example-usage">Example Usage</h3>
<p>Assuming you've now run <code>git clone https://github.com/acoustep/slim-twig-eloquent.git</code>, <code>composer install</code>, renamed the <code>config.php</code> file and changed your database settings let's create a table. I've opted to use MySQL - here's the query to make a <code>posts</code> table:</p>
<pre data-lang="sql" style="background-color:#2b303b;color:#c0c5ce;" class="language-sql "><code class="language-sql" data-lang="sql"><span style="color:#b48ead;">CREATE TABLE </span><span>`</span><span style="color:#8fa1b3;">posts</span><span>` (
</span><span> `</span><span style="color:#a3be8c;">id</span><span>` </span><span style="color:#b48ead;">int</span><span>(</span><span style="color:#d08770;">10</span><span>) unsigned NOT </span><span style="color:#d08770;">NULL</span><span> AUTO_INCREMENT,
</span><span> `</span><span style="color:#a3be8c;">title</span><span>` </span><span style="color:#b48ead;">varchar</span><span>(</span><span style="color:#d08770;">255</span><span>) NOT </span><span style="color:#d08770;">NULL</span><span>,
</span><span> `</span><span style="color:#a3be8c;">content</span><span>` </span><span style="color:#b48ead;">text </span><span>NOT </span><span style="color:#d08770;">NULL</span><span>,
</span><span> `</span><span style="color:#a3be8c;">created_at</span><span>` </span><span style="color:#b48ead;">datetime </span><span>NOT </span><span style="color:#d08770;">NULL</span><span>,
</span><span> `</span><span style="color:#a3be8c;">updated_at</span><span>` </span><span style="color:#b48ead;">timestamp </span><span>NOT </span><span style="color:#d08770;">NULL </span><span style="color:#b48ead;">DEFAULT </span><span style="color:#96b5b4;">CURRENT_TIMESTAMP</span><span>,
</span><span> </span><span style="color:#b48ead;">PRIMARY KEY</span><span> (`</span><span style="color:#a3be8c;">id</span><span>`)
</span><span>) ENGINE=InnoDB </span><span style="color:#b48ead;">DEFAULT</span><span> CHARSET=utf8 AUTO_INCREMENT=</span><span style="color:#d08770;">1</span><span> ;
</span></code></pre>
<p>A simple table with an unique id integer, title string, content text, created_at datetime and updated_at timestamp.</p>
<p>Next for the routes. Enter the following in <code>app/routes.php</code>:</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span style="color:#65737e;">/* new */
</span><span>$</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">get</span><span>( '</span><span style="color:#a3be8c;">/posts/new</span><span>', </span><span style="color:#b48ead;">function </span><span>() </span><span style="color:#b48ead;">use </span><span>( $</span><span style="color:#bf616a;">app</span><span>, $</span><span style="color:#bf616a;">data </span><span>) {
</span><span> $</span><span style="color:#bf616a;">data</span><span>['</span><span style="color:#a3be8c;">post</span><span>'] = </span><span style="color:#b48ead;">new </span><span style="color:#ebcb8b;">Post</span><span>;
</span><span> $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">render</span><span>( '</span><span style="color:#a3be8c;">posts_new.twig</span><span>', $</span><span style="color:#bf616a;">data </span><span>);
</span><span>})-></span><span style="color:#bf616a;">name</span><span>( '</span><span style="color:#a3be8c;">posts_new</span><span>' );
</span><span>
</span><span style="color:#65737e;">/* create */
</span><span>$</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">post</span><span>( '</span><span style="color:#a3be8c;">/posts</span><span>', </span><span style="color:#b48ead;">function </span><span>() </span><span style="color:#b48ead;">use </span><span>( $</span><span style="color:#bf616a;">app</span><span>, $</span><span style="color:#bf616a;">data </span><span>) {
</span><span> $</span><span style="color:#bf616a;">post </span><span>= </span><span style="color:#b48ead;">new </span><span style="color:#ebcb8b;">Post</span><span>;
</span><span> $</span><span style="color:#bf616a;">post</span><span>-></span><span style="color:#bf616a;">title </span><span>= $</span><span style="color:#bf616a;">_POST</span><span>['</span><span style="color:#a3be8c;">title</span><span>'];
</span><span> $</span><span style="color:#bf616a;">post</span><span>-></span><span style="color:#bf616a;">content </span><span>= $</span><span style="color:#bf616a;">_POST</span><span>['</span><span style="color:#a3be8c;">content</span><span>'];
</span><span> </span><span style="color:#b48ead;">if</span><span>( $</span><span style="color:#bf616a;">post</span><span>-></span><span style="color:#bf616a;">validate</span><span>( </span><span style="color:#96b5b4;">array</span><span>(
</span><span> '</span><span style="color:#a3be8c;">title</span><span>' => $</span><span style="color:#bf616a;">_POST</span><span>['</span><span style="color:#a3be8c;">title</span><span>'],
</span><span> '</span><span style="color:#a3be8c;">content</span><span>' => $</span><span style="color:#bf616a;">_POST</span><span>['</span><span style="color:#a3be8c;">content</span><span>']
</span><span> )))
</span><span> {
</span><span> $</span><span style="color:#bf616a;">post</span><span>-></span><span style="color:#bf616a;">save</span><span>();
</span><span> $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">flash</span><span>( '</span><span style="color:#a3be8c;">notice</span><span>', "</span><span style="color:#a3be8c;">you're post has been created</span><span>" );
</span><span> $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">redirect</span><span>( $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">urlFor</span><span>( '</span><span style="color:#a3be8c;">posts_index</span><span>' ) );
</span><span> }
</span><span> </span><span style="color:#b48ead;">else
</span><span> {
</span><span> $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">flash</span><span>( '</span><span style="color:#a3be8c;">error</span><span>', '</span><span style="color:#a3be8c;">your post was not valid</span><span>' );
</span><span> $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">flash</span><span>( '</span><span style="color:#a3be8c;">errors</span><span>', $</span><span style="color:#bf616a;">post</span><span>-></span><span style="color:#bf616a;">errors</span><span>() );
</span><span> $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">redirect</span><span>( $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">urlFor</span><span>( '</span><span style="color:#a3be8c;">posts_new</span><span>' ) );
</span><span> }
</span><span>})-></span><span style="color:#bf616a;">name</span><span>( '</span><span style="color:#a3be8c;">posts_create</span><span>' );
</span><span>
</span><span style="color:#65737e;">/* view all */
</span><span>$</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">get</span><span>( '</span><span style="color:#a3be8c;">/posts</span><span>', </span><span style="color:#b48ead;">function </span><span>() </span><span style="color:#b48ead;">use </span><span>( $</span><span style="color:#bf616a;">app</span><span>, $</span><span style="color:#bf616a;">data </span><span>) {
</span><span> $</span><span style="color:#bf616a;">data</span><span>['</span><span style="color:#a3be8c;">posts</span><span>'] = </span><span style="color:#ebcb8b;">Post</span><span>::</span><span style="color:#bf616a;">all</span><span>();
</span><span> $</span><span style="color:#bf616a;">app</span><span>-></span><span style="color:#bf616a;">render</span><span>( '</span><span style="color:#a3be8c;">posts.twig</span><span>', $</span><span style="color:#bf616a;">data </span><span>);
</span><span>})-></span><span style="color:#bf616a;">name</span><span>( '</span><span style="color:#a3be8c;">posts_index</span><span>' );
</span></code></pre>
<p>Above there are three routes. GET <code>/posts</code> to view all posts, GET <code>/posts/new</code> to enter a new post and POST <code>/posts</code> to create the post. On lines 9 and 31 you can see calls to a class <code>Post</code> which we will create shortly in our models directory. On line 12 you can see that you can use <code>$posts->validate()</code> to verify entered input.</p>
<p>Here's the model along with validation located in<code>app/models/Post.php</code></p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;"><?php
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Post </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">Illuminate\Database\Eloquent\Model
</span><span style="color:#eff1f5;">{
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">protected </span><span>$</span><span style="color:#bf616a;">table </span><span>= '</span><span style="color:#a3be8c;">posts</span><span>'</span><span style="color:#eff1f5;">;
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">private </span><span>$</span><span style="color:#bf616a;">errors</span><span style="color:#eff1f5;">;
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">private </span><span>$</span><span style="color:#bf616a;">rules </span><span>= </span><span style="color:#96b5b4;">array</span><span style="color:#eff1f5;">(
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">title</span><span>' => '</span><span style="color:#a3be8c;">required|between:4,16</span><span>'</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">content</span><span>' => '</span><span style="color:#a3be8c;">required|min:3</span><span>'
</span><span style="color:#eff1f5;"> );
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">private </span><span>$</span><span style="color:#bf616a;">messages </span><span>= </span><span style="color:#96b5b4;">array</span><span style="color:#eff1f5;">(
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">required</span><span>' => '</span><span style="color:#a3be8c;">Your :attribute is required.</span><span>'</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">min</span><span>' => '</span><span style="color:#a3be8c;">Your :attribute must be at least :min characters long.</span><span>'</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">max</span><span>' => '</span><span style="color:#a3be8c;">Your :attribute must be a maximum of :max characters long.</span><span>'</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">between</span><span>' => '</span><span style="color:#a3be8c;">Your :attribute must be between :min - :max characters long.</span><span>'</span><span style="color:#eff1f5;">,
</span><span style="color:#eff1f5;"> </span><span>'</span><span style="color:#a3be8c;">email</span><span>' => '</span><span style="color:#a3be8c;">Your :attribute must be a valid email address</span><span>'
</span><span style="color:#eff1f5;"> );
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">validate</span><span style="color:#eff1f5;">(</span><span>$</span><span style="color:#bf616a;">params</span><span style="color:#eff1f5;">)
</span><span style="color:#eff1f5;"> {
</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">validator </span><span>= </span><span style="color:#ebcb8b;">Validator</span><span style="color:#eff1f5;">::</span><span style="color:#bf616a;">make</span><span style="color:#eff1f5;">( </span><span>$</span><span style="color:#bf616a;">params</span><span style="color:#eff1f5;">, </span><span>$</span><span style="color:#bf616a;">this</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">rules</span><span style="color:#eff1f5;">, </span><span>$</span><span style="color:#bf616a;">this</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">messages </span><span style="color:#eff1f5;">);
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">if</span><span style="color:#eff1f5;">( </span><span>$</span><span style="color:#bf616a;">validator</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">fails</span><span style="color:#eff1f5;">() )
</span><span style="color:#eff1f5;"> {
</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">this</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">errors </span><span>= $</span><span style="color:#bf616a;">validator</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">errors</span><span style="color:#eff1f5;">()-></span><span style="color:#bf616a;">all</span><span style="color:#eff1f5;">();
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">return </span><span style="color:#d08770;">false</span><span style="color:#eff1f5;">;
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">return </span><span style="color:#d08770;">true</span><span style="color:#eff1f5;">;
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;">
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">errors</span><span style="color:#eff1f5;">()
</span><span style="color:#eff1f5;"> {
</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">return </span><span>$</span><span style="color:#bf616a;">this</span><span style="color:#eff1f5;">-></span><span style="color:#bf616a;">errors</span><span style="color:#eff1f5;">;
</span><span style="color:#eff1f5;"> }
</span><span style="color:#eff1f5;">}
</span></code></pre>
<p><em>Validation is entirely optional - If you do not require validation just create the class and extend <code>Illuminate\Database\Eloquent\Model</code></em></p>
<p>At this point we just need to create our views - but before that you should run <code>composer update</code> so that your new model can be automatically loaded.</p>
<p>All templates are kept in <code>app/views</code></p>
<p>posts.twig</p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>{% extends 'layout.twig' %}
</span><span>
</span><span>{% block content %}
</span><span> <</span><span style="color:#bf616a;">ul</span><span>>
</span><span> {% for post in posts %}
</span><span> <</span><span style="color:#bf616a;">li</span><span>>{{ post.title }}</</span><span style="color:#bf616a;">li</span><span>>
</span><span> {% else %}
</span><span> There are no posts here :(
</span><span> {% endfor %}
</span><span> </</span><span style="color:#bf616a;">ul</span><span>>
</span><span> <</span><span style="color:#bf616a;">a </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">{{ urlFor('posts_new') }}</span><span>">Create Post</</span><span style="color:#bf616a;">a</span><span>>
</span><span>{% endblock %}
</span></code></pre>
<p>posts_new.twig</p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>{% extends 'layout.twig' %}
</span><span>
</span><span>{% block content %}
</span><span> <</span><span style="color:#bf616a;">form </span><span style="color:#d08770;">action</span><span>="</span><span style="color:#a3be8c;">{{ urlFor('posts_create') }}</span><span>" </span><span style="color:#d08770;">method</span><span>="</span><span style="color:#a3be8c;">post</span><span>">
</span><span> {% if flash.error %}
</span><span> <</span><span style="color:#bf616a;">p</span><span>>{{ flash.error }}</</span><span style="color:#bf616a;">p</span><span>>
</span><span> {% if flash.errors %}
</span><span> <</span><span style="color:#bf616a;">ul</span><span>>
</span><span> {% for error in flash.errors %}
</span><span> <</span><span style="color:#bf616a;">li</span><span>>{{ error }}</</span><span style="color:#bf616a;">li</span><span>>
</span><span> {% endfor %}
</span><span> </</span><span style="color:#bf616a;">ul</span><span>>
</span><span> {% endif %}
</span><span> {% endif %}
</span><span> <</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>="</span><span style="color:#a3be8c;">text</span><span>" </span><span style="color:#d08770;">name</span><span>="</span><span style="color:#a3be8c;">title</span><span>" </span><span style="color:#d08770;">placeholder</span><span>="</span><span style="color:#a3be8c;">Title</span><span>">
</span><span> <</span><span style="color:#bf616a;">br</span><span>>
</span><span> <</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>="</span><span style="color:#a3be8c;">text</span><span>" </span><span style="color:#d08770;">name</span><span>="</span><span style="color:#a3be8c;">content</span><span>" </span><span style="color:#d08770;">placeholder</span><span>="</span><span style="color:#a3be8c;">Content</span><span>">
</span><span><</span><span style="color:#bf616a;">br</span><span>>
</span><span> <</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>="</span><span style="color:#a3be8c;">submit</span><span>" </span><span style="color:#d08770;">value</span><span>="</span><span style="color:#a3be8c;">Create</span><span>">
</span><span> </</span><span style="color:#bf616a;">form</span><span>>
</span><span>{% endblock %}
</span></code></pre>
<p>layout.twig (Put your layout in here as usual)</p>
<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>{% block content %}
</span><span>{% endblock %}
</span></code></pre>
<p>As you can see, Slim functions are available with <code>urlFor()</code> being called on line 11 in <code>posts.twig</code> and line 4 in <code>posts_new.twig</code>. You can also use Slim's flash functionality to send messages to redirections. At this point everything should work!</p>
<h3 id="custom-configurations">Custom Configurations</h3>
<p>Perhaps you don't want your templates to be in a view folder, you want to disable twig caching or change error reporting for different environments?</p>
<p>99% of the magic inside of <code>app/application.php</code>. This is where Slim is instantiated along with Twig and Eloquent. Make sure that you take a look so you can perfect your own set up!</p>
<h3 id="resources">Resources</h3>
<p>While putting this together I came across some links which helped me out a lot. Here are as many as I can remember:</p>
<ul>
<li><a href="http://www.slimframework.com/news/slim-and-laravel-eloquent-orm">Slim and Laravel Eloquent ORM</a></li>
<li><a href="https://github.com/codeguy/Slim-Extras/pull/58">Fix Class 'Twig_Extensions_Slim' not found</a></li>
<li><a href="http://www.12devsofxmas.co.uk/post/2012-12-29-day-4-mixing-and-matching-php-components-with-composer">Mixing and Matching PHP Components with Composer</a></li>
<li><a href="https://github.com/codeguy/Slim-Views">Slim Views</a></li>
</ul>