<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Chris Gmyr]]></title><description><![CDATA[Husband, dad, &amp; grilling aficionado. Loves Laravel &amp; coffee. Staff Engineer @ Rula, and TrianglePHP Co-organizer]]></description><link>https://chrisgmyr.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 02 Jan 2026 13:13:39 GMT</lastBuildDate><atom:link href="https://chrisgmyr.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a YNAB CLI in Laravel]]></title><description><![CDATA[I've been using You Need a Budget (YNAB) since 2019 and love it. I'm in the budget daily, entering transactions, tinkering, and ensuring my family is on track with our financial goals and current priorities. However, as the transactions and payees pi...]]></description><link>https://chrisgmyr.dev/building-a-ynab-cli-in-laravel</link><guid isPermaLink="true">https://chrisgmyr.dev/building-a-ynab-cli-in-laravel</guid><category><![CDATA[ynab]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[budget]]></category><category><![CDATA[cli]]></category><category><![CDATA[TUI]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Fri, 31 Jan 2025 17:49:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XU1L22IUKnc/upload/c93718c900973473bc6e416213c6e537.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been using You Need a Budget (YNAB) since 2019 and love it. I'm in the budget daily, entering transactions, tinkering, and ensuring my family is on track with our financial goals and current priorities. However, as the transactions and payees pile up over the years, the web UI has become slower and slower. It's not <em>that</em> bad, but when I only want to enter a simple transaction or check on the current amount for a category I don't always want to wait 20-30 seconds for the entire web UI to load.</p>
<p>I used to use a fantastic Chrome extension called <a target="_blank" href="https://sproutforynab.danielcabuslay.com/">Sprout</a> for entering transactions, but unfortunately the developer took the extension off the Chrome store and stopped development. I wanted something that could be equally as fast and easy to use.</p>
<p>Over this blog series, we'll utilize the PHP Laravel framework and its built-in tools to build a robust CLI application and interface with YNAB's API.</p>
<p>The CLI functionality will not replace the excellent web UI but only introduce faster ways to alleviate everyday tasks to go about our day. It's a perfect fit since I'm usually in my terminal during the day.</p>
<p>Before we begin, if you aren't a YNAB user, please <a target="_blank" href="https://ynab.com/referral/?ref=PWdXkxyHrHqoKZAw&amp;utm_source=customer_referral">sign up</a>. Using my referral link will give you a 34 day trial and if you decide to purchase a subscription, YNAB will give us both a free month!</p>
<h2 id="heading-set-up-laravel">Set up Laravel</h2>
<p>To get started, install Laravel from the <a target="_blank" href="https://laravel.com/docs/11.x#creating-a-laravel-project">docs</a>. I usually use the Laravel installer, but do whatever works best for you. At the time of writing, this will install a new Laravel 11 application.</p>
<pre><code class="lang-plaintext">laravel new ynab
cd ynab
</code></pre>
<p>Because we want an easy to use interface within the CLI, we'll also want to install <a target="_blank" href="https://laravel.com/docs/11.x/prompts">Laravel Prompts</a>.</p>
<pre><code class="lang-plaintext">composer require laravel/prompts
</code></pre>
<p>Next, we'll want to add config values for our YNAB API key and the default budget ID that we'll want to utilize. Right now, I'm only setting this up as a single budget, but in the future we could make this more configurable, or you can do this on your own in your own application.</p>
<p>Within <code>.env</code> and <code>.env.example</code>, add</p>
<pre><code class="lang-plaintext">YNAB_TOKEN=
YNAB_BUDGET_ID=
</code></pre>
<p>Within <code>config/services.php</code>, add</p>
<pre><code class="lang-php">    <span class="hljs-string">'ynab'</span> =&gt; [
        <span class="hljs-string">'token'</span> =&gt; env(<span class="hljs-string">'YNAB_TOKEN'</span>),
        <span class="hljs-string">'budget_id'</span> =&gt; env(<span class="hljs-string">'YNAB_BUDGET_ID'</span>),
    ],
</code></pre>
<p>To get a YNAB API key, go into your <code>Account Settings</code>, scroll toward the bottom and within the <code>Developer Settings</code> click the <code>Developer Settings</code> button. On the next page, under <code>Personal Access Tokens</code>, click the <code>New Token</code> button. This will reveal your API token that you can paste into your <code>.env</code> file.</p>
<p>The budget id is the UUID in the URL when viewing your budget in the YNAB web UI, so you can copy/paste if from there, or get it from the API (<code>/budgets</code>).</p>
<h2 id="heading-create-transaction-command">Create Transaction Command</h2>
<p>Next, we'll want to set up our initial artisan command, so we'll run <code>php artisan make:command CreateTransaction</code>, which will create <code>app/Console/Commands/CreateTransaction.php</code>. Then, we'll stub out the initial command class.</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateTransaction</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Command</span>
</span>{
    <span class="hljs-keyword">protected</span> $signature = <span class="hljs-string">'ynab:transaction'</span>;
    <span class="hljs-keyword">protected</span> $description = <span class="hljs-string">'Creates a new transaction in YNAB'</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params"></span>): <span class="hljs-title">int</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">self</span>::SUCCESS;
    }
</code></pre>
<p>Before we get too far, let's set up a shared trait for the HTTP client to be shared across all commands, since we'll be creating more commands in the next blog posts. Create <code>app/Console/Commands/Concerns/Ynab.php</code> with the following code</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-keyword">declare</span>(strict_types=<span class="hljs-number">1</span>);

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Commands</span>\<span class="hljs-title">Concerns</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Client</span>\<span class="hljs-title">PendingRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Http</span>;

<span class="hljs-keyword">trait</span> Ynab
{
    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getClient</span>(<span class="hljs-params"></span>): <span class="hljs-title">PendingRequest</span>
    </span>{
        <span class="hljs-keyword">return</span> Http::baseUrl(<span class="hljs-string">'https://api.ynab.com/v1/'</span>)
            -&gt;acceptJson()
            -&gt;asJson()
            -&gt;throw()
            -&gt;withToken(config(<span class="hljs-string">'services.ynab.token'</span>));
    }
}
</code></pre>
<p>This helper method will allow us to remove the boilerplate of setting up the YNAB client and set up JSON handling, throw an exception if there's an API error, and use the token from the config file for authentication.</p>
<p>Then, we'll include that in the transaction command class</p>
<pre><code class="lang-diff">class CreateTransaction extends Command
{
<span class="hljs-addition">+    use Concerns\Ynab;</span>
<span class="hljs-addition">+</span>
    protected $signature = 'ynab:transaction';
    protected $description = 'Creates a new transaction in YNAB';
</code></pre>
<p>Similar to the Sprout UI, we'll want to build a CLI to handle:</p>
<ul>
<li><p>Amount</p>
</li>
<li><p>Account</p>
</li>
<li><p>Payee</p>
</li>
<li><p>Category</p>
</li>
<li><p>Optional memo</p>
</li>
<li><p>Optional flag</p>
</li>
<li><p>Optional cleared/uncleared</p>
</li>
</ul>
<p>For now, we'll skip date (we'll assume today's date), approval (default to true), split transactions, and other options found in the Sprout and YNAB UIs. For reference, here is the Sprout UI.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738297800917/3fa6cf37-a6f8-4d23-bc03-744297804e5d.png" alt="Sprout extension UI" class="image--center mx-auto" /></p>
<h2 id="heading-gathering-budget-data">Gathering Budget Data</h2>
<p>Before we start building our CLI UI, we'll need to get initial budget data from YNAB. You can find the API docs <a target="_blank" href="https://api.ynab.com/">here</a>. For now, we'll want to gather</p>
<ul>
<li><p>Accounts</p>
</li>
<li><p>Payees</p>
</li>
<li><p>Categories</p>
</li>
</ul>
<p>So, we'll stub those out in our command</p>
<pre><code class="lang-diff">class CreateTransaction extends Command
{
    use Concerns\Ynab;

    protected $signature = 'ynab:transaction';
    protected $description = 'Creates a new transaction in YNAB';

    public function handle(): int
    {
<span class="hljs-addition">+        $accounts = $this-&gt;getAccounts();</span>
<span class="hljs-addition">+        $payees = $this-&gt;getPayees();</span>
<span class="hljs-addition">+        $categories = $this-&gt;getCategories();</span>
<span class="hljs-addition">+</span>
        return self::SUCCESS;
    }
</code></pre>
<h3 id="heading-getting-budget-accounts">Getting Budget Accounts</h3>
<pre><code class="lang-php">    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAccounts</span>(<span class="hljs-params"></span>): <span class="hljs-title">Collection</span>
    </span>{
        $response = <span class="hljs-keyword">$this</span>-&gt;getClient()-&gt;get(<span class="hljs-string">'budgets/'</span> . config(<span class="hljs-string">'services.ynab.budget_id'</span>) . <span class="hljs-string">'/accounts'</span>);

        <span class="hljs-keyword">return</span> collect($response-&gt;json(<span class="hljs-string">'data.accounts'</span>))
            -&gt;filter(<span class="hljs-function"><span class="hljs-keyword">fn</span>(<span class="hljs-params">$account</span>) =&gt; $<span class="hljs-title">account</span>['<span class="hljs-title">on_budget</span>'] &amp;&amp; !$<span class="hljs-title">account</span>['<span class="hljs-title">closed</span>'])
            -&gt;<span class="hljs-title">mapWithKeys</span>(<span class="hljs-params">fn(<span class="hljs-params">$account</span>) =&gt; [$account[<span class="hljs-string">'id'</span>] =&gt; $account[<span class="hljs-string">'name'</span>]]</span>)</span>;
    }
</code></pre>
<p>In this step, we're</p>
<ol>
<li><p>Getting the raw response from the <code>/accounts</code> endpoint from the API</p>
</li>
<li><p>Grabbing the data from the response under <code>data.accounts</code></p>
</li>
<li><p>Filtering for only accounts "on budget" and not closed. On budget means that the money is used for specific budgeting purposes. Alternatively, there are "tracking" accounts that don't impact your budget positively or negatively.</p>
</li>
<li><p>Creating a new collection of account IDs matching their name</p>
</li>
</ol>
<h3 id="heading-getting-budget-payees">Getting Budget Payees</h3>
<pre><code class="lang-php">    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPayees</span>(<span class="hljs-params"></span>): <span class="hljs-title">Collection</span>
    </span>{
        $response = <span class="hljs-keyword">$this</span>-&gt;getClient()-&gt;get(<span class="hljs-string">'budgets/'</span> . config(<span class="hljs-string">'services.ynab.budget_id'</span>) . <span class="hljs-string">'/payees'</span>);

        <span class="hljs-keyword">return</span> collect($response-&gt;json(<span class="hljs-string">'data.payees'</span>))
            -&gt;reject(<span class="hljs-function"><span class="hljs-keyword">fn</span>(<span class="hljs-params">$payee</span>) =&gt; $<span class="hljs-title">payee</span>['<span class="hljs-title">deleted</span>'])
            -&gt;<span class="hljs-title">mapWithKeys</span>(<span class="hljs-params">fn(<span class="hljs-params">$payee</span>) =&gt; [$payee[<span class="hljs-string">'id'</span>] =&gt; $payee[<span class="hljs-string">'name'</span>]]</span>)</span>;
    }
</code></pre>
<p>In this step, we're</p>
<ol>
<li><p>Getting the raw response from the <code>/payees</code> endpoint from the API</p>
</li>
<li><p>Grabbing the data from the response under <code>data.payees</code></p>
</li>
<li><p>Removing any deleted payees</p>
</li>
<li><p>Creating a new collection of payee IDs matching their name</p>
</li>
</ol>
<h3 id="heading-getting-budget-categories">Getting Budget Categories</h3>
<p>This step is a little more involved because the <code>/categories</code> endpoint has top-level Category Groups, then includes the categories data within each group. You can see the schema for <code>CategoryGroupWithCategories</code> <a target="_blank" href="https://api.ynab.com/v1">here</a>.</p>
<pre><code class="lang-php">    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCategories</span>(<span class="hljs-params"></span>): <span class="hljs-title">Collection</span>
    </span>{
        $response = <span class="hljs-keyword">$this</span>-&gt;getClient()-&gt;get(<span class="hljs-string">'budgets/'</span> . config(<span class="hljs-string">'services.ynab.budget_id'</span>) . <span class="hljs-string">'/categories'</span>);

        <span class="hljs-keyword">return</span> collect($response-&gt;json(<span class="hljs-string">'data.category_groups'</span>))
            -&gt;reject(<span class="hljs-function"><span class="hljs-keyword">fn</span>(<span class="hljs-params">$categoryGroup</span>) =&gt; $<span class="hljs-title">categoryGroup</span>['<span class="hljs-title">hidden</span>'] || $<span class="hljs-title">categoryGroup</span>['<span class="hljs-title">deleted</span>'])
            -&gt;<span class="hljs-title">pluck</span>(<span class="hljs-params"><span class="hljs-string">'categories'</span></span>)
            -&gt;<span class="hljs-title">flatten</span>(<span class="hljs-params"><span class="hljs-number">1</span></span>)
            -&gt;<span class="hljs-title">reject</span>(<span class="hljs-params">fn(<span class="hljs-params">$category</span>) =&gt; $category[<span class="hljs-string">'hidden'</span>] || $category[<span class="hljs-string">'deleted'</span>] || $category[<span class="hljs-string">'category_group_name'</span>] === <span class="hljs-string">'Credit Card Payments'</span> || $category[<span class="hljs-string">'category_group_name'</span>] === <span class="hljs-string">'Internal Master Category'</span></span>)
            -&gt;<span class="hljs-title">mapWithKeys</span>(<span class="hljs-params">fn(<span class="hljs-params">$category</span>) =&gt; [$category[<span class="hljs-string">'id'</span>] =&gt; $category[<span class="hljs-string">'category_group_name'</span>].<span class="hljs-string">': '</span>.$category[<span class="hljs-string">'name'</span>]]</span>)</span>;
    }
</code></pre>
<p>In this step, we're</p>
<ol>
<li><p>Getting the raw response from the <code>/categories</code> endpoint from the API</p>
</li>
<li><p>Grabbing the data from the response under <code>data.category_groups</code></p>
</li>
<li><p>Removing hidden and deleted category groups</p>
</li>
<li><p>Grabbing the <code>categories</code> array from each group</p>
</li>
<li><p>Flattening the collection so all of the categories are in the same root of the collection</p>
</li>
<li><p>Removing any categories that are hidden, deleted, or match specific YNAB-generate categories we don't need</p>
</li>
<li><p>Creating a new collection of category IDs matching the category group name with the category name</p>
</li>
</ol>
<p>Whew, we did it! Now, we'll move on to the Laravel Prompts UI.</p>
<h2 id="heading-leveraging-laravel-prompts-for-the-cli-ui">Leveraging Laravel Prompts for the CLI UI</h2>
<p>Prompts has numerous options for CLI inputs. These can either be one-off or chained together into a form. A form enables us to have multiple questions and inputs while collecting the responses to be used later. Since we'll have a number of questions to ask, we'll be using a form.</p>
<pre><code class="lang-php">        $responses = form()
            -&gt;text(<span class="hljs-string">'Amount'</span>, required: <span class="hljs-literal">true</span>, name: <span class="hljs-string">'amount'</span>)
            -&gt;select(<span class="hljs-string">'Account'</span>, options: $accounts, required: <span class="hljs-literal">true</span>, name: <span class="hljs-string">'account'</span>)
            -&gt;search(
                label: <span class="hljs-string">'Payee'</span>,
                options: <span class="hljs-function"><span class="hljs-keyword">fn</span> (<span class="hljs-params"><span class="hljs-keyword">string</span> $value</span>) =&gt; <span class="hljs-title">strlen</span>(<span class="hljs-params">$value</span>) &gt; 0
                    ? $<span class="hljs-title">payees</span>-&gt;<span class="hljs-title">filter</span>(<span class="hljs-params">fn (<span class="hljs-params">$payee</span>) =&gt; Str::contains(<span class="hljs-params">$payee, $value, <span class="hljs-literal">true</span></span>)</span>)-&gt;<span class="hljs-title">all</span>(<span class="hljs-params"></span>)
                    : [],
                <span class="hljs-title">name</span>: '<span class="hljs-title">payee</span>'
            )
            -&gt;<span class="hljs-title">search</span>(<span class="hljs-params">
                label: <span class="hljs-string">'Category'</span>,
                options: fn (<span class="hljs-params"><span class="hljs-keyword">string</span> $value</span>) =&gt; strlen(<span class="hljs-params">$value</span>) &gt; <span class="hljs-number">0</span>
                    ? $categories-&gt;filter(<span class="hljs-params">fn (<span class="hljs-params">$category</span>) =&gt; Str::contains(<span class="hljs-params">$category, $value, <span class="hljs-literal">true</span></span>)</span>)-&gt;all(<span class="hljs-params"></span>)
                    : [],
                name: <span class="hljs-string">'category'</span>
            </span>)
            -&gt;<span class="hljs-title">text</span>(<span class="hljs-params"><span class="hljs-string">'Memo (optional)'</span>, name: <span class="hljs-string">'memo'</span></span>)
            -&gt;<span class="hljs-title">select</span>(<span class="hljs-params"><span class="hljs-string">'Flag color (optional)'</span>, options: [<span class="hljs-string">'none'</span>, <span class="hljs-string">'red'</span>, <span class="hljs-string">'orange'</span>, <span class="hljs-string">'yellow'</span>, <span class="hljs-string">'green'</span>, <span class="hljs-string">'blue'</span>, <span class="hljs-string">'purple'</span>], name: <span class="hljs-string">'flag_color'</span></span>)
            -&gt;<span class="hljs-title">select</span>(<span class="hljs-params"><span class="hljs-string">'Cleared'</span>, options: [<span class="hljs-string">'cleared'</span>, <span class="hljs-string">'uncleared'</span>], <span class="hljs-keyword">default</span>: <span class="hljs-string">'uncleared'</span>, name: <span class="hljs-string">'cleared'</span></span>)
            -&gt;<span class="hljs-title">submit</span>(<span class="hljs-params"></span>)</span>;
</code></pre>
<p>There's a lot to unpack here, but it should be straight forward</p>
<ol>
<li><p>We'll ask for the amount of the transaction - either positive (income) or negative (expense)</p>
</li>
<li><p>Select the account from a <code>select</code> prompt loaded with our accounts from the API</p>
</li>
<li><p>A search box for the payee loaded from the API. As new characters are added, the search suggest auto-updates and allows you to arrow up/down to select the payee. The <code>options</code> function searches and returns the values the match the input given.</p>
</li>
<li><p>Similar to the payees, the categories work the same way with a search suggest</p>
</li>
<li><p>Memo is a simple text field and also optional</p>
</li>
<li><p>Flag color is also optional using a select, like accounts. YNAB allows you to use their default flags, or change them in the UI. I only use these occasionally.</p>
</li>
<li><p>Cleared or uncleared transaction using another select. If I already know the transaction has cleared the bank/card, then I'll change to <code>cleared</code> but otherwise most transactions are new and haven't hit the account yet, so we'll default to <code>uncleared</code></p>
</li>
<li><p>Finally, submit the responses</p>
</li>
</ol>
<h2 id="heading-creating-the-new-transaction-via-the-api">Creating the new transaction via the API</h2>
<p>The Laravel Prompts <code>form</code> will return an array of <code>$responses</code> that will contain the key/value from what's provided in each input's name. We'll use the <code>/transactions</code> endpoint to combine our data and use some sensible defaults for other transaction-related data points.</p>
<pre><code class="lang-php">        $response = <span class="hljs-keyword">$this</span>-&gt;getClient()-&gt;post(<span class="hljs-string">'budgets/'</span> . config(<span class="hljs-string">'services.ynab.budget_id'</span>) . <span class="hljs-string">'/transactions'</span>, [
            <span class="hljs-string">'transaction'</span> =&gt; [
                <span class="hljs-string">'account_id'</span> =&gt; $responses[<span class="hljs-string">'account'</span>],
                <span class="hljs-string">'payee_id'</span> =&gt; $responses[<span class="hljs-string">'payee'</span>],
                <span class="hljs-string">'category_id'</span> =&gt; $responses[<span class="hljs-string">'category'</span>],
                <span class="hljs-string">'amount'</span> =&gt; $responses[<span class="hljs-string">'amount'</span>] * <span class="hljs-number">1000</span>,
                <span class="hljs-string">'memo'</span> =&gt; $responses[<span class="hljs-string">'memo'</span>],
                <span class="hljs-string">'flag_color'</span> =&gt; $responses[<span class="hljs-string">'flag_color'</span>] === <span class="hljs-string">'none'</span> ? <span class="hljs-literal">null</span> : $responses[<span class="hljs-string">'flag_color'</span>],
                <span class="hljs-string">'cleared'</span> =&gt; $responses[<span class="hljs-string">'cleared'</span>],
                <span class="hljs-string">'date'</span> =&gt; now()-&gt;toDateString(),
                <span class="hljs-string">'approved'</span> =&gt; <span class="hljs-literal">true</span>,
            ],
        ]);
</code></pre>
<ol>
<li><p>The account will be the account's uuid from YNAB</p>
</li>
<li><p>The payee will be the payee's uuid</p>
</li>
<li><p>The category will be the category's uuid</p>
</li>
<li><p>The amount (positive or negative) needs to be converted to what YNAB calls <code>milliunits</code> or 3 decimal places, so we'll multiply our amount by 1,000 (<a target="_blank" href="https://api.ynab.com/#formats">docs</a>)</p>
</li>
<li><p>Optional memo text</p>
</li>
<li><p>Optional flag color, or converting <code>none</code> to <code>null</code> in case a color isn't specified</p>
</li>
<li><p>Cleared value</p>
</li>
<li><p>Current date - I usually add transactions as they happen, so we'll default to the current date. We might make this configurable in the future.</p>
</li>
<li><p>Approved defaulted to <code>true</code> - YNAB will highlight any new transactions via their auto-import process which you can either approve or delete. However, for the CLI's purposes, I'm entering the transaction manually, I'm approving at the same time.</p>
</li>
</ol>
<h2 id="heading-handling-api-responses">Handling API responses</h2>
<p>At this point, I'm not too worried about responses and everything has been working smoothly so far. We'll expand on this in the future, but again, we'll keep things simple right now.</p>
<pre><code class="lang-php">        <span class="hljs-keyword">if</span> ($response-&gt;successful()) {
            <span class="hljs-keyword">$this</span>-&gt;info(<span class="hljs-string">'Expense created successfully'</span>);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">$this</span>-&gt;error(<span class="hljs-string">'Failed to create expense - '</span> . $response-&gt;json(<span class="hljs-string">'error.detail'</span>));
        }

        <span class="hljs-keyword">return</span> <span class="hljs-built_in">self</span>::SUCCESS;
</code></pre>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>I hope you enjoyed this quick journey through setting up a fresh, new, Laravel application with Prompts and the YNAB API. As stated earlier, I'll be adding more blog posts to this series as I work through other command and refactorings along the way. I'm sure you can already see some areas that could use improvement. Further, the YNAB API offers some interesting options like Delta Requests, Rate Limiting, and better error handling that we can explore later as well.</p>
<h3 id="heading-live-demo">Live Demo</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738345131753/488aa8a6-a76b-46f5-badc-820e8622f2f3.gif" alt="demo CLI transaction" class="image--center mx-auto" /></p>
<h3 id="heading-check-out-the-repo-on-github">Check out the repo on GitHub</h3>
<ul>
<li><p><a target="_blank" href="https://github.com/cmgmyr/laravel-ynab-cli">laravel-ynab-cli</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/cmgmyr/laravel-ynab-cli/pull/1">PR #1 for the CreateTransaction command</a></p>
</li>
</ul>
<h3 id="heading-listen-to-the-podcast-episode">Listen to the podcast episode</h3>
<iframe width="100%" height="180" src="https://share.transistor.fm/e/0ac29d66"></iframe>

<h3 id="heading-subscribe">Subscribe</h3>
<p>Please subscribe to my newsletter, RSS feed, podcast, or the GH repo to follow along for more content!</p>
<h3 id="heading-join-the-ynab-family">Join the YNAB Family</h3>
<p><a target="_blank" href="https://ynab.com/referral/?ref=PWdXkxyHrHqoKZAw&amp;utm_source=customer_referral">Signing up</a> using my referral link will give you a 34 day trial and if you decide to purchase a subscription, YNAB will give us both a free month!</p>
]]></content:encoded></item><item><title><![CDATA[Colocating Tests in Laravel]]></title><description><![CDATA[In a typical Laravel application, tests are housed in the /Tests directory and spread across /Tests/Feature and /Tests/Unit, which mimics the structure within the app/ directory. While this is a great starting point for apps, it quickly breaks down w...]]></description><link>https://chrisgmyr.dev/colocating-tests-in-laravel</link><guid isPermaLink="true">https://chrisgmyr.dev/colocating-tests-in-laravel</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Testing]]></category><category><![CDATA[pestphp]]></category><category><![CDATA[PHPUnit]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Sun, 01 Sep 2024 16:50:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/rPM6qVp_tgk/upload/60ef95f6c16f16d4872bb6e4ea21efa2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a typical Laravel application, tests are housed in the <code>/Tests</code> directory and spread across <code>/Tests/Feature</code> and <code>/Tests/Unit</code>, which mimics the structure within the <code>app/</code> directory. While this is a great starting point for apps, it quickly breaks down within a more extensive application and minimizes developer efficiency and happiness over time.</p>
<p>Let's look at the typical structure with minimal application code and tests.</p>
<pre><code class="lang-plaintext">.
├── app/
│   ├── Http/
│   │   └── Controllers/
│   │       └── MyUserController.php
│   └── Models/
│       └── User.php
└── tests/
    ├── TestCase.php
    ├── Features/
    │   └── Http/
    │       └── Controllers/
    │           └── MyUserControllerTest.php
    └── Unit/
        └── Models/
            └── UserTest.php
</code></pre>
<p>This issue gets exacerbated in larger applications with additional sub-directories. For example,</p>
<pre><code class="lang-plaintext">.
├── app/
│   ├── Http/
│   │   └── Controllers/
│   │       └── Admin/
│   │           └── Users/
│   │               └── MyUserController.php
│   └── Models/
│       └── User.php
└── tests/
    ├── TestCase.php
    ├── Features/
    │   └── Http/
    │       └── Controllers/
    │           └── Admin/
    │               └── Users/
    │                   └── MyUserControllerTest.php
    └── Unit/
        └── Models/
            └── UserTest.php
</code></pre>
<p>This structure further breaks down if you've chosen to break up your application within modules or domains but don't include the tests within the directories. The application and test structures could easily be five or more directories deep.</p>
<p>Discoverability of application files with their test partners is challenging, and it is unclear if a given application file has a partnering test.</p>
<h2 id="heading-the-solution">The solution</h2>
<p>Colocating tests is an ideal structure. The test files are elevated to the same "status" and layer as application files. This makes it very easy to notice, at a glance, whether a file has a partnering test. Further, this simplifies pull requests for reviewers since the two files are next to each other.</p>
<p>The structure of our application now looks like this.</p>
<pre><code class="lang-plaintext">.
├── app/
│   ├── Http/
│   │   └── Controllers/
│   │       ├── MyUserController.php
│   │       └── MyUserControllerTest.php
│   └── Models/
│       ├── User.php
│       └── UserTest.php
└── tests/
    └── TestCase.php
</code></pre>
<p>or</p>
<pre><code class="lang-plaintext">.
├── app/
│   ├── Http/
│   │   └── Controllers/
│   │       └── Admin/
│   │           └── Users/
│   │               ├── MyUserController.php
│   │               └── MyUserControllerTest.php
│   └── Models/
│       ├── User.php
│       └── UserTest.php
└── tests/
    └── TestCase.php
</code></pre>
<p>Now imagine if this app contains hundreds of different directories and files. Can you easily see if <code>MyUserController</code> has a test? Yes! How about the <code>User</code> model? Yes again!</p>
<h2 id="heading-the-mechanics">The mechanics</h2>
<p>Unfortunately, we cannot just move our tests into this new structure. We'll have to make some small adjustments, but they are small and straightforward.</p>
<h3 id="heading-composerjson">composer.json</h3>
<p>Add <code>autoload.exclude-from-classmap</code></p>
<pre><code class="lang-diff">{
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Database\\Factories\\": "database/factories/",
            "Database\\Seeders\\": "database/seeders/"
<span class="hljs-deletion">-        }</span>
<span class="hljs-addition">+        },</span>
<span class="hljs-addition">+        "exclude-from-classmap": [</span>
<span class="hljs-addition">+            "app/**/*Test"</span>
<span class="hljs-addition">+        ]</span>
    }
}
</code></pre>
<h3 id="heading-phpunitxml">phpunit.xml</h3>
<p>Add or adjust <code>source</code> and <code>testsuites</code>.</p>
<pre><code class="lang-diff">    &lt;source&gt;  
        &lt;include&gt;  
            &lt;directory suffix=".php"&gt;./app&lt;/directory&gt;
        &lt;/include&gt;  
<span class="hljs-addition">+        &lt;exclude&gt;  </span>
<span class="hljs-addition">+            &lt;directory suffix="Test.php"&gt;./app&lt;/directory&gt;</span>
<span class="hljs-addition">+        &lt;/exclude&gt;  </span>
    &lt;/source&gt;
    &lt;testsuites&gt;
<span class="hljs-deletion">-        &lt;testsuite name="Unit"&gt;</span>
<span class="hljs-deletion">-            &lt;directory&gt;tests/Unit&lt;/directory&gt;</span>
<span class="hljs-deletion">-        &lt;/testsuite&gt;</span>
<span class="hljs-deletion">-        &lt;testsuite name="Feature"&gt;</span>
<span class="hljs-deletion">-            &lt;directory&gt;tests/Feature&lt;/directory&gt;</span>
<span class="hljs-deletion">-        &lt;/testsuite&gt;</span>
<span class="hljs-addition">+        &lt;testsuite name="Application Test Suite"&gt;</span>
<span class="hljs-addition">+            &lt;directory&gt;./app&lt;/directory&gt;</span>
<span class="hljs-addition">+        &lt;/testsuite&gt;</span>
    &lt;/testsuites&gt;
</code></pre>
<h3 id="heading-testspestphp">tests/Pest.php</h3>
<p>Ensure all tests have the <code>TestCase</code> and other <code>uses</code> classes available.</p>
<pre><code class="lang-diff">uses(  
    Tests\TestCase::class,  
    // Illuminate\Foundation\Testing\RefreshDatabase::class,  
<span class="hljs-deletion">-)-&gt;in('Feature');</span>
<span class="hljs-addition">+)-&gt;in('../');</span>
</code></pre>
<h3 id="heading-appconsolekernelphp-lt-v11">app/Console/Kernel.php (&lt; v11)</h3>
<p>Add or adjust the <code>load()</code> method.</p>
<pre><code class="lang-diff">protected function load($paths)
{
    $paths = Arr::wrap($paths);
    $namespace = $this-&gt;app-&gt;getNamespace();

    foreach ((new Finder)-&gt;in($paths)-&gt;files() as $command) {
        $command = $namespace.str_replace(
            ['/', '.php'],
            ['\\', ''],
            Str::after($command-&gt;getPathname(), realpath(app_path()).DIRECTORY_SEPARATOR)
        );

<span class="hljs-addition">+        $isTestClass = Str::endsWith($command, 'Test');</span>

        if (
<span class="hljs-addition">+            !$isTestClass &amp;&amp;</span>
            is_subclass_of($command, Command::class) &amp;&amp;
            !(new ReflectionClass($command))-&gt;isAbstract()
        ) {
            Artisan::starting(function ($artisan) use ($command) {
                $artisan-&gt;resolve($command);
            });
        }
    }
}
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🐛</div>
<div data-node-type="callout-text">Currently, in Laravel 11, Pest file tests colocated in <code>app/Console/Commands/*</code> isn't supported. To colocate these tests correctly, you'll have to use PHPUnit's class-based syntax.</div>
</div>

<h3 id="heading-external-tooling">External Tooling</h3>
<p>If your application uses a tool like <a target="_blank" href="https://codeclimate.com/quality">Code Climate</a>, you might need to make additional config changes. For example, within the <code>.codeclimate.yml</code>, add <code>**/*Test.php</code> within the <code>exclude_patterns</code>.</p>
<pre><code class="lang-diff">exclude_patterns:
<span class="hljs-addition">+  - '**/*Test.php'</span>
</code></pre>
<h2 id="heading-quality-of-life">Quality of life</h2>
<p>If you like the idea of colocating tests, but don't want the additional mess of looking at test files all the time you can enable <a target="_blank" href="https://www.jetbrains.com/help/phpstorm/file-nesting-dialog.html">File Nesting</a> in PHPStorm, and other JetBrains IDEs. Once you add the <code>.php -&gt; Test.php</code> mapping your files will be collapsed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725560469831/98da68e8-13e5-422d-8737-4b687ca26ee2.png" alt class="image--center mx-auto" /></p>
<p>Thanks to <a target="_blank" href="https://x.com/enzoinnocenzi/status/1831723197537849375">Enzo Innocenzi</a> for bringing that to my attention.</p>
<p>If you use VSCode, please check out <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=liamhammett.temphpest">Liam Hammett</a>'s extension.</p>
<h2 id="heading-wrapping-up">Wrapping up</h2>
<p>Even though colocating your tests within the <code>app/</code> directory in your Laravel application is currently atypical, I highly recommend you try it out. By colocating tests alongside application code, the direct mapping of the two will become stronger, minimizing the cognitive load in your app. You will also have a simplified structure and ease of refactoring if you happen to move your modules or domains within your app or to a new one.</p>
<p>Please comment below or reach out via <a target="_blank" href="https://x.com/cmgmyr">X/Twitter</a> about what you think about colocating tests and if you've implemented it within your application.</p>
<h2 id="heading-listen-to-the-podcast">Listen to the Podcast</h2>
<iframe width="100%" height="180" src="https://share.transistor.fm/e/6dec622a"></iframe>]]></content:encoded></item><item><title><![CDATA[Improve Your Productivity While Utilizing Laravel Mocks]]></title><description><![CDATA[Laravel has numerous test helpers and mocks within the framework, which is fantastic. However, I see other engineers getting caught up on debugging issues when they arise.
The Laravel docs show this as an example
Queue::assertPushed(function (ShipOrd...]]></description><link>https://chrisgmyr.dev/improve-your-productivity-while-utilizing-laravel-mocks</link><guid isPermaLink="true">https://chrisgmyr.dev/improve-your-productivity-while-utilizing-laravel-mocks</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Mocking]]></category><category><![CDATA[PHPUnit]]></category><category><![CDATA[pestphp]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Tue, 26 Sep 2023 13:00:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695686912950/31e332a3-ea9d-4c9e-88ba-77d24b535e38.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Laravel has numerous test helpers and <a target="_blank" href="https://laravel.com/docs/10.x/mocking">mocks</a> within the framework, which is fantastic. However, I see other engineers getting caught up on debugging issues when they arise.</p>
<p>The <a target="_blank" href="https://laravel.com/docs/10.x/mocking#queue-fake">Laravel docs</a> show this as an example</p>
<pre><code class="lang-php">Queue::assertPushed(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">ShipOrder $job</span>) <span class="hljs-title">use</span> (<span class="hljs-params">$order</span>) </span>{
    <span class="hljs-keyword">return</span> $job-&gt;order-&gt;id === $order-&gt;id;
});
</code></pre>
<p>Straightforward, right? We're trying to ensure that the <code>ShipOrder</code> job was pushed to the queue and the order IDs matched.</p>
<p>What happens when the IDs do not match, or something else happens? We get the following error</p>
<pre><code class="lang-bash">Failed asserting that <span class="hljs-literal">false</span> is <span class="hljs-literal">true</span>.
</code></pre>
<p>Which is not that helpful. We know that the job wasn't pushed, but was it an error with the IDs matching, or something else? This issue gets exacerbated when you have multiple conditions that need to pass.</p>
<pre><code class="lang-php">Queue::assertPushed(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">ShipOrder $job</span>) <span class="hljs-title">use</span> (<span class="hljs-params">$order</span>) </span>{
    <span class="hljs-keyword">return</span> $job-&gt;order-&gt;id === $order-&gt;id
        &amp;&amp; $job-&gt;order-&gt;second === $order-&gt;second
        &amp;&amp; $job-&gt;order-&gt;third === $order-&gt;third
        &amp;&amp; $job-&gt;order-&gt;fourth === $order-&gt;fourth;
});
</code></pre>
<p>Now, if <em>any</em> of those conditions fail, then we'll still get the same PHPUnit error message.</p>
<pre><code class="lang-bash">Failed asserting that <span class="hljs-literal">false</span> is <span class="hljs-literal">true</span>.
</code></pre>
<p>There is no way to know which condition failed easily. You can remove one condition at a time, but that's a time-consuming process. There's a better way.</p>
<p>Instead of using chained conditions, we can use assertions for each of the comparisons. Then we can manually return true if all of the conditions are true.</p>
<pre><code class="lang-php">Queue::assertPushed(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">ShipOrder $job</span>) <span class="hljs-title">use</span> (<span class="hljs-params">$order</span>) </span>{
    <span class="hljs-keyword">$this</span>-&gt;assertSame($job-&gt;order-&gt;id, $order-&gt;id);
    <span class="hljs-keyword">$this</span>-&gt;assertSame($job-&gt;order-&gt;second, $order-&gt;second);
    <span class="hljs-keyword">$this</span>-&gt;assertSame($job-&gt;order-&gt;third, $order-&gt;third);
    <span class="hljs-keyword">$this</span>-&gt;assertSame($job-&gt;order-&gt;fourth, $order-&gt;fourth);

    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
});
</code></pre>
<p>Now, if any of these assertions fail, PHPUnit will be able to narrow in on the exact line number that failed - making it quicker to debug and getting you on your way.</p>
<pre><code class="lang-bash">Failed asserting that X is identical to Y

<span class="hljs-comment"># ---</span>

Failed asserting that 456 is identical to 123.
 /tests/PathToFileTest.php:62
</code></pre>
<p>In conclusion, by using assertions for each comparison, you can quickly pinpoint and resolve issues in your code. Leveraging Laravel mocks effectively can significantly improve your productivity as a developer.</p>
<hr />
<p>Don't forget to share this article with your fellow developers to help them optimize their Laravel testing experience!</p>
]]></content:encoded></item><item><title><![CDATA[Tips, Tricks, and Good Practices with Laravel's Eloquent]]></title><description><![CDATA[This is a talk I gave at TrianglePHP on Aug 16, 2018. We'll learn how Eloquent functions on the basic levels and continue through some more well-known methods and some possibly lesser-known ones. Then we'll finish with some more advanced ideas and te...]]></description><link>https://chrisgmyr.dev/tips-tricks-and-good-practices-with-laravels-eloquent</link><guid isPermaLink="true">https://chrisgmyr.dev/tips-tricks-and-good-practices-with-laravels-eloquent</guid><category><![CDATA[Laravel]]></category><category><![CDATA[eloquent]]></category><category><![CDATA[PHP]]></category><category><![CDATA[orm]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Fri, 17 Aug 2018 16:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/PIrOQrqewLE/upload/ac0c312674270ccf5d1b4d8814b8c4ef.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a talk I gave at <a target="_blank" href="https://www.meetup.com/trianglephp/events/zgpswmyxlbvb/">TrianglePHP</a> on Aug 16, 2018. We'll learn how Eloquent functions on the basic levels and continue through some more well-known methods and some possibly lesser-known ones. Then we'll finish with some more advanced ideas and techniques.</p>
<hr />
<iframe class="speakerdeck-iframe" src="https://speakerdeck.com/player/7124aaaa559c4042ac5f5812cf45d239" style="border:0px;background:padding-box padding-box rgba(0, 0, 0, 0.1);margin:0px;padding:0px;border-radius:6px;box-shadow:rgba(0, 0, 0, 0.2) 0px 5px 40px;width:100%;height:auto;aspect-ratio:560 / 315"></iframe>

<hr />
<h2 id="heading-tips-tricks-and-good-practices">Tips, Tricks, and Good Practices</h2>
<h3 id="heading-with">with</h3>
<h2 id="heading-laravels-eloquent">Laravel's Eloquent</h2>
<h3 id="heading-presented-by-chris-gmyr">Presented by Chris Gmyr</h3>
<hr />
<h1 id="heading-what-is-laravel">What is Laravel?</h1>
<p>Laravel is a modern PHP framework that helps you create applications using simple, expressive syntax as well as offers powerful features like an ORM, routing, queues, events, notifications, simple authentication...</p>
<p>...and so much more!</p>
<hr />
<h1 id="heading-what-is-eloquent">What is Eloquent?</h1>
<blockquote>
<p>The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table. Models allow you to query for data in your tables, as well as insert new records into the table.</p>
</blockquote>
<p>https://laravel.com/docs/5.6/eloquent</p>
<hr />
<h1 id="heading-the-basics">The Basics</h1>
<hr />
<h1 id="heading-a-model">A Model</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-comment">// look Ma, no code!</span>
}
</code></pre>
<pre><code class="lang-bash">- id
- title
- created_at
- updated_at
</code></pre>
<pre><code class="lang-php">$post = Post::find(<span class="hljs-number">1</span>);
</code></pre>
<hr />
<h1 id="heading-artisan-goodies">Artisan Goodies</h1>
<pre><code class="lang-bash">php artisan make:model Product
</code></pre>
<hr />
<h1 id="heading-artisan-goodies-1">Artisan Goodies</h1>
<pre><code class="lang-bash">php artisan make:model Product -mcr
</code></pre>
<p><code>-m</code> will create a migration file <code>-c</code> will create a controller <code>-r</code> will indicate that controller should be resourceful</p>
<hr />
<h1 id="heading-cruddy">Cruddy</h1>
<hr />
<h1 id="heading-creating">Creating</h1>
<pre><code class="lang-php">$user = <span class="hljs-keyword">new</span> User();
$user-&gt;first_name = <span class="hljs-string">'Chris'</span>;
$user-&gt;email = <span class="hljs-string">'cmgmyr@gmail.com'</span>;
$user-&gt;save();
</code></pre>
<hr />
<h1 id="heading-creating-1">Creating</h1>
<pre><code class="lang-php">$user = User::create([
    <span class="hljs-string">'first_name'</span> =&gt; <span class="hljs-string">'Chris'</span>,
    <span class="hljs-string">'email'</span> =&gt; <span class="hljs-string">'cmgmyr@gmail.com'</span>,
]);
</code></pre>
<p><strong>Note</strong>: <code>$fillable</code>/<code>$guarded</code> properties</p>
<hr />
<h1 id="heading-updating">Updating</h1>
<pre><code class="lang-php">$user = User::find(<span class="hljs-number">1</span>);
$user-&gt;email = <span class="hljs-string">'me@chrisgmyr.com'</span>;
$user-&gt;save();
</code></pre>
<hr />
<h1 id="heading-updating-1">Updating</h1>
<pre><code class="lang-php">$user = User::find(<span class="hljs-number">1</span>);
$user-&gt;update([
    <span class="hljs-string">'email'</span> =&gt; <span class="hljs-string">'me@chrisgmyr.com'</span>,
]);
</code></pre>
<p><strong>Note</strong>: <code>$fillable</code>/<code>$guarded</code> properties</p>
<hr />
<h1 id="heading-updating-2">Updating</h1>
<pre><code class="lang-php">$user = User::find(<span class="hljs-number">1</span>);
$user-&gt;fill([
    <span class="hljs-string">'email'</span> =&gt; <span class="hljs-string">'me@chrisgmyr.com'</span>,
]);
$user-&gt;save();
</code></pre>
<p><strong>Note</strong>: <code>$fillable</code>/<code>$guarded</code> properties</p>
<hr />
<h1 id="heading-deleting">Deleting</h1>
<pre><code class="lang-php">$user = User::find(<span class="hljs-number">1</span>);
$user-&gt;delete();
</code></pre>
<hr />
<h1 id="heading-deleting-1">Deleting</h1>
<pre><code class="lang-php">User::destroy(<span class="hljs-number">1</span>);
User::destroy([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]);
User::destroy(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>);
</code></pre>
<hr />
<h1 id="heading-or-helper-methods">"or" helper methods</h1>
<pre><code class="lang-php">User::findOrFail(<span class="hljs-number">1</span>);

$user-&gt;saveOrFail(); <span class="hljs-comment">// same as save(), but uses transaction</span>

User::firstOrCreate([ <span class="hljs-comment">/* attributes */</span>]);

User::updateOrInsert([<span class="hljs-comment">/* attributes to search */</span>], [<span class="hljs-comment">/* attributes to update */</span>]);
</code></pre>
<hr />
<h1 id="heading-querying">Querying</h1>
<hr />
<h1 id="heading-querying-1">Querying</h1>
<pre><code class="lang-php">$users = User::get(); <span class="hljs-comment">// User::all()</span>
$user  = User::where(<span class="hljs-string">'id'</span>, <span class="hljs-number">1</span>)-&gt;first();
$user  = User::find(<span class="hljs-number">1</span>);
$user  = User::findOrFail(<span class="hljs-number">1</span>);
$users = User::find([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]);
$users = User::whereIn(<span class="hljs-string">'id'</span>, [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>])-&gt;get();

$users = User::where(<span class="hljs-string">'is_admin'</span>, <span class="hljs-literal">true</span>)
    -&gt;where(<span class="hljs-string">'id'</span>, <span class="hljs-string">'!='</span>, Auth::id())
    -&gt;take(<span class="hljs-number">10</span>)
    -&gt;orderBy(<span class="hljs-string">'last_name'</span>, <span class="hljs-string">'ASC'</span>)
    -&gt;get();
</code></pre>
<hr />
<h1 id="heading-chunking">Chunking</h1>
<pre><code class="lang-php">User::chunk(<span class="hljs-number">50</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$users</span>) </span>{
    <span class="hljs-keyword">foreach</span> ($users <span class="hljs-keyword">as</span> $user) {
        <span class="hljs-comment">//</span>
    }
});
</code></pre>
<hr />
<h1 id="heading-collections">Collections</h1>
<blockquote>
<p>For Eloquent methods like <code>all()</code> and <code>get()</code> which retrieve multiple results, an instance of <code>Illuminate\Database\Eloquent\Collection</code> will be returned.</p>
</blockquote>
<pre><code class="lang-php">$admins = $users-&gt;filter(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$user</span>) </span>{
    <span class="hljs-keyword">return</span> $user-&gt;is_admin;
});
</code></pre>
<hr />
<h1 id="heading-raw-query-methods">Raw query methods</h1>
<pre><code class="lang-php">Product::whereRaw(<span class="hljs-string">'price &gt; IF(state = "NC", ?, 100)'</span>, [<span class="hljs-number">200</span>])
    -&gt;get();

Post::groupBy(<span class="hljs-string">'category_id'</span>)
    -&gt;havingRaw(<span class="hljs-string">'COUNT(*) &gt; 1'</span>)
    -&gt;get();

Customer::where(<span class="hljs-string">'created_at'</span>, <span class="hljs-string">'&gt;'</span>, <span class="hljs-string">'2016-01-01'</span>)
    -&gt;orderByRaw(<span class="hljs-string">'(updated_at - created_at) desc'</span>)
    -&gt;get();
</code></pre>
<hr />
<h1 id="heading-relationships">Relationships</h1>
<hr />
<h1 id="heading-relationships-1">Relationships</h1>
<pre><code class="lang-php">hasOne() <span class="hljs-comment">// User has one Address</span>
belongsTo() <span class="hljs-comment">// Address belongs to User</span>
hasMany() <span class="hljs-comment">// Post has many Comment</span>
belongsToMany() <span class="hljs-comment">// Role belongs to many User</span>
hasManyThrough() <span class="hljs-comment">// Country has many Post through User</span>

<span class="hljs-comment">// Use single table</span>
morphTo() <span class="hljs-comment">// Comment can be on Post, Video, Album</span>
morphMany() <span class="hljs-comment">// Post has many Comment</span>

<span class="hljs-comment">// Use pivot table</span>
morphToMany() <span class="hljs-comment">// Post has many Tag</span>
morphedByMany() <span class="hljs-comment">// Tag has many Post</span>
</code></pre>
<hr />
<h1 id="heading-relationships-2">Relationships</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Video</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">comments</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(Comment::class);
    }
}

$video = Video::find(<span class="hljs-number">1</span>);
<span class="hljs-keyword">foreach</span> ($video-&gt;comments <span class="hljs-keyword">as</span> $comment) {
    <span class="hljs-comment">// $comment-&gt;body</span>
}
</code></pre>
<hr />
<h1 id="heading-relationships-3">Relationships</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Video</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">comments</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(Comment::class);
    }
}

$video = Video::find(<span class="hljs-number">1</span>);
<span class="hljs-keyword">foreach</span> ($video-&gt;comments()-&gt;where(<span class="hljs-string">'approved'</span>, <span class="hljs-literal">true</span>)-&gt;get() <span class="hljs-keyword">as</span> $comment) {
    <span class="hljs-comment">// $comment-&gt;body</span>
}
</code></pre>
<hr />
<h1 id="heading-default-conditions-and-ordering">Default conditions and ordering</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Video</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">comments</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(Comment::class)
        -&gt;where(<span class="hljs-string">'approved'</span>, <span class="hljs-literal">true</span>)
        -&gt;latest();
    }
}
</code></pre>
<hr />
<h1 id="heading-default-conditions-and-ordering-1">Default conditions and ordering</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Video</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">comments</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(Comment::class);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">publicComments</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;comments()
        -&gt;where(<span class="hljs-string">'approved'</span>, <span class="hljs-literal">true</span>)
        -&gt;latest();
    }
}
</code></pre>
<hr />
<h1 id="heading-default-models">Default Models</h1>
<p>Default models can be used with <code>belongsTo()</code>, <code>hasOne()</code>, and <code>morphOne()</code> relationships.</p>
<hr />
<h1 id="heading-default-models-1">Default Models</h1>
<pre><code class="lang-bash">{{ <span class="hljs-variable">$post</span>-&gt;author-&gt;name }} // error <span class="hljs-keyword">if</span> author not found
</code></pre>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">author</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(User::class);
    }
}
</code></pre>
<hr />
<h1 id="heading-default-models-2">Default Models</h1>
<pre><code class="lang-bash">{{ <span class="hljs-variable">$post</span>-&gt;author-&gt;name ?? <span class="hljs-string">''</span> }} // meh
</code></pre>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">author</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(User::class);
    }
}
</code></pre>
<hr />
<h1 id="heading-default-models-3">Default Models</h1>
<pre><code class="lang-bash">{{ <span class="hljs-variable">$post</span>-&gt;author-&gt;name }} // better!
</code></pre>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">author</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(User::class)-&gt;withDefault();
    }
}
</code></pre>
<hr />
<h1 id="heading-default-models-4">Default Models</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">author</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(User::class)-&gt;withDefault([
            <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'Guest Author'</span>,
        ]);
    }
}
</code></pre>
<hr />
<h1 id="heading-events">Events</h1>
<blockquote>
<p>The <code>retrieved</code> event will fire when an existing model is retrieved from the database. When a new model is saved for the first time, the <code>creating</code> and <code>created</code> events will fire. If a model already existed in the database and the <code>save()</code> method is called, the <code>updating</code> / <code>updated</code> events will fire. However, in both cases, the <code>saving</code> / <code>saved</code> events will fire.</p>
</blockquote>
<p><a target="_blank" href="https://laravel.com/docs/5.6/eloquent#events">https://laravel.com/docs/5.6/eloquent#events</a></p>
<hr />
<h1 id="heading-events-1">Events</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">protected</span> $dispatchesEvents = [
        <span class="hljs-string">'saved'</span> =&gt; UserSaved::class,
        <span class="hljs-string">'deleted'</span> =&gt; UserDeleted::class,
    ];
}
</code></pre>
<hr />
<h1 id="heading-observers">Observers</h1>
<p><code>php artisan make:observer UserObserver --model=User</code></p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ModelObserverServiceProvider</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ServiceProvider</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">boot</span>(<span class="hljs-params"></span>)
    </span>{
        User::observe(UserObserver::class);
    }
}
</code></pre>
<hr />
<h1 id="heading-observers-1">Observers</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserObserver</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">created</span>(<span class="hljs-params">User $user</span>)
    </span>{
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updated</span>(<span class="hljs-params">User $user</span>)
    </span>{
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deleted</span>(<span class="hljs-params">User $user</span>)
    </span>{
    }
}
</code></pre>
<hr />
<h1 id="heading-boot-method"><code>boot()</code> method</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">boot</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-built_in">parent</span>::boot();

        <span class="hljs-built_in">self</span>::creating(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$model</span>) </span>{
            $model-&gt;uuid = (<span class="hljs-keyword">string</span>) Uuid::generate();
        });
    }
}
</code></pre>
<hr />
<h1 id="heading-bootable-trait">Bootable Trait</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasUuid</span>;
}

<span class="hljs-keyword">trait</span> HasUuid
{
    <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootHasUuid</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-built_in">self</span>::creating(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$model</span>) </span>{
            $model-&gt;uuid = (<span class="hljs-keyword">string</span>) Uuid::generate();
        });
    }

    <span class="hljs-comment">// more uuid related methods</span>
}
</code></pre>
<hr />
<h1 id="heading-helper-methods">Helper Methods</h1>
<hr />
<h1 id="heading-increments-and-decrements">Increments and Decrements</h1>
<pre><code class="lang-php">$post = Post::find(<span class="hljs-number">1</span>);
$post-&gt;stars++;
$post-&gt;save();

$post-&gt;stars--;
$post-&gt;save();
</code></pre>
<hr />
<h1 id="heading-increments-and-decrements-1">Increments and Decrements</h1>
<pre><code class="lang-php">$post = Post::find(<span class="hljs-number">1</span>);
$post-&gt;increment(<span class="hljs-string">'stars'</span>); <span class="hljs-comment">// add 1</span>
$post-&gt;increment(<span class="hljs-string">'stars'</span>, <span class="hljs-number">15</span>); <span class="hljs-comment">// add 15</span>

$post-&gt;decrement(<span class="hljs-string">'stars'</span>); <span class="hljs-comment">// subtract 1</span>
$post-&gt;decrement(<span class="hljs-string">'stars'</span>, <span class="hljs-number">15</span>); <span class="hljs-comment">// subtract 15</span>
</code></pre>
<hr />
<h1 id="heading-aggregates">Aggregates</h1>
<pre><code class="lang-php">$count = Product::where(<span class="hljs-string">'active'</span>, <span class="hljs-number">1</span>)-&gt;count();
$min   = Product::where(<span class="hljs-string">'active'</span>, <span class="hljs-number">1</span>)-&gt;min(<span class="hljs-string">'price'</span>);
$max   = Product::where(<span class="hljs-string">'active'</span>, <span class="hljs-number">1</span>)-&gt;max(<span class="hljs-string">'price'</span>);
$avg   = Product::where(<span class="hljs-string">'active'</span>, <span class="hljs-number">1</span>)-&gt;avg(<span class="hljs-string">'price'</span>);
$sum   = Product::where(<span class="hljs-string">'active'</span>, <span class="hljs-number">1</span>)-&gt;sum(<span class="hljs-string">'price'</span>);
</code></pre>
<hr />
<h1 id="heading-check-if-records-exist">Check if Records Exist</h1>
<p>Instead of <code>count()</code>, you could use...</p>
<pre><code class="lang-php">User::where(<span class="hljs-string">'username'</span>, <span class="hljs-string">'cmgmyr'</span>)-&gt;exists();

User::where(<span class="hljs-string">'username'</span>, <span class="hljs-string">'cmgmyr'</span>)-&gt;doesntExist();
</code></pre>
<hr />
<h1 id="heading-model-state">Model State</h1>
<pre><code class="lang-php">$model-&gt;isDirty($attributes = <span class="hljs-literal">null</span>);
$model-&gt;isClean($attributes = <span class="hljs-literal">null</span>);
$model-&gt;wasChanged($attributes = <span class="hljs-literal">null</span>);
$model-&gt;hasChanges($changes, $attributes = <span class="hljs-literal">null</span>);
$model-&gt;getDirty();
$model-&gt;getChanges();

<span class="hljs-comment">//Indicates if the model exists.</span>
$model-&gt;exists;

<span class="hljs-comment">//Indicates if the model was inserted during the current request lifecycle.</span>
$model-&gt;wasRecentlyCreated;
</code></pre>
<hr />
<h1 id="heading-magic-where">"Magic" where()</h1>
<pre><code class="lang-php">$users = User::where(<span class="hljs-string">'approved'</span>, <span class="hljs-number">1</span>)-&gt;get();
$users = User::whereApproved(<span class="hljs-number">1</span>)-&gt;get();

$user = User::where(<span class="hljs-string">'username'</span>, <span class="hljs-string">'cmgmyr'</span>)-&gt;get();
$user = User::whereUsername(<span class="hljs-string">'cmgmyr'</span>)-&gt;get();

$admins = User::where(<span class="hljs-string">'is_admin'</span>, <span class="hljs-literal">true</span>)-&gt;get();
$admins = User::whereIsAdmin(<span class="hljs-literal">true</span>)-&gt;get();
</code></pre>
<hr />
<h1 id="heading-super-magic-where">Super "Magic" where()</h1>
<pre><code class="lang-php">User::whereTypeAndStatus(<span class="hljs-string">'admin'</span>, <span class="hljs-string">'active'</span>)-&gt;get();
User::whereTypeOrStatus(<span class="hljs-string">'admin'</span>, <span class="hljs-string">'active'</span>)-&gt;get();
</code></pre>
<p>https://twitter.com/themsaid/status/1029731544942952448</p>
<hr />
<h1 id="heading-dates">Dates</h1>
<pre><code class="lang-php">User::whereDate(<span class="hljs-string">'created_at'</span>, date(<span class="hljs-string">'Y-m-d'</span>));
User::whereDay(<span class="hljs-string">'created_at'</span>, date(<span class="hljs-string">'d'</span>));
User::whereMonth(<span class="hljs-string">'created_at'</span>, date(<span class="hljs-string">'m'</span>));
User::whereYear(<span class="hljs-string">'created_at'</span>, date(<span class="hljs-string">'Y'</span>));
</code></pre>
<hr />
<h1 id="heading-when-to-eliminate-conditionals"><code>when()</code> to eliminate conditionals</h1>
<pre><code class="lang-php">$query = Author::query();

<span class="hljs-keyword">if</span> (request(<span class="hljs-string">'filter_by'</span>) == <span class="hljs-string">'likes'</span>) {
    $query-&gt;where(<span class="hljs-string">'likes'</span>, <span class="hljs-string">'&gt;'</span>, request(<span class="hljs-string">'likes_amount'</span>, <span class="hljs-number">0</span>));
}

<span class="hljs-keyword">if</span> (request(<span class="hljs-string">'filter_by'</span>) == <span class="hljs-string">'date'</span>) {
    $query-&gt;orderBy(<span class="hljs-string">'created_at'</span>, request(<span class="hljs-string">'ordering_rule'</span>, <span class="hljs-string">'desc'</span>));
}
</code></pre>
<hr />
<h1 id="heading-when-to-eliminate-conditionals-1"><code>when()</code> to eliminate conditionals</h1>
<pre><code class="lang-php">$query = Author::query();

$query-&gt;when(request(<span class="hljs-string">'filter_by'</span>) == <span class="hljs-string">'likes'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$q</span>) </span>{
    <span class="hljs-keyword">return</span> $q-&gt;where(<span class="hljs-string">'likes'</span>, <span class="hljs-string">'&gt;'</span>, request(<span class="hljs-string">'likes_amount'</span>, <span class="hljs-number">0</span>));
});

$query-&gt;when(request(<span class="hljs-string">'filter_by'</span>) == <span class="hljs-string">'date'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$q</span>) </span>{
    <span class="hljs-keyword">return</span> $q-&gt;orderBy(<span class="hljs-string">'created_at'</span>, request(<span class="hljs-string">'ordering_rule'</span>, <span class="hljs-string">'desc'</span>));
});
</code></pre>
<hr />
<h1 id="heading-replicate-a-model"><code>replicate()</code> a Model</h1>
<pre><code class="lang-php">$invoice = Invoice::find(<span class="hljs-number">1</span>);
$newInvoice = $invoice-&gt;replicate();
$newInvoice-&gt;save();
</code></pre>
<hr />
<h1 id="heading-pagination">Pagination</h1>
<pre><code class="lang-php"><span class="hljs-comment">// 1, 2, 3, 4, 5...</span>
$users = User::where(<span class="hljs-string">'active'</span>, <span class="hljs-literal">true</span>)-&gt;paginate(<span class="hljs-number">15</span>);

<span class="hljs-comment">// Previous/Next</span>
$users = User::where(<span class="hljs-string">'active'</span>, <span class="hljs-literal">true</span>)-&gt;simplePaginate(<span class="hljs-number">15</span>);

<span class="hljs-comment">// In Blade</span>
{{ $users-&gt;links() }}
</code></pre>
<hr />
<p>Pagination to Json</p>
<pre><code class="lang-javascript">{
   <span class="hljs-string">"total"</span>: <span class="hljs-number">50</span>,
   <span class="hljs-string">"per_page"</span>: <span class="hljs-number">15</span>,
   <span class="hljs-string">"current_page"</span>: <span class="hljs-number">1</span>,
   <span class="hljs-string">"last_page"</span>: <span class="hljs-number">4</span>,
   <span class="hljs-string">"first_page_url"</span>: <span class="hljs-string">"https://my.app?page=1"</span>,
   <span class="hljs-string">"last_page_url"</span>: <span class="hljs-string">"https://my.app?page=4"</span>,
   <span class="hljs-string">"next_page_url"</span>: <span class="hljs-string">"https://my.app?page=2"</span>,
   <span class="hljs-string">"prev_page_url"</span>: <span class="hljs-literal">null</span>,
   <span class="hljs-string">"path"</span>: <span class="hljs-string">"https://my.app"</span>,
   <span class="hljs-string">"from"</span>: <span class="hljs-number">1</span>,
   <span class="hljs-string">"to"</span>: <span class="hljs-number">15</span>,
   <span class="hljs-string">"data"</span>:[
        {
            <span class="hljs-comment">// Result Object</span>
        },
        {
            <span class="hljs-comment">// Result Object</span>
        }
   ]
}
</code></pre>
<hr />
<h1 id="heading-model-properties">Model Properties</h1>
<pre><code class="lang-php"><span class="hljs-keyword">protected</span> $table = <span class="hljs-string">'users'</span>;
<span class="hljs-keyword">protected</span> $fillable = [<span class="hljs-string">'first_name'</span>, <span class="hljs-string">'email'</span>, <span class="hljs-string">'password'</span>]; <span class="hljs-comment">// create()/update()</span>
<span class="hljs-keyword">protected</span> $dates = [<span class="hljs-string">'created'</span>, <span class="hljs-string">'deleted_at'</span>]; <span class="hljs-comment">// Carbon</span>
<span class="hljs-keyword">protected</span> $appends = [<span class="hljs-string">'full_name'</span>, <span class="hljs-string">'company'</span>]; <span class="hljs-comment">// additional JSON values</span>
<span class="hljs-keyword">protected</span> $casts = [<span class="hljs-string">'is_admin'</span> =&gt; <span class="hljs-string">'boolean'</span>, <span class="hljs-string">'options'</span> =&gt; <span class="hljs-string">'array'</span>];

<span class="hljs-keyword">protected</span> $primaryKey = <span class="hljs-string">'uuid'</span>;
<span class="hljs-keyword">public</span> $incrementing = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">protected</span> $perPage = <span class="hljs-number">25</span>;
<span class="hljs-keyword">const</span> CREATED_AT = <span class="hljs-string">'created'</span>;
<span class="hljs-keyword">const</span> UPDATED_AT = <span class="hljs-string">'updated'</span>;
<span class="hljs-keyword">public</span> $timestamps = <span class="hljs-literal">false</span>;
</code></pre>
<p>...and more!</p>
<hr />
<h1 id="heading-overriding-updatedat">Overriding <code>updated_at</code></h1>
<pre><code class="lang-php">$product = Product::find(<span class="hljs-number">1</span>);
$product-&gt;updated_at = <span class="hljs-string">'2020-01-01 10:00:00'</span>;
$product-&gt;save([<span class="hljs-string">'timestamps'</span> =&gt; <span class="hljs-literal">false</span>]);
</code></pre>
<hr />
<h1 id="heading-primary-key-methods">Primary Key Methods</h1>
<pre><code class="lang-php">$video = Video::find(<span class="hljs-number">1</span>);
$video-&gt;getKeyName(); <span class="hljs-comment">// 'id'</span>
$video-&gt;getKeyType(); <span class="hljs-comment">// 'int'</span>
$video-&gt;getKey(); <span class="hljs-comment">// 1</span>
</code></pre>
<hr />
<h1 id="heading-accessorsmutators">Accessors/Mutators</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setFirstNameAttribute</span>(<span class="hljs-params">$value</span>)
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;attributes[<span class="hljs-string">'first_name'</span>] = strtolower($value);
    }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setLastNameAttribute</span>(<span class="hljs-params">$value</span>)
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;attributes[<span class="hljs-string">'last_name'</span>] = strtolower($value);
    }
}
</code></pre>
<hr />
<h1 id="heading-accessorsmutators-1">Accessors/Mutators</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getFirstNameAttribute</span>(<span class="hljs-params">$value</span>)
    </span>{
        <span class="hljs-keyword">return</span> ucfirst($value);
    }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLastNameAttribute</span>(<span class="hljs-params">$value</span>)
    </span>{
        <span class="hljs-keyword">return</span> ucfirst($value);
    }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getEmailAttribute</span>(<span class="hljs-params">$value</span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Email($value);
    }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getFullNameAttribute</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"<span class="hljs-subst">{$this-&gt;first_name}</span> <span class="hljs-subst">{$this-&gt;last_name}</span>"</span>;
    }
}
</code></pre>
<hr />
<h1 id="heading-accessorsmutators-2">Accessors/Mutators</h1>
<pre><code class="lang-php">$user = User::create([
    <span class="hljs-string">'first_name'</span> =&gt; <span class="hljs-string">'Chris'</span>, <span class="hljs-comment">// chris</span>
    <span class="hljs-string">'last_name'</span> =&gt; <span class="hljs-string">'Gmyr'</span>, <span class="hljs-comment">// gmyr</span>
    <span class="hljs-string">'email'</span> =&gt; <span class="hljs-string">'cmgmyr@gmail.com'</span>,
]);

$user-&gt;first_name; <span class="hljs-comment">// Chris</span>
$user-&gt;last_name; <span class="hljs-comment">// Gmyr</span>
$user-&gt;email; <span class="hljs-comment">// instance of Email</span>
$user-&gt;full_name; <span class="hljs-comment">// 'Chris Gmyr'</span>
</code></pre>
<hr />
<h1 id="heading-to-arrayjson">To Array/Json</h1>
<pre><code class="lang-php">$user = User::find(<span class="hljs-number">1</span>);
<span class="hljs-keyword">return</span> $user-&gt;toArray();
<span class="hljs-keyword">return</span> $user-&gt;toJson();
</code></pre>
<p>You can also return <code>$user</code> from a controller method and it will automatically return JSON.</p>
<hr />
<h1 id="heading-appending-values-to-json">Appending Values to JSON</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">protected</span> $appends = [<span class="hljs-string">'full_name'</span>]; <span class="hljs-comment">// adds to toArray()</span>

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getFullNameAttribute</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"<span class="hljs-subst">{$this-&gt;first_name}</span> <span class="hljs-subst">{$this-&gt;last_name}</span>"</span>;
    }
}

<span class="hljs-comment">// or...</span>
<span class="hljs-keyword">return</span> $user-&gt;append(<span class="hljs-string">'full_name'</span>)-&gt;toArray();
<span class="hljs-keyword">return</span> $user-&gt;setAppends([<span class="hljs-string">'full_name'</span>])-&gt;toArray();
</code></pre>
<hr />
<h1 id="heading-local-scopes">Local Scopes</h1>
<pre><code class="lang-php">
$posts = Post::whereNotNull(<span class="hljs-string">'published_at'</span>)
    -&gt;where(<span class="hljs-string">'published_at'</span>, <span class="hljs-string">'&lt;='</span>, Carbon::now())
    -&gt;latest(<span class="hljs-string">'published_at'</span>)
    -&gt;get();
</code></pre>
<hr />
<h1 id="heading-local-scopes-1">Local Scopes</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">scopePublished</span>(<span class="hljs-params">$query</span>)
    </span>{
        <span class="hljs-keyword">return</span> $query-&gt;whereNotNull(<span class="hljs-string">'published_at'</span>)
            -&gt;where(<span class="hljs-string">'published_at'</span>, <span class="hljs-string">'&lt;='</span>, Carbon::now())
            -&gt;latest(<span class="hljs-string">'published_at'</span>);
    }
}
</code></pre>
<hr />
<h1 id="heading-local-scopes-2">Local Scopes</h1>
<pre><code class="lang-php">$posts = Post::published()-&gt;get();
</code></pre>
<hr />
<h1 id="heading-single-table-inheritance">Single Table Inheritance</h1>
<hr />
<h1 id="heading-single-table-inheritance-1">Single Table Inheritance</h1>
<pre><code class="lang-php">$admins = User::where(<span class="hljs-string">'is_admin'</span>, <span class="hljs-literal">true</span>)-&gt;get();
$customers = User::where(<span class="hljs-string">'is_admin'</span>, <span class="hljs-literal">false</span>)-&gt;get();
</code></pre>
<hr />
<h1 id="heading-single-table-inheritance-2">Single Table Inheritance</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">scopeAdmin</span>(<span class="hljs-params">$query</span>)
    </span>{
        <span class="hljs-keyword">return</span> $query-&gt;where(<span class="hljs-string">'is_admin'</span>, <span class="hljs-literal">true</span>);
    }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">scopeCustomer</span>(<span class="hljs-params">$query</span>)
    </span>{
        <span class="hljs-keyword">return</span> $query-&gt;where(<span class="hljs-string">'is_admin'</span>, <span class="hljs-literal">false</span>);
    }
}

$admins = User::admin()-&gt;get();
$customers = User::customer()-&gt;get();
</code></pre>
<hr />
<h1 id="heading-single-table-inheritance-3">Single Table Inheritance</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Admin</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">User</span>
</span>{
    <span class="hljs-keyword">protected</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">boot</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-built_in">parent</span>::boot();
        <span class="hljs-built_in">static</span>::addGlobalScope(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$query</span>) </span>{
            $query-&gt;where(<span class="hljs-string">'is_admin'</span>, <span class="hljs-literal">true</span>);
        });
    }
}
</code></pre>
<hr />
<h1 id="heading-single-table-inheritance-4">Single Table Inheritance</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Customer</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">User</span>
</span>{
    <span class="hljs-keyword">protected</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">boot</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-built_in">parent</span>::boot();
        <span class="hljs-built_in">static</span>::addGlobalScope(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$query</span>) </span>{
            $query-&gt;where(<span class="hljs-string">'is_admin'</span>, <span class="hljs-literal">false</span>);
        });
    }
}
</code></pre>
<hr />
<h1 id="heading-single-table-inheritance-5">Single Table Inheritance</h1>
<pre><code class="lang-php">$admins = Admin::get();
$customers = Customer::get();
</code></pre>
<hr />
<h1 id="heading-single-table-inheritance-6">Single Table Inheritance</h1>
<p>Read more:</p>
<ul>
<li><p>https://twitter.com/cmgmyr/status/885204646498893824</p>
</li>
<li><p>https://tighten.co/blog/extending-models-in-eloquent</p>
</li>
</ul>
<hr />
<h1 id="heading-default-model-data">Default Model Data</h1>
<hr />
<h1 id="heading-default-model-data-1">Default Model Data</h1>
<pre><code class="lang-php">Schema::create(<span class="hljs-string">'users'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
    $table-&gt;increments(<span class="hljs-string">'id'</span>);
    $table-&gt;string(<span class="hljs-string">'name'</span>);
    $table-&gt;string(<span class="hljs-string">'email'</span>)-&gt;unique();
    $table-&gt;string(<span class="hljs-string">'password'</span>);
    $table-&gt;string(<span class="hljs-string">'role'</span>)-&gt;default(<span class="hljs-string">'user'</span>); <span class="hljs-comment">// moderator, admin, etc</span>
    $table-&gt;rememberToken();
    $table-&gt;timestamps();
});
</code></pre>
<hr />
<h1 id="heading-default-model-data-2">Default Model Data</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">protected</span> $fillable = [
        <span class="hljs-string">'name'</span>, <span class="hljs-string">'email'</span>, <span class="hljs-string">'password'</span>, <span class="hljs-string">'role'</span>
    ];
}
</code></pre>
<hr />
<h1 id="heading-default-model-data-3">Default Model Data</h1>
<pre><code class="lang-php">$user = <span class="hljs-keyword">new</span> User();
$user-&gt;name = <span class="hljs-string">'Chris'</span>;
$user-&gt;email = <span class="hljs-string">'cmgmyr@gmail.com'</span>;
$user-&gt;password = Hash::make(<span class="hljs-string">'p@ssw0rd'</span>);

<span class="hljs-comment">// $user-&gt;role is currently NULL</span>

$user-&gt;save();

$user-&gt;role; <span class="hljs-comment">// 'user'</span>
</code></pre>
<hr />
<h1 id="heading-default-model-data-4">Default Model Data</h1>
<p>Remove <code>-&gt;default('user')</code>;</p>
<pre><code class="lang-php">Schema::create(<span class="hljs-string">'users'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
    $table-&gt;increments(<span class="hljs-string">'id'</span>);
    $table-&gt;string(<span class="hljs-string">'name'</span>);
    $table-&gt;string(<span class="hljs-string">'email'</span>)-&gt;unique();
    $table-&gt;string(<span class="hljs-string">'password'</span>);
    $table-&gt;string(<span class="hljs-string">'role'</span>); <span class="hljs-comment">// moderator, admin, etc</span>
    $table-&gt;rememberToken();
    $table-&gt;timestamps();
});
</code></pre>
<hr />
<h1 id="heading-default-model-data-5">Default Model Data</h1>
<p>Set <code>$attributes</code></p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">protected</span> $fillable = [
        <span class="hljs-string">'name'</span>, <span class="hljs-string">'email'</span>, <span class="hljs-string">'password'</span>, <span class="hljs-string">'role'</span>
    ];

    <span class="hljs-keyword">protected</span> $attributes = [
        <span class="hljs-string">'role'</span> =&gt; <span class="hljs-string">'user'</span>,
    ];
}
</code></pre>
<hr />
<h1 id="heading-default-model-data-6">Default Model Data</h1>
<pre><code class="lang-php">$user = <span class="hljs-keyword">new</span> User();
$user-&gt;name = <span class="hljs-string">'Chris'</span>;
$user-&gt;email = <span class="hljs-string">'cmgmyr@gmail.com'</span>;
$user-&gt;password = Hash::make(<span class="hljs-string">'p@ssw0rd'</span>);

<span class="hljs-comment">// $user-&gt;role is currently 'user'!</span>

$user-&gt;save();

$user-&gt;role; <span class="hljs-comment">// 'user'</span>
</code></pre>
<hr />
<h1 id="heading-default-model-data-7">Default Model Data</h1>
<pre><code class="lang-php">$user = <span class="hljs-keyword">new</span> User();
$user-&gt;name = <span class="hljs-string">'Chris'</span>;
$user-&gt;email = <span class="hljs-string">'cmgmyr@gmail.com'</span>;
$user-&gt;password = Hash::make(<span class="hljs-string">'p@ssw0rd'</span>);
$user-&gt;role = <span class="hljs-string">'admin'</span>; <span class="hljs-comment">// can override default</span>
$user-&gt;save();

$user-&gt;role; <span class="hljs-comment">// 'admin'</span>
</code></pre>
<hr />
<h1 id="heading-default-models-5">Default Models</h1>
<p>Remember our previous example?</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">author</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(User::class)-&gt;withDefault([
            <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'Guest Author'</span>,
        ]);
    }
}
</code></pre>
<hr />
<h1 id="heading-default-models-6">Default Models</h1>
<p>We no longer need to provide a <code>name</code>, use the <code>User $attributes</code> property!</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">author</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(User::class)-&gt;withDefault();
    }
}
</code></pre>
<hr />
<h1 id="heading-default-model-data-8">Default Model Data</h1>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">protected</span> $fillable = [
        <span class="hljs-string">'name'</span>, <span class="hljs-string">'email'</span>, <span class="hljs-string">'password'</span>, <span class="hljs-string">'role'</span>
    ];

    <span class="hljs-keyword">protected</span> $attributes = [
        <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'Guest Author'</span>,
        <span class="hljs-string">'role'</span> =&gt; <span class="hljs-string">'user'</span>,
    ];
}
</code></pre>
<hr />
<h1 id="heading-default-model-data-9">Default Model Data</h1>
<p>Watch Colin DeCarlo's - Keeping Eloquent Eloquent from Laracon US 2016</p>
<p>https://streamacon.com/video/laracon-us-2016/colin-decarlo-keeping-eloquent-eloquent</p>
<hr />
<h1 id="heading-sub-queries">Sub-Queries</h1>
<pre><code class="lang-php">$customers = Customer::with(<span class="hljs-string">'company'</span>)
    -&gt;orderByName()
    -&gt;paginate();
</code></pre>
<p>Get latest interactions?</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{ $customer
    -&gt;interactions()
    -&gt;latest()
    -&gt;first()
    -&gt;created_at
    -&gt;diffForHumans() }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
</code></pre>
<hr />
<h1 id="heading-sub-queries-1">Sub-Queries</h1>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">scopeWithLastInteractionDate</span>(<span class="hljs-params">$query</span>)
</span>{
    $subQuery = \DB::table(<span class="hljs-string">'interactions'</span>)
        -&gt;select(<span class="hljs-string">'created_at'</span>)
        -&gt;whereRaw(<span class="hljs-string">'customer_id = customers.id'</span>)
        -&gt;latest()
        -&gt;limit(<span class="hljs-number">1</span>);

    <span class="hljs-keyword">return</span> $query-&gt;select(<span class="hljs-string">'customers.*'</span>)-&gt;selectSub($subQuery, <span class="hljs-string">'last_interaction_date'</span>);
}

$customers = Customer::with(<span class="hljs-string">'company'</span>)
    -&gt;withLastInteractionDate()
    -&gt;orderByName()
    -&gt;paginate();
</code></pre>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{ $customer-&gt;last_interaction_date-&gt;diffForHumans() }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
</code></pre>
<hr />
<h1 id="heading-sub-queries-2">Sub-Queries</h1>
<p>Jonathan Reinink's Laracon 2018 Online Talk - Advanced Querying with Eloquent</p>
<p>https://github.com/reinink/laracon2018</p>
<hr />
<h1 id="heading-resources">Resources</h1>
<ul>
<li><p>https://laravel.com/docs/5.6/eloquent</p>
</li>
<li><p>https://laravel-news.com/eloquent-tips-tricks</p>
</li>
<li><p>https://twitter.com/themsaid/status/1029731544942952448</p>
</li>
<li><p>https://twitter.com/cmgmyr/status/885204646498893824</p>
</li>
<li><p>https://tighten.co/blog/extending-models-in-eloquent</p>
</li>
<li><p>https://streamacon.com/video/laracon-us-2016/colin-decarlo-keeping-eloquent-eloquent</p>
</li>
<li><p>https://github.com/reinink/laracon2018</p>
</li>
<li><p>https://eloquentbyexample.com</p>
</li>
</ul>
<hr />
<h1 id="heading-thank-you">Thank you!</h1>
<h2 id="heading-please-say-hi">Please say "hi"</h2>
<h4 id="heading-twittercomcmgmyr">twitter.com/cmgmyr</h4>
<h4 id="heading-githubcomcmgmyr">github.com/cmgmyr</h4>
<h4 id="heading-chrisgmyrcom">chrisgmyr.com</h4>
]]></content:encoded></item><item><title><![CDATA[Deploying Specific Branches with Laravel, CircleCI, and Envoyer]]></title><description><![CDATA[A few weeks ago I was trying to update a side project's CircleCI config from version 1 to version 2 since they are depreciating V1 in August 2018. In the process, I was curious how I could deploy specific branches to specific environments in Laravel'...]]></description><link>https://chrisgmyr.dev/deploying-specific-branches-with-laravel-circleci-and-envoyer</link><guid isPermaLink="true">https://chrisgmyr.dev/deploying-specific-branches-with-laravel-circleci-and-envoyer</guid><category><![CDATA[Laravel]]></category><category><![CDATA[CircleCI]]></category><category><![CDATA[laravel envoyer]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Fri, 06 Apr 2018 16:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/br9D5K3UTRQ/upload/83ef35e738a833317895da39650ce618.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A few weeks ago I was trying to update a side project's <a target="_blank" href="https://circleci.com/">CircleCI</a> config from version 1 to version 2 since they are <a target="_blank" href="https://circleci.com/blog/sunsetting-1-0/">depreciating V1</a> in August 2018. In the process, I was curious how I could deploy specific branches to specific environments in Laravel's <a target="_blank" href="https://envoyer.io">Envoyer</a> if the tests passed successfully.</p>
<p>My project has two main branches: <code>develop</code> and <code>master</code>. In Envoyer I have two projects, one for <code>dev.project.com</code> which uses the <code>develop</code> branch and the other for <code>project.com</code> which uses the <code>master</code> branch.</p>
<p>Here is the final result of the <code>circle.yml</code> file. Let's work through each of the sections below.</p>
<h2 id="heading-section-1-defaults">Section 1: Defaults</h2>
<p>By leveraging <a target="_blank" href="https://learnxinyminutes.com/docs/yaml/">YAML anchors</a> we can set a group of defaults that will be used for all of our later jobs. For now, this includes our</p>
<ul>
<li><p>working directory</p>
</li>
<li><p>chosen CircleCI <a target="_blank" href="https://circleci.com/docs/2.0/circleci-images/">docker image</a></p>
</li>
</ul>
<h2 id="heading-section-2-jobs">Section 2: Jobs</h2>
<p>In this file we have three jobs: <code>build</code> (and test), <code>deploy_develop</code>, and <code>deploy_master</code>.</p>
<p>Our <code>build</code> job</p>
<ol>
<li><p>Imports the defaults</p>
</li>
<li><p>Sets environment variables</p>
</li>
<li><p>Checks out the repo's code</p>
</li>
<li><p>Restores <code>composer</code> cache, if available</p>
</li>
<li><p>Runs <code>composer install</code></p>
</li>
<li><p>Saves a new <code>composer</code> cache</p>
</li>
<li><p>Runs the test suite with PHPUnit</p>
</li>
</ol>
<p>Our "deploy" jobs:</p>
<ol>
<li><p>Imports the defaults</p>
</li>
<li><p>Pings Envoyer to deploy the project</p>
</li>
</ol>
<h2 id="heading-section-3-workflows">Section 3: Workflows</h2>
<p>Now that we have our jobs set up, we need to implement <a target="_blank" href="https://circleci.com/docs/2.0/workflows/">workflows</a> to pull everything together. Workflows are optional, but they can come in handy depending on what you'd like to do with your project.</p>
<p>In this example, we only need one workflow <code>notify_deploy</code> which will notify Envoyer that we want to deploy a specific branch.</p>
<p>Within the workflow, you'll notice that we are listing all of our jobs: <code>build</code>, <code>deploy_develop</code>, and <code>deploy_master</code>.</p>
<p>We start off running our <code>build</code> job, and if that is successful, we'll move forward with our deploy jobs. Each deploy job requires the <code>build</code> to run first; then we'll check if the version branch matches the filter on the workflow. So <code>deploy_develop</code> is only run on the <code>develop</code> branch and <code>deploy_master</code> is only run on the <code>master</code> branch.</p>
<p>By limiting the filters to only the <code>develop</code> and <code>master</code> branches we can guarantee that we're only deploying those specific branches, but the <code>build</code> job will run on all branches (bug, hotfix, and feature branches), which is needed for pull requests.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Once we merge a branch into either <code>develop</code> or <code>master</code> CircleCI will build and notify Envoyer to deploy if successful. In our CircleCI dashboard, you'll now see a workflow similar to this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698249237158/8508be83-16d6-400a-b98b-7d0d730bb73e.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>You'll also need to make sure you turn off the "Deploy When Code Is Pushed" option in your Enoyer project.</p>
</blockquote>
<h2 id="heading-learn-more">Learn More</h2>
<p>This is only scratching the surface of what you can do with CircleCI builds and workflows. I encourage you to look through the documentation and example projects to see what you can implement in your projects.</p>
<ul>
<li><p><a target="_blank" href="https://circleci.com/docs/2.0/">2.0 Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://circleci.com/docs/2.0/tutorials/">Sample Projects</a></p>
</li>
<li><p><a target="_blank" href="https://circleci.com/docs/2.0/workflows/">Workflow Documentation</a></p>
</li>
<li><p><a target="_blank" href="http://www.yaml.org/spec/1.2/spec.html">YAML 1.2 Spec</a></p>
</li>
</ul>
<blockquote>
<p>Please note - CircleCI nor Envoyer/Laravel paid me to write this article, I'm just a happy customer.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Revisiting Our Work]]></title><description><![CDATA[I recently watched David Heinemeier Hansson’s (@dhh) video on code comments and refactoring. While it’s interesting to see how he tackles these code changes, the most interesting thing to me is what he said he does with the codebase.



“I read throu...]]></description><link>https://chrisgmyr.dev/revisiting-our-work-ee6fe366142</link><guid isPermaLink="true">https://chrisgmyr.dev/revisiting-our-work-ee6fe366142</guid><category><![CDATA[Dhh]]></category><category><![CDATA[refactoring]]></category><category><![CDATA[code review]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Wed, 28 Feb 2018 15:26:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/w7ZyuGYNpRQ/upload/fe8b889ad16fa6c164eeb12cad2e459c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently watched David Heinemeier Hansson’s (<a target="_blank" href="https://twitter.com/dhh">@dhh</a>) video on code comments and refactoring. While it’s interesting to see how he tackles these code changes, the most interesting thing to me is what he said he does with the codebase.</p>
<iframe src="https://www.youtube.com/embed/H5i1gdwe1Ls?feature=oembed&amp;start=212" width="700" height="393"></iframe>

<blockquote>
<p><em>“I read through the entire codebase of Basecamp 3 and try to make things better that I don’t think are good enough, or revisit decisions that we’ve made earlier that I think now I have a better idea of how to do”</em></p>
</blockquote>
<p>There’s a lot in that statement, so let’s unpack it.</p>
<p>First, he reads through the <em>entire</em> codebase! The fact that he does this shows great care for what he does and believes in.</p>
<p>Second, he makes changes where he doesn’t feel like the code is <em>good enough</em>. While many of us have ideas about what is “good” or not, I’m sure we’ve all gone back through older code and just known when we’ve seen it.</p>
<p>Lastly, he revisits past decisions that can be better handled now. Programming is an ever-changing space and developers are enhancing their skills and knowledge on a daily basis. So why shouldn’t our code reflect our most up-to-date understandings?</p>
<p>DHH isn’t the only one who does this though. <a target="_blank" href="https://twitter.com/taylorotwell">Taylor Otwell</a> (creator of Laravel) does something similar.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/taylorotwell/status/818863355066798080">https://twitter.com/taylorotwell/status/818863355066798080</a></div>
<p> </p>
<p>Going through code and documentation that you’ve already worked on isn’t the most glamorous job, even quite tedious, but necessary. It’s silly to think that something that was done years, weeks, or even days ago is still “good enough” for today.</p>
<p>I know I’ve fallen into the habit of not revisiting what I’ve worked on long ago — and maybe some projects don’t need it. However, if the public is using it — like in a current site, app, or package it might be time to take a look through for improvements.</p>
<h3 id="heading-next-steps">Next Steps</h3>
<p>I’m going to take a hard look at my <a target="_blank" href="https://github.com/cmgmyr/laravel-messenger">messenger</a> package (which needs some love) as well as a few side projects that I haven’t worked on in a while. What projects are you going to look at? Let’s share some successes. Send me a tweet or screenshot on <a target="_blank" href="https://twitter.com/cmgmyr">Twitter</a>, or leave a comment below.</p>
]]></content:encoded></item><item><title><![CDATA[How to order by all() in Laravel]]></title><description><![CDATA[One common issue that I see with Laravel newcomers is that they have hangups using Eloquent correctly. The most basic reference given in the documentation and tutorials is using the all() method.
$users = User::all();

But what happens when you want ...]]></description><link>https://chrisgmyr.dev/how-to-order-by-all-in-laravel-1a308497de55</link><guid isPermaLink="true">https://chrisgmyr.dev/how-to-order-by-all-in-laravel-1a308497de55</guid><category><![CDATA[Laravel]]></category><category><![CDATA[eloquent]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Wed, 31 May 2017 21:19:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/o6GEPQXnqMY/upload/ec8a8c7f5d70d7f9176dd4db35930a85.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One common issue that I see with Laravel newcomers is that they have <a target="_blank" href="https://stackoverflow.com/questions/17553181/laravel-4-how-to-order-by-using-eloquent-orm/18289241#18289241">hangups</a> using Eloquent correctly. The most basic reference given in the documentation and tutorials is using the <code>all()</code> method.</p>
<pre><code class="lang-php">$users = User::all();
</code></pre>
<p><strong>But what happens when you want to sort your users?</strong></p>
<p>As newcomers to the framework, I feel like most are too excited to “jump in and build something” instead of <a target="_blank" href="https://laravel.com/docs/5.4/eloquent#retrieving-models">learning more</a> about it. (But who can blame them, right?!?) So something like this would happen:</p>
<pre><code class="lang-php">$users = User::all()-&gt;orderBy(<span class="hljs-string">'name'</span>, <span class="hljs-string">'ASC'</span>);

<span class="hljs-comment"># BadMethodCallException with message 'Method orderBy does not exist.'</span>

<span class="hljs-comment">// or</span>

$users = User::orderBy(<span class="hljs-string">'name'</span>, <span class="hljs-string">'ASC'</span>)-&gt;all();

<span class="hljs-comment"># BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::all()'</span>
</code></pre>
<h3 id="heading-forget-about-all">Forget about <code>all()</code></h3>
<p>In my experience, I’ve never needed an unordered dump of data in an application.</p>
<p>Note that <code>all()</code> is a convenience method for <code>get()</code> but does not allow you to chain additional methods. <a target="_blank" href="https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Model.php#L340">Take a look</a>:</p>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">all</span>(<span class="hljs-params">$columns = [<span class="hljs-string">'*'</span>]</span>)
</span>{
    <span class="hljs-keyword">return</span> (<span class="hljs-keyword">new</span> <span class="hljs-built_in">static</span>)-&gt;newQuery()-&gt;get
        (is_array($columns) ? $columns : func_get_args()
    );
}
</code></pre>
<p>By using <code>get()</code> you’ll be able to achieve the desired results.</p>
<pre><code class="lang-php">$users = User::orderBy(<span class="hljs-string">'name'</span>, <span class="hljs-string">'ASC'</span>)-&gt;get();

<span class="hljs-comment">// and</span>

$users = User::where(<span class="hljs-string">'email'</span>, <span class="hljs-string">'LIKE'</span>, <span class="hljs-string">'%@gmail.com'</span>)  
    -&gt;orderBy(<span class="hljs-string">'name'</span>, <span class="hljs-string">'ASC'</span>)-&gt;get();
</code></pre>
<p>So any time you reach for the <code>all()</code> method, I highly recommend using <code>get()</code> instead.</p>
]]></content:encoded></item><item><title><![CDATA[Prioritizing Queued Jobs in Laravel]]></title><description><![CDATA[Laravel queues allow you to defer long-running, or resource-intensive, processes until a later time. A queue system is imperative for larger applications but can be helpful for smaller ones as well. But with so many jobs and queues, how can we priori...]]></description><link>https://chrisgmyr.dev/prioritizing-queued-jobs-in-laravel-6f1b688acc1d</link><guid isPermaLink="true">https://chrisgmyr.dev/prioritizing-queued-jobs-in-laravel-6f1b688acc1d</guid><category><![CDATA[Laravel]]></category><category><![CDATA[queue]]></category><category><![CDATA[queue management]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Tue, 10 Jan 2017 16:34:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Nzb4LBsctyQ/upload/3b76997bcb4260656369a23572cb0b07.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Laravel queues allow you to defer long-running, or resource-intensive, processes until a later time. A queue system is imperative for larger applications but can be helpful for smaller ones as well. But with so many jobs and queues, how can we prioritize them?</p>
<p>On a current project, I had to figure out how to grab a ton of data from an API (Facebook) that had dependent data — meaning the script cannot proceed to get new data before it’s done with the current data set. This typically ended up being an ID that was needed for the next call as well as some data that needed to be processed.</p>
<p>In pseudo-code, this would look something similar to:</p>
<pre><code class="lang-php">$a = $get-&gt;a();  
$b = $get-&gt;b($a);   
$c = $get-&gt;c($b);   
<span class="hljs-keyword">$this</span>-&gt;doSomethingWith($c);
</code></pre>
<p>As you can see, the code should not move ahead without processing the previous data. In my situation, I had to process all of the “A” jobs, then all of the “B” jobs, and so on, before continuing.</p>
<p>The data that I needed was quite large and each round needed a good amount of processing before continuing to the next step. Luckily this is where <a target="_blank" href="https://laravel.com/">Laravel</a> and its queue system stepped in to help!</p>
<p>Laravel lets you specify a dynamic queue name along with a job, like so:</p>
<pre><code class="lang-php">dispatch((<span class="hljs-keyword">new</span> JobA($data))-&gt;onQueue(<span class="hljs-string">'a'</span>));
</code></pre>
<p>In my application in each “JobA” a “JobB” would be called. For each “JobB” a “JobC” would be called. At the end of “JobC”, we’d do some additional work on the whole data collection. So you’d get something like this:</p>
<pre><code class="lang-php"><span class="hljs-comment">// In Controller  </span>
dispatch((<span class="hljs-keyword">new</span> JobA($data))-&gt;onQueue(<span class="hljs-string">'a'</span>));

<span class="hljs-comment">// In JobA  </span>
dispatch((<span class="hljs-keyword">new</span> JobB($data))-&gt;onQueue(<span class="hljs-string">'b'</span>));

<span class="hljs-comment">// In JobB  </span>
dispatch((<span class="hljs-keyword">new</span> JobC($data))-&gt;onQueue(<span class="hljs-string">'c'</span>));

<span class="hljs-comment">// In JobC  </span>
dispatch((<span class="hljs-keyword">new</span> JobFinish($data))-&gt;onQueue(<span class="hljs-string">'finish'</span>));
</code></pre>
<p>I wanted to make sure we ran all of the jobs in order, and only continued onto the next set of jobs once the current batch was finished. This was very important since the application could easily have 20, or so, JobAs, 50 JobBs, and hundreds of JobCs.</p>
<p>In my supervisor config file, I added something similar to this:</p>
<pre><code class="lang-bash">[program:artisan-queue]  
<span class="hljs-built_in">command</span> = php artisan queue:work --queue=a,b,c,finish
</code></pre>
<p>Now all of the jobs on the “a” queue would have to finish before the “b” jobs would start, and “c” would wait for “b”, and so on.</p>
<p>So there you have it — a prioritized queuing system in only a few lines of code!</p>
<p>The Laravel Queue system is very robust, and if you haven’t used it, I’d highly recommend trying it in your next project. You can read more about the queue system and queue priorities <a target="_blank" href="https://laravel.com/docs/5.3/queues#queue-priorities">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Moving from self-hosted image service to Cloudinary]]></title><description><![CDATA[Image manipulation is hard. Handling images in the long term is even harder. Here at Dose we have a lot of images and a number of ways to serve them. We have Android and iOS apps, as well as completely responsive websites, so each image has the poten...]]></description><link>https://chrisgmyr.dev/moving-from-self-hosted-image-service-to-cloudinary-bd7370317a0d</link><guid isPermaLink="true">https://chrisgmyr.dev/moving-from-self-hosted-image-service-to-cloudinary-bd7370317a0d</guid><category><![CDATA[cloudinary]]></category><category><![CDATA[image processing]]></category><category><![CDATA[image manipulation]]></category><dc:creator><![CDATA[Chris Gmyr]]></dc:creator><pubDate>Tue, 28 Jun 2016 17:59:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/8DMuvdp-vso/upload/e2d139c05298bcdd65fddf03414ebf2e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696965223063/a97acee0-e650-4b87-a4aa-826f4cceb3b5.png" alt /></p>
<p>Image manipulation is hard. Handling images in the long term is even harder. Here at <a target="_blank" href="http://about.dose.com/">Dose</a> we have a lot of images and a number of ways to serve them. We have Android and iOS apps, as well as completely responsive websites, so each image has the potential of getting manipulated multiple times depending on the device, orientation, and how we optimize an image for a certain platform.</p>
<h3 id="heading-handling-this-ourselves">Handling this ourselves</h3>
<p>We used to have an internal image service that took an author’s uploaded image and handled some pre-processing. On upload, we’d make sure it’s within a max width and encoded correctly. Animated <strong>GIF</strong>? We’d have to convert the file to <strong>MP4</strong> and <strong>WEBM</strong> also. This would end up adding extra load on our servers and more space taken in our S3 account.</p>
<p>When an asset was requested with a certain height/width combination we’d check to see if we had it in our S3 bucket. If not we’d return the master asset and kick off a queue job to create it then add it to the bucket for the next request. As you can imagine, the usage was huge. Want to change the article promo image from 500px width to 475px width? That would invalidate all previous images and we’d need to recreate all new images the next time they were requested. What a mess!</p>
<p>If your product is not image manipulation, then don’t do this yourself. Services like Cloudinary do this much more efficiently and much better than you will, so use them. And if you’re worried about the cost, think about how much it’ll cost you in development and upkeep, as well as hosting, storage, and delivery costs. More on that later.</p>
<h3 id="heading-getting-started-with-cloudinary">Getting Started with Cloudinary</h3>
<blockquote>
<p>Cloudinary is the market leader in providing a comprehensive cloud-based image management solution. Cloudinary is being used by tens of thousands of web and mobile application developers all around the world, from small startups to large enterprises. We are here to cover your every image-related need.</p>
</blockquote>
<p><a target="_blank" href="http://cloudinary.com/about">Source</a></p>
<p>Cloudinary has taken the massive task of handling and manipulating images and broke it down to something very simple — URL manipulation.</p>
<p>Take the following image url:</p>
<p><code>http://res.cloudinary.com/demo/image/upload/sample.jpg</code></p>
<p>Let’s say we’d like to resize this to a max width of 300px, you’d get</p>
<p><code>http://res.cloudinary.com/demo/image/upload/w_300/sample.jpg</code></p>
<p>How about we restrict the height to 150px</p>
<p><code>http://res.cloudinary.com/demo/image/upload/w_300,h_150/sample.jpg</code></p>
<p>Well, we got the size that we wanted, but it looks <a target="_blank" href="http://res.cloudinary.com/demo/image/upload/w_300,h_150/sample.jpg">pretty bad</a> right now. We’ll need to make some adjustments. Let’s <strong>crop</strong> it to <strong>fit</strong> the space that we need</p>
<p><code>http://res.cloudinary.com/demo/image/upload/w_300,h_150,c_fit/sample.jpg</code></p>
<p>Yeah, <a target="_blank" href="http://res.cloudinary.com/demo/image/upload/w_300,h_150,c_fit/sample.jpg">this looks a lot better</a>!</p>
<h3 id="heading-combining-transformations">Combining Transformations</h3>
<p>Combining transformations is just as straightforward as single transformations, all you have to do is add another segment to the URL</p>
<p><code>http://res.cloudinary.com/demo/image/upload/w_300,h_150/c_crop,w_50,h_50,x_100,y_75/sample.jpg</code></p>
<p>So with this example we are:</p>
<ol>
<li><p>Resizing the image to 300px X 150px then</p>
</li>
<li><p>Cropping the image to be 50px X 50px and moving the X, Y point to 100, 75 in order to focus in on the <a target="_blank" href="http://res.cloudinary.com/demo/image/upload/w_300,h_150/c_crop,w_50,h_50,x_100,y_75/sample.jpg">yellow part</a> of one of the flowers.</p>
</li>
</ol>
<p><a target="_blank" href="http://cloudinary.com/documentation/image_transformations#chained_transformations">Learn more</a></p>
<h3 id="heading-file-type-transformations">File Type Transformations</h3>
<p>This is by far one of the most powerful features of Cloudinary. Say we are uploading a <strong>GIF</strong>, but want to optimize it and change it to a <strong>MP4</strong>.</p>
<p><code>http://res.cloudinary.com/demo/image/upload/kitten_fighting.gif</code></p>
<p>would turn into</p>
<p><code>http://res.cloudinary.com/demo/image/upload/kitten_fighting.mp4</code></p>
<p>Just by changing the extension of the file in the URL will make this conversion for you. No more keeping track of different file references or different hashes. Only ask for a different file extension!</p>
<p><a target="_blank" href="http://cloudinary.com/blog/reduce_size_of_animated_gifs_automatically_convert_to_webm_and_mp4">Learn more</a></p>
<h3 id="heading-programmatic-sdks">Programmatic SDKs</h3>
<p>It’s all well and good that we can convert any file asset via a URL, but if you want to make these changes more programmatically, you can use one of their many SDKs. For this example, we’ll be using their <a target="_blank" href="https://github.com/cloudinary/cloudinary_php">PHP SDK</a></p>
<p>In my initial example, we ended with the final URL of</p>
<p><code>http://res.cloudinary.com/demo/image/upload/w_300,h_150,c_fit/sample.jpg</code></p>
<p>for our image. In the PHP SDK, we’d be able to do</p>
<pre><code class="lang-bash"><span class="hljs-variable">$transformations</span> = [<span class="hljs-string">'width'</span> =&gt; 100, <span class="hljs-string">'height'</span> =&gt; 150, <span class="hljs-string">'crop'</span> =&gt; <span class="hljs-string">'fill'</span>];  
<span class="hljs-variable">$url</span> = cloudinary_url(<span class="hljs-string">'sample.jpg'</span>, <span class="hljs-variable">$transformations</span>);
</code></pre>
<p>and multiple transformations, like our second example, would look like</p>
<pre><code class="lang-bash"><span class="hljs-variable">$transformations</span> = [<span class="hljs-string">'transformation'</span> =&gt; [  
   [<span class="hljs-string">'width'</span> =&gt; 300, <span class="hljs-string">'height'</span> =&gt; 150],  
   [<span class="hljs-string">'width'</span> =&gt; 50, <span class="hljs-string">'height'</span> =&gt; 50, <span class="hljs-string">'crop'</span> =&gt; <span class="hljs-string">'crop'</span>, <span class="hljs-string">'x'</span> =&gt; 100, <span class="hljs-string">'y'</span> =&gt; 75]  
]];  
<span class="hljs-variable">$url</span> = cloudinary_url(<span class="hljs-string">'sample.jpg'</span>, <span class="hljs-variable">$transformations</span>);
</code></pre>
<h3 id="heading-migrating-to-cloudinary">Migrating to Cloudinary</h3>
<p>Cloudinary has taken the hard work out of migrating assets over to their platform. There are currently a few options to choose from.</p>
<ol>
<li><p>Add a full URL to your current image and Cloudinary will automatically pull this in for you.</p>
</li>
<li><p>Set up an auto upload mapping to your S3 bucket. When this “virtual” directory is requested, it will pull the asset from your bucket into your Cloudinary account.</p>
</li>
</ol>
<p><a target="_blank" href="http://cloudinary.com/blog/how_to_automatically_migrate_all_your_images_to_the_cloud">Learn more</a></p>
<h3 id="heading-why-we-chose-cloudinary">Why We Chose Cloudinary</h3>
<p>Before moving forward with anything new, we went through a diligent research period where we were looking at a handful of SaaS options as well as possibly redesigning our current in-house system. As we worked through the options one thought became very clear — handling our own system didn’t make sense. There are a good handful of services that are more cost-effective and feature-rich than something that we could ever make. Our business is not handling images. Just the cost of AWS instances, storage, and CDN fees were tens of thousands of dollars per month. Now take developer costs to maintain and add to the system, and it adds up quickly. But even more importantly, each time something would break or need to be added to our image service, it would take attention away from more important tasks or services.</p>
<p>After evaluating a number of image and CDN services, we moved forward with Cloudinary for a number of reasons:</p>
<ol>
<li><p><strong>They were very attentive.</strong> They set up multiple meetings with their sales and tech teams to answer all of our questions and to help get us the best price for the resources we need. Even now, after the “sale”, they continue to reach out personally to share new features that haven’t been published yet and check in on analytics and our performance.</p>
</li>
<li><p><strong>Price.</strong> Their price is a LOT cheaper than running all of our instances, storage, and CDN through AWS.</p>
</li>
<li><p><strong>Features.</strong> The flexibility and amount of features they have for image handling is impressive and there would be no realistic way that we’d have the bandwidth in order to make similar features ourselves. Their system makes it very easy to make adjustments on the fly to see what combinations perform, and look better.</p>
</li>
<li><p><strong>Uptime.</strong> Since the majority of our content is showing images, if the images aren’t available we aren’t able to provide the value to our users that we need to. Since moving to Cloudinary, our image uptime has been spectacular and image responsiveness has been significantly faster too.</p>
</li>
</ol>
<h3 id="heading-summary">Summary</h3>
<p>We have simplified our image handling process over the last 6 months since starting to work with Cloudinary. They can easily handle our <strong>4.5 Million images</strong> (and counting) and over <strong>1.4 Billion requests per month</strong>. They also provide insight to help us further improve our performance on our sites. Site performance and user experience are not taken lightly, so we are very happy with our decision to utilize their services. Some other helpful points are</p>
<ul>
<li><p><a target="_blank" href="http://cloudinary.com/documentation">Great documentation</a></p>
</li>
<li><p>A bunch of <a target="_blank" href="http://cloudinary.com/addons">add-on plugins</a> like JPEGmini and Imagga</p>
</li>
<li><p>Tons of transformation options like <a target="_blank" href="http://cloudinary.com/blog/adding_watermarks_credits_badges_and_text_overlays_to_images">watermarks</a>, <a target="_blank" href="http://cloudinary.com/cookbook/pixelate_an_image_or_a_region">custom pixelation</a>, and even <a target="_blank" href="http://cloudinary.com/cookbook/convert_pdf_to_jpg">PDF to image</a> conversion</p>
</li>
<li><p>Automatic <a target="_blank" href="http://cloudinary.com/blog/introducing_intelligent_responsive_image_breakpoints_solutions">responsive image</a> handling</p>
</li>
<li><p>Video <a target="_blank" href="http://cloudinary.com/documentation/video_management">management and transformations</a></p>
</li>
<li><p><a target="_blank" href="http://cloudinary.com/blog/automatic_backup_of_user_uploaded_images_using_cloudinary">Automatic backups to S3</a></p>
</li>
<li><p>Newly introduced “<a target="_blank" href="http://cloudinary.com/blog/introducing_smart_cropping_intelligent_quality_selection_and_automated_responsive_images">Auto Everything</a>”</p>
</li>
</ul>
<p>As an aside, we’re just sharing this article and information as happy customers. Cloudinary did not commission this article nor give us any incentive for writing it. We just want to share our experience.</p>
]]></content:encoded></item></channel></rss>