<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>James Brooks</title>
        <link>https://james.brooks.page</link>
        <description>I’m James Brooks. I build epic developer tools.</description>
        <lastBuildDate>Wed, 06 May 2026 08:09:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>James Brooks</title>
            <url>https://james.brooks.page/favicon.ico</url>
            <link>https://james.brooks.page</link>
        </image>
        <copyright>All rights reserved 2026</copyright>
        <item>
            <title><![CDATA[Announcing Berroku]]></title>
            <link>https://james.brooks.page/blog/announcing-berroku</link>
            <guid>https://james.brooks.page/blog/announcing-berroku</guid>
            <pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Sudoku, meet Minesweeper. A berry-themed logic puzzle game I built with Claude Code, now live on the App Store.]]></description>
            <content:encoded><![CDATA[<p>I&#x27;ve been quietly working on something for the last few months, and today I&#x27;m finally shipping it: <a href="https://berroku.com">Berroku</a>, a berry-themed logic puzzle for iPhone and iPad. Think Sudoku, but with a sprinkle of Minesweeper.</p>
<img alt="The Berroku home screen, showing today&#x27;s daily puzzles and the current streak" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fhome.9cf6eb7d.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fhome.9cf6eb7d.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fhome.9cf6eb7d.png&amp;w=3840&amp;q=75" width="1320" height="2868" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<h2>What is Berroku?</h2>
<p>Sudoku gives you the grid. Minesweeper gives you the clues. Berroku takes both, swaps the numbers for berries, and turns the result into the kind of puzzle I find myself reaching for on the train or in the queue at a coffee shop.</p>
<p>The rules are simple:</p>
<ul>
<li>Place exactly <strong>3 berries</strong> in every row.</li>
<li>Place exactly <strong>3 berries</strong> in every column.</li>
<li>Place exactly <strong>3 berries</strong> in every block.</li>
<li>Numbered clues tell you how many of the 8 surrounding cells contain a berry.</li>
</ul>
<p>The numbers are your insight into the grid, and chasing them down to a solution is genuinely satisfying.</p>
<img alt="A Berroku puzzle in its starting state, with numbered clues scattered across the grid" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpuzzle.9d1b87a2.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpuzzle.9d1b87a2.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpuzzle.9d1b87a2.png&amp;w=3840&amp;q=75" width="1320" height="2868" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<p>Three new puzzles drop every day for free, across <strong>Standard</strong>, <strong>Advanced</strong>, and <strong>Expert</strong> difficulties. There&#x27;s a streak system, Game Center achievements and leaderboards, a home screen widget, and full offline play. If you really get the bug, there&#x27;s a one-time Pro unlock that opens up more puzzles.</p>
<img alt="A Berroku puzzle mid-solve, with berries placed and the surrounding clues partially satisfied" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpuzzle-gameplay.4d5502b3.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpuzzle-gameplay.4d5502b3.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpuzzle-gameplay.4d5502b3.png&amp;w=3840&amp;q=75" width="1320" height="2868" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<h2>Built with Claude Code</h2>
<p>Here&#x27;s the part that I think is most interesting: I built almost all of Berroku with <a href="https://claude.com/claude-code">Claude Code</a>, with just a sprinkling of handwritten changes where I wanted to nudge things in a particular direction.</p>
<p>I used the <strong>Opus 4.7 (1M Context)</strong> model throughout. The 1M context window earned its keep on a project like this — the iOS app, the marketing site, and the App Store metadata all sat comfortably in scope without me having to shuffle things around.</p>
<p>It wasn&#x27;t a single prompt or a single sitting. The UI went through a handful of looks before I settled on something that felt right on a small iPhone screen and on iPad. The widget, achievements, and Game Center integration each needed their own focused passes. My role through all of it felt more like a director than an author.</p>
<h2>Relearning App Store Connect</h2>
<p>The one part of this project that absolutely was not vibes-and-AI was App Store Connect. I haven&#x27;t shipped an iOS app in years, and walking back into it in 2026 was its own little adventure — provisioning profiles, capabilities, screenshots at the right device sizes, privacy declarations, the lot.</p>
<p>I got there in the end, but if you&#x27;re dipping your toes back into iOS after a long break: budget more time for the paperwork than you think you need. The code is the easy bit now.</p>
<h2>Launching on Product Hunt</h2>
<p>Berroku is also live on <a href="https://www.producthunt.com/products/berroku">Product Hunt</a> today. If you&#x27;d like to try it, leave feedback, or just throw an upvote at it, I&#x27;d really appreciate it.</p>
<ul>
<li><a href="https://berroku.com">Website</a></li>
<li><a href="https://apps.apple.com/app/berroku/id6761375301">App Store</a></li>
<li><a href="https://www.producthunt.com/products/berroku">Product Hunt</a></li>
</ul>
<p>If you spot something weird, have an idea for a feature, or just want to brag about your streak, please <a href="https://x.com/jbrooksuk">let me know</a>. And if you end up building something with Claude Code off the back of this, I&#x27;d genuinely love to hear about it.</p>
<p>Happy berry-placing.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Getting into public speaking]]></title>
            <link>https://james.brooks.page/blog/getting-into-public-speaking</link>
            <guid>https://james.brooks.page/blog/getting-into-public-speaking</guid>
            <pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Here are ten things I learnt about public speaking.]]></description>
            <content:encoded><![CDATA[<p>Inspired by a post by <a href="https://dylanbeattie.net/2025/12/08/so-you-want-to-speak-at-software-conferences.html">Dylan Beattie</a>, I wanted to share my own experience and advice.</p>
<p>At the end of 2022, I woke up with a singular decision: I was going to start public speaking. Up until then, I’d only given a couple of meetup talks and a slot at a Laracon US &quot;Science Fair&quot;, but I had never been on a big stage. Within a few months of that decision, I went from speaking at meetups with 40 people to standing in front of crowds of over a thousand.</p>
<p>After giving a dozen talks around the world over the last few years, here are the ten things I’ve learned.</p>
<h2>Preparation &amp; Logistics</h2>
<ol>
<li>
<p><strong>Start small.</strong> Don’t underestimate the power of local meetups. Giving both technical and soft talks in smaller, safer environments was crucial for building my confidence. It proved to me that I had the ability to deliver before the stakes got high.</p>
</li>
<li>
<p><strong>Practice is a form of respect.</strong> Holding an audience’s attention for 30–60 minutes is a privilege, and you should give them your best. You can always tell when a speaker is surprised their submission was accepted and hasn’t rehearsed. Even if I’m giving a talk for the fifth time, I practice relentlessly.</p>
</li>
<li>
<p><strong>Everything is a story.</strong> When you’re on stage, you are telling a story, even if you are live-coding. Think about the arc: What is the start? What is the end? How do you get there? Avoid jumping around or trying to tell too many stories at once. Keep the thread clear.</p>
</li>
<li>
<p><strong>Big fonts. Bigger still.</strong> This is especially important when live-coding. Make the font as big as you possibly can without losing context, then go one step further. I’ve watched <a href="https://x.com/enunomaduro">Nuno Maduro</a> during a tech check increase the font to a size he was happy with, and then on the day, he increased it further still. Go big!</p>
</li>
</ol>
<h2>On The Stage</h2>
<ol start="5">
<li>
<p><strong>Take off your lanyard.</strong> It sounds minor, but it matters. Lanyards spoil pictures and bounce around distractingly while you are exploring the stage. Ditch it before you walk on.</p>
</li>
<li>
<p><strong>Start with a joke.</strong> I don’t feel truly comfortable on stage until the audience cracks a smile, or better yet, a laugh. If it’s at my expense, even better. It breaks the tension, and at that moment, I feel like they are on my side.</p>
</li>
<li>
<p><strong>Own the stage.</strong> Even if there is a lectern, don’t hide behind it immediately. Start in the middle of the stage, introduce yourself, pace a little, enjoy the moment, and take it all in. You’ll find you become much more comfortable once you’ve physically explored the space. Also, during a tech check (where you test your screen connects and everything looks good) I like to stand on the stage and take it all in with no eyes on you.</p>
</li>
<li>
<p><strong>It will be different on the day.</strong> You won’t nail your script perfectly, and that’s absolutely fine. You will deviate, you will ad-lib, or you might reference a previous speaker who gave you food for thought. Embrace that. Being human makes the talk better.</p>
</li>
<li>
<p><strong>Be yourself, but also be a performer.</strong> Giving a talk is ultimately a stage performance. You have to put yourself out there and become a slightly exaggerated version of yourself, speaking louder and with more energy than usual. Enjoy the show.</p>
</li>
</ol>
<h2>The Most Important Lesson</h2>
<ol start="10">
<li><strong>Nobody wants to see you fail.</strong> My friend <a href="https://x.com/freekmurze">Freek van der Herten</a> shared this advice with me at Laracon India, and it is the single most comforting thing to remember. Nobody wants to see you go on stage and crash out. The audience is rooting for you to succeed.</li>
</ol>
<h2>Bonus Lesson</h2>
<ol start="11">
<li><strong>Take water with you.</strong> The &quot;cotton mouth&quot; is real. Some speakers mark specific slides in their deck as reminders to take a sip. I’ve seen speakers have coughing fits with no water nearby, and it is painful to watch. Beyond hydration, the water bottle is a tactical tool; it forces you to slow down, take a breath, and find your place again. Side note: yes, it feels weird drinking while hundreds of eyes stare at you, but do it anyway.</li>
</ol>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Supercharging Rate Limiting with Cloudflare]]></title>
            <link>https://james.brooks.page/blog/supercharging-rate-limiting-with-cloudflare</link>
            <guid>https://james.brooks.page/blog/supercharging-rate-limiting-with-cloudflare</guid>
            <pubDate>Mon, 06 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Enhance Laravel’s rate limiting by using Cloudflare’s Web Application Firewall (WAF) to block excessive requests at the edge, reducing server load and improving API performance.]]></description>
            <content:encoded><![CDATA[<p>Recently on <a href="https://forge.laravel.com">Laravel Forge</a>, we’ve been seeing an increase in the number of malicious, unauthenticated, or throttled requests hitting various API endpoints. Thanks to our internal testing of <a href="https://nightwatch.laravel.com">Laravel Nightwatch</a>, we’ve been able to detect and handle these quickly. However, while we use <a href="https://laravel.com/docs/11.x/rate-limiting">Laravel’s rate limiting</a> functionality, our web servers still process these requests before ultimately rejecting them. These &quot;dead requests&quot; contribute to unnecessary server load, which can lead to performance issues or even downtime.</p>
<p>To mitigate this, we’ve enhanced our approach by combining Laravel’s rate limiting with Cloudflare’s rate limiting via its Web Application Firewall (WAF). This allows us to reject requests at the edge—before they even reach our servers — reducing server load and keeping our API endpoints responsive.</p>
<p>In this article, I’ll show you how we’ve implemented this and how you can do the same.</p>
<h2>What is Rate Limiting?</h2>
<p>Rate limiting is a technique used to control the number of requests a client can make within a specific time period. Essentially, it <strong>limits</strong> the <strong>rate</strong> at which requests can be made. This helps prevent abuse, such as denial of service attacks, and ensures that your server remains responsive.</p>
<p>Typically, rate limiting works by returning <code>x-ratelimit-*</code> headers in the response, which include details about the current limit, remaining requests, and reset time. Laravel uses this method, and we can leverage it to instruct Cloudflare to enforce rate limiting at the edge.</p>
<h2>Rate Limiting on the Edge</h2>
<p><a href="https://developers.cloudflare.com/waf/rate-limiting-rules/">Cloudflare’s rate limiting</a> is found under the Security / WAF section of the Cloudflare dashboard. It allows you to set up rules that block requests exceeding a certain threshold before they hit your server.</p>
<h3>Setting Up Cloudflare Rate Limiting</h3>
<ol>
<li>
<p><strong>Create a New Rule:</strong></p>
<ul>
<li><strong>Field:</strong> URI Path</li>
<li><strong>Operator:</strong> Wildcard</li>
<li><strong>Value:</strong> <code>/api/*</code></li>
</ul>
</li>
<li>
<p><strong>Match Unique Requests:</strong></p>
<ul>
<li>Enable &quot;Use Custom Counting Expression&quot;</li>
<li><strong>Header Value:</strong> <code>authorization</code></li>
<li><strong>Field:</strong> Response Header</li>
<li><strong>Name:</strong> <code>x-ratelimit-remaining</code></li>
<li><strong>Operator:</strong> <code>equals</code></li>
<li><strong>Value:</strong> <code>0</code></li>
</ul>
<p>Alternatively, you can use an expression:</p>
<pre><code>(any(http.response.headers[&quot;x-ratelimit-remaining&quot;][*] eq &quot;0&quot;))
</code></pre>
</li>
<li>
<p><strong>Set the Rate Limit Trigger:</strong></p>
<ul>
<li>Configure Cloudflare to block requests after a specified threshold. For example, if your application allows 60 requests per minute, Cloudflare can block the 61st request instantly.</li>
</ul>
</li>
<li>
<p><strong>Define the Response:</strong></p>
<ul>
<li><strong>Action:</strong> Block</li>
<li><strong>Response Type:</strong> Custom JSON</li>
<li><strong>Response Code:</strong> 429</li>
<li><strong>Response Body:</strong></li>
</ul>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span> <span class="token string">&quot;Too many attempts.&quot;</span><span class="token punctuation">}</span>
</code></pre>
</li>
<li>
<p><strong>Set a Block Duration:</strong></p>
<ul>
<li>Any request that exceeds the threshold during this period will be automatically blocked.</li>
</ul>
</li>
</ol>
<h2>Why Cloudflare?</h2>
<p>Rate limiting ultimately relies on respect — there’s nothing technically stopping a client from making additional requests beyond the limit. However, by using Cloudflare’s WAF, we enforce rate limiting <strong>before</strong> our servers ever process the request, preventing unnecessary load and improving overall system stability.</p>
<p>By implementing rate limiting at the edge, we reduce “dead requests” and ensure our API remains available and performant. If you&#x27;re experiencing high server load due to excessive API requests, pairing Laravel’s rate limiting with Cloudflare’s WAF can be a super quick and effective solution.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[2024 Recap]]></title>
            <link>https://james.brooks.page/blog/2024-recap</link>
            <guid>https://james.brooks.page/blog/2024-recap</guid>
            <pubDate>Wed, 01 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Looking back over 2024.]]></description>
            <content:encoded><![CDATA[<p>With 2024 now at an end, I’ve taken a moment to reflect on what has been an incredible and transformative year both personally and professionally.
Here are some of the highlights:</p>
<h2>Laser Eye Surgery</h2>
<p>This was truly a life-changing experience. I’m amazed at how much it’s improved my day-to-day life, especially while travelling.</p>
<p>If you’re in the UK and thinking about surgery, feel free to message me—I’m happy to share my experience!</p>
<h2>Speaking</h2>
<p>I had the privilege to continue speaking at conferences around the world, including; Laracon EU, Laracon India, and PHP UK.
It was an incredible experience to share knowledge, connect with the community, and learn from so many talented people.</p>
<p>In June, I helped organise and co-host <a href="https://laravellive.uk">Laravel Live UK</a> alongside my good friend <a href="https://joedixon.co.uk">Joe Dixon</a>
was such a cool experience and a great way to connect with the UK Laravel community.</p>
<h2>Career Growth</h2>
<p>This year, I was promoted to Engineering Team Lead at Laravel, responsible for Envoyer, Forge and Vapor, while working
with a fantastic team of five developers.</p>
<p>Later this year we&#x27;ll be announcing a series of exciting updates to <a href="https://forge.laravel.com">Forge</a>.</p>
<h2>Cachet 3.x Progress</h2>
<p>Development on Cachet 3.x continued strong this year. Here’s what’s new:</p>
<ul>
<li>Comprehensive <a href="https://docs.cachethq.io">new documentation</a> and <a href="https://docs.cachethq.io/api-reference">API reference</a></li>
<li>Integration with OhDear</li>
<li>Theming and customization</li>
<li>Many more features in the works!</li>
</ul>
<h2>PHP Stoke Meetups</h2>
<p>This year, I hosted 4 <a href="https://phpstoke.co.uk">PHP Stoke</a> meetups. It’s always inspiring to see developers coming together to learn and share ideas.</p>
<h2>Fitness Journey</h2>
<p>In <a href="/blog/2022-recap">2022</a>, I started running. Unfortunately, my &quot;career&quot; was cut short due to shin splints. I switched
to spinning classes and then to strength training. I’ve been enjoying the gym, and feeling stronger and better than ever.
I&#x27;ve also been working with a PT to really push myself and see what I can achieve.</p>
<h2>Previous Recaps</h2>
<ul>
<li><a href="/blog/2022-recap">2022</a></li>
<li><a href="/blog/2023-recap">2023</a></li>
</ul>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[A Poem Of Grief]]></title>
            <link>https://james.brooks.page/blog/a-poem-of-grief</link>
            <guid>https://james.brooks.page/blog/a-poem-of-grief</guid>
            <pubDate>Mon, 18 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A poem I wrote shortly after the passing of my brother and the birth of my first child.]]></description>
            <content:encoded><![CDATA[<div class="rounded-md bg-yellow-50 p-4"><div class="flex"><div class="ml-3"><div class="text-sm text-yellow-700"><strong class="text-yellow-800">Warning:</strong> This post contains themes of grief and loss.</div></div></div></div>
<p>While searching through my Notes app yesterday, I came across a poem that I’d written 6ish years ago.
I was in two minds of posting this, but I think it’s important to share the rawness of grief.</p>
<p>It was written shortly after the passing of my brother and the birth of my first child. My world was a blur, and I was struggling
to comprehend and process the very mixed, raw emotions I was feeling.</p>
<p>My therapist had suggested I try experimenting with different mediums as a way to process what I was experiencing.
I don’t remember much about writing this, but I remember thinking it wasn’t great.
Ultimately, I ended up creating <a href="https://happydev.fm">Happy Dev</a> as a way to express myself and process my own grief.</p>
<blockquote>
<p>In a car, speeding to the hospital,<br/>
Did I hear that right? The news felt impossible.<br/>
Heart pounding, hands gripping the wheel tight,<br/>
Fighting the dark, chasing the light.</p>
<p>We walk through the doors, my head is spinning,<br/>
&quot;Is he okay?&quot; I wonder, my chest tightening.<br/>
Then three words hit like a hammer to my head:<br/>
&quot;Your brother, he&#x27;s dead.&quot;</p>
<p>The spinning stops. The world caves in,<br/>
A broken heart where hope had been.<br/>
He&#x27;s not alright — his fight is done,<br/>
My brother just eighteen, is gone.</p>
<p>I see my dad, stooped by the bed,<br/>
Holding his son&#x27;s hand, his grief unsaid.<br/>
From birth to death, eighteen years flash by,<br/>
I see my brother&#x27;s face and cry.<br/>
He&#x27;s not even in peace on the day he dies.</p>
<p>I organise everything, as much as I can,<br/>
Because how do parents bury their child with a plan?<br/>
I call the funeral director; I see the sums,<br/>
Even in death, we owe the man.</p>
<p>The days blur; my brother still waits.<br/>
A funeral looms, held hostage by dates.<br/>
We play his favourite band and I take mum’s hand,<br/>
To tell her the truth, something impossible to say —<br/>
you can no longer see your boy today.</p>
<p>All the while, I&#x27;m about to become a dad.<br/>
How can a father find joy with grief so mad?<br/>
Joy and grief wrestle, tearing me apart,<br/>
Two worlds colliding, one breaking heart.</p>
<p>My daughter is born, just one month from the loss,<br/>
A fragile new life, a line I must cross.<br/>
A life for a life — some cosmic mistake,<br/>
Not a trade I would make, not a trade I could take.</p>
</blockquote>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[tweet.new]]></title>
            <link>https://james.brooks.page/blog/tweet-new</link>
            <guid>https://james.brooks.page/blog/tweet-new</guid>
            <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[I bought a .new domain to simplify tweeting—say hello to tweet.new, your shortcut to posting faster on X!]]></description>
            <content:encoded><![CDATA[<p>Last night I did something crazy... I bought <code>tweet.new</code>. Yep, I know we&#x27;re not <em>supposed</em> to call it &quot;tweeting&quot;,
it&#x27;s now &quot;posting&quot;, but let&#x27;s be honest, we all do it.</p>
<h2>What is <code>.new</code>?</h2>
<blockquote>
<p>.new is a domain extension exclusively for performing new actions online: any act that leads to creation can have a
quick and memorable .new shortcut associated with it. Help your customers take action faster. Less time clicking means more time creating.</p>
<p>— <a href="https://get.new">https://get.new</a></p>
</blockquote>
<p>There are already some really good use cases of <code>.new</code>:</p>
<ul>
<li><a href="https://doc.new">doc.new</a> - Create a new Google Doc</li>
<li><a href="https://sheet.new">sheet.new</a> - Create a new Google Sheet</li>
<li><a href="https://cal.new">cal.new</a> - Create a new Google Calendar event</li>
<li><a href="https://meeting.new">meeting.new</a> - Create a new Google Meet meeting</li>
<li><a href="https://invoice.new">invoice.new</a> - Create a Stripe invoice</li>
<li><a href="https://subscription.new">subscription.new</a> - Create a Stripe subscription</li>
<li><a href="https://repo.new">repo.new</a> - Create a new GitHub repository</li>
<li><a href="https://gist.new">gist.new</a> - Create a new GitHub Gist</li>
</ul>
<h2>Back to Twitter</h2>
<p>Anyway, back to Twitter... Did you know that you can create a link to post a tweet, <em>ahem</em>, post? Just use the following URL:</p>
<pre><code>https://x.com/intent/post?text={encoded_text}
</code></pre>
<p>So, for example, if I wanted to people to click a button that composes a tweet saying <em>&quot;I just read <a href="https://james.brooks.page/blog/tweet-new">https://james.brooks.page/blog/tweet-new</a>. Thanks @jbrooksuk!&quot;</em> the link would be:</p>
<pre><code>https://x.com/intent/post?text=I%20just%20read%20https%3A%2F%2Fjames.brooks.page%2Fblog%2Ftweet-new.%20Thanks%20%40jbrooksuk%21
</code></pre>
<p>I&#x27;ve never been able to remember that URL, so I&#x27;ve created a new one: <a href="https://tweet.new">tweet.new</a>.</p>
<p>It&#x27;s a simple redirect to the above URL, but it&#x27;s so much easier to remember. All you need to do is type <code>tweet.new</code> into your browser,
or you can append the <code>?text=</code> query parameter to the URL to pre-fill the tweet.</p>
<pre><code>https://tweet.new/?text=This%20is%20awesome%21
</code></pre>
<p>If you like this post, <a href="https://tweet.new/?text=I%20just%20read%20about%20tweet.new%20from%20%40jbrooksuk%20https%3A%2F%2Fx.com%2Fjbrooksuk%2Fstatus%2F1843914471947022830">please share it</a>!</p>
<h2>Questions</h2>
<h3>Did this really cost you $400?</h3>
<p>Yes, yes it did 👀</p>
<h3>Why did you do that?</h3>
<ol>
<li>It&#x27;s actually useful. It&#x27;s much easier to remember <code>tweet.new</code> than the intent URL.</li>
<li>I think it&#x27;s funny.</li>
<li>It&#x27;s hosted on Cloudflare and I&#x27;m using their redirect rules, so there&#x27;s no maintenance.</li>
</ol>
<h3>Can I use it?</h3>
<p>Yes! I implore you to use it, let&#x27;s make the $400 worth it 😂</p>
<h3>Can I buy it from you?</h3>
<p>Nope, sorry. I&#x27;m keeping this one. However, if you&#x27;d like to sponsor the cost, I&#x27;m definitely open to that. You
can <a href="https://github.com/jbrooksuk/tweet.new?sponsor=1">sponsor me on GitHub</a>.</p>
<p><a href="https://news.ycombinator.com/item?id=41785361">Comment on Hacker News</a>.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Secrets of the Laravel Team Talk Video]]></title>
            <link>https://james.brooks.page/blog/secrets-of-the-laravel-team-talk</link>
            <guid>https://james.brooks.page/blog/secrets-of-the-laravel-team-talk</guid>
            <pubDate>Thu, 09 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Watch my Documenting Laravel APIs talk from Laracon AU.]]></description>
            <content:encoded><![CDATA[<p>At the start of 2024, I gave a talk titled &quot;Secrets of the Laravel Team&quot; in which I talk about how <a href="https://laravel.com">Laravel</a> works as a company, how we manage projects and continue to ship the high quality you come to expect of us.</p>
<p>I‘ve given this talk at:</p>
<ul>
<li><a href="https://laracon.eu">Laracon EU</a> (where the video was recorded)</li>
<li><a href="https://laracon.in">Laracon India</a></li>
</ul>
<iframe width="714" height="402" src="https://www.youtube.com/embed/rUDTrm5nkCg?si=tMJw2LG_6lGjRtCE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Laravel Artisan Cheatsheet API]]></title>
            <link>https://james.brooks.page/blog/laravel-artisan-cheatsheet-api</link>
            <guid>https://james.brooks.page/blog/laravel-artisan-cheatsheet-api</guid>
            <pubDate>Wed, 24 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[In October 2023, I updated the Laravel Artisan Cheatsheet to Nuxt 3.x. Along with several other changes, I wanted to add a new feature to the site, an API.]]></description>
            <content:encoded><![CDATA[<p>In <a href="https://github.com/jbrooksuk/artisan.page/pull/34">October 2023</a>, I updated the <a href="https://artisan.page">Laravel Artisan Cheatsheet</a> to Nuxt 3.x.
Along with several other changes, I wanted to add a new feature to the site, an API.</p>
<h2>Versions</h2>
<p>The <code>/api/versions</code> endpoint simply lists all versions of Laravel supported by the site:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">[</span>
  <span class="token string">&quot;10.x&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;9.x&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;8.x&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;7.x&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;6.x&quot;</span><span class="token punctuation">,</span>
  <span class="token string">&quot;5.x&quot;</span>
<span class="token punctuation">]</span>
</code></pre>
<p>When Laravel 11.x is released, it will of course be available here too.</p>
<h2>Packages</h2>
<p>The <code>/api/packages</code> endpoint is similar in that it lists all packages supported by the site:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">[</span>
    <span class="token string">&quot;laravel/breeze&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/cashier&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/cashier-paddle&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/dusk&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/envoy&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/fortify&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/horizon&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/jetstream&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/passport&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/pennant&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/nova&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/octane&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/pulse&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/sail&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/sanctum&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/scout&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/socialite&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;laravel/telescope&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;livewire/livewire&quot;</span><span class="token punctuation">,</span>
    <span class="token string">&quot;inertiajs/inertia-laravel&quot;</span>
<span class="token punctuation">]</span>
</code></pre>
<p>All packages are listed by their Composer package name, not their display name. When building the documentation for each Laravel version, we&#x27;ll attempt
to install the packages listed on this endpoint. Of course, not all packages are available due to different versioning constraints.</p>
<h2>Commands</h2>
<p>To find the commands available for each version of Laravel, you may use the <code>/api/{version}</code> endpoint. For example, <code>/api/10.x</code> will return all commands for
Laravel 10.x.</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">[</span>
  <span class="token punctuation">{</span>
    <span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token string">&quot;about&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;description&quot;</span><span class="token operator">:</span><span class="token string">&quot;Display basic information about your application&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;synopsis&quot;</span><span class="token operator">:</span><span class="token string">&quot;about [--only [ONLY]] [--json]&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;definition&quot;</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token property">&quot;aliases&quot;</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token property">&quot;arguments&quot;</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token property">&quot;options&quot;</span><span class="token operator">:</span><span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        <span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token string">&quot;only&quot;</span><span class="token punctuation">,</span>
        <span class="token property">&quot;description&quot;</span><span class="token operator">:</span><span class="token string">&quot;The section to display&quot;</span><span class="token punctuation">,</span>
        <span class="token property">&quot;value_required&quot;</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation">,</span>
        <span class="token property">&quot;value_optional&quot;</span><span class="token operator">:</span><span class="token boolean">true</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span>
        <span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token string">&quot;json&quot;</span><span class="token punctuation">,</span>
        <span class="token property">&quot;description&quot;</span><span class="token operator">:</span><span class="token string">&quot;Output the information as JSON&quot;</span><span class="token punctuation">,</span>
        <span class="token property">&quot;value_required&quot;</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation">,</span>
        <span class="token property">&quot;value_optional&quot;</span><span class="token operator">:</span><span class="token boolean">false</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// More commands...</span>
<span class="token punctuation">]</span>
</code></pre>
<p>I&#x27;d love to see what you can build with this API. If you do build something, please let me know on <a href="https://x.com/jbrooksuk">X (Twitter)</a>!</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[TIL: macOS’ Hidden Gem – The "caffeinate" Command!]]></title>
            <link>https://james.brooks.page/blog/macos-caffeinate-command</link>
            <guid>https://james.brooks.page/blog/macos-caffeinate-command</guid>
            <pubDate>Mon, 11 Dec 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Did you know macOS has a built-in alternative to Caffeine? It’s called caffeinate and it’s awesome.]]></description>
            <content:encoded><![CDATA[<p>We’ve all heard of (or used) third-party applications like <a href="https://intelliscapesolutions.com/apps/caffeine">Caffeine</a> to keep our Macs awake during long-running tasks.
But did you know macOS has a built-in alternative? It’s called the <code>caffeinate</code> command, and it’s a nifty tool included right in your system.</p>
<h2>What is the <code>caffeinate</code> Command?</h2>
<p>The <code>caffeinate</code> command is a feature included in macOS that prevents the system from sleeping. You can find it in <code>/usr/bin/caffeinate</code>.
It’s essentially a way to tell your Mac, &quot;Hey, stay awake for a bit, I’ve got things to do!&quot;</p>
<h2>How Does It Work?</h2>
<p>When you use <code>caffeinate</code>, it creates assertions that alter the system’s sleep behavior. By default, it prevents idle sleep, but you can customize its behaviour with various options:</p>
<ul>
<li><code>-d</code>: Prevents the display from sleeping.</li>
<li><code>-i</code>: Prevents the system from idle sleeping.</li>
<li><code>-m</code>: Keeps the disk awake.</li>
<li><code>-s</code>: Stops the system from sleeping when connected to power.</li>
<li><code>-u</code>: Declares user activity, turning the display on and preventing sleep (with a default 5-second timeout if not specified).</li>
</ul>
<h2>Practical Example</h2>
<p>Let’s say you’re running a long build process — maybe you&#x27;re still using Gulp 👀. You can use <code>caffeinate -i npm run build</code>, and your Mac won’t go to sleep until the process is complete.
It’s simple yet incredibly effective.</p>
<h2>Why Use caffeinate?</h2>
<p>Using <code>caffeinate</code> means you no longer need to rely on third-party apps to keep your Mac awake.
It’s a built-in, powerful tool that gives you control over your system’s sleep behavior, perfect for software developers or anyone running long tasks on their Mac.</p>
<p>So next time you need your Mac to stay awake for a bit, remember this hidden gem. And, remember to stay caffeinated ☕</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Documenting Laravel APIs Talk Video]]></title>
            <link>https://james.brooks.page/blog/documenting-laravel-apis-talk</link>
            <guid>https://james.brooks.page/blog/documenting-laravel-apis-talk</guid>
            <pubDate>Fri, 01 Dec 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Watch my Documenting Laravel APIs talk from Laracon AU.]]></description>
            <content:encoded><![CDATA[<p>This year I’ve been giving a talk at various conferences and meetups. The talk is about documenting Laravel APIs and how we can utilise a package called <a href="https://scribe.knuckles.wtf">Scribe</a> to do this for us almost automatically.</p>
<p>I gave this talk at <a href="https://laracon.au">Laracon AU</a> in November and it was recorded. The video is now available on YouTube and I’ve embedded it below.</p>
<iframe width="714" height="402" src="https://www.youtube.com/embed/K-g-t99mKZU" title="James Brooks - Documenting Laravel APIs - Laracon AU 2023" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"></iframe>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Review: Three Peaks GBR Commuter 22L]]></title>
            <link>https://james.brooks.page/blog/three-peaks-commuter-22l-backpack-review</link>
            <guid>https://james.brooks.page/blog/three-peaks-commuter-22l-backpack-review</guid>
            <pubDate>Fri, 28 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A review of the Three Peaks GBR Commuter 22L backpack]]></description>
            <content:encoded><![CDATA[<img alt="Three Peaks GBR Commuter 22L backpack" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fall-black.94ba8b53.webp&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fall-black.94ba8b53.webp&amp;w=3840&amp;q=75" width="2400" height="1001" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<p>Online I’m not known for much more than being a software developer. In real life, I’m obsessed with bags, specifically backpacks. I’ve owned a lot of bags over the years, and I’ve got a lot of opinions on them. After all these years, I’m still looking for the perfect one.</p>
<blockquote>
<p><strong>Note:</strong> This is not a paid / sponsored review. All links are direct to the Three Peaks GBR website.</p>
</blockquote>
<p>A couple of months ago I purchased the <a href="https://threepeaksgbr.com/collections/all-bags?sca_ref=5317084.FPwBAY65oY">Three Peaks GBR Commuter 22L backpack</a>. Now that its had some use, I thought I’d do something different and publish a review. While I don&#x27;t have pictures of the back pack myself, I&#x27;m currently using the marketing images from Three Peaks&#x27; website.</p>
<h2>Review</h2>
<p>The <a href="https://threepeaksgbr.com/collections/all-bags?sca_ref=5317084.FPwBAY65oY">Three Peaks GBR Commuter 22L backpack</a> is an excellent choice for commuters and travelers alike. With its thoughtful design and useful features, it proves to be a reliable companion for daily journeys. I&#x27;ve been using this backpack for a while now, and here&#x27;s my take on its various aspects.</p>
<img alt="Three Peaks GBR Commuter 22L backpack in black with black tag" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fblack-black.33a9a0f0.webp&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fblack-black.33a9a0f0.webp&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fblack-black.33a9a0f0.webp&amp;w=3840&amp;q=75" width="1400" height="1874" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<h3>Pros</h3>
<ul>
<li><strong>Anti-Theft Pocket</strong>: The hidden anti-theft pocket at the rear is a practical addition, ensuring that my valuable belongings remain safe and secure during my travels. It gives me peace of mind knowing that my essentials are out of sight and reach of potential thieves. I especially like using this to store my keys / passport when travelling. I also tried storing my AirPods in there but found that I could feel them in my back more than I expected.</li>
<li><strong>Ample Space for Laptop</strong>: The well-padded rear compartment provides a snug fit for my 17&quot; laptop, protecting it from damage. I only have a 13&quot; MacBook Air, but it means there&#x27;s plenty of space even with a case.</li>
<li><strong>Built-in USB Port</strong>: The built-in USB port is incredibly convenient. Though a power bank isn&#x27;t included, it allows me to easily charge my devices on the go, making sure I stay connected throughout the day. Pair this with the a power bank such as the Anker 22000 MHa and you&#x27;re good to go on your travels.</li>
<li><strong>Eco-Friendly Construction</strong>: The use of recycled plastic PET bottles for manufacturing is a great initiative towards sustainability. The backpack&#x27;s 600D RPET material feels soft yet durable, and its water-resistant exterior is a valuable addition for outdoor adventures. Even in heavy rain, my belongings have remained dry and secure.</li>
<li><strong>Stand-Up Feature</strong>: One aspect I truly appreciate (and not seen in other bags I&#x27;ve tested) is that the backpack can stand upright by itself. This feature comes in handy when waiting in queues or during other situations when I don&#x27;t want to keep putting the bag on and off.</li>
<li><strong>Stylish Design</strong>: The sleek and simple design of the backpack, especially in the black with black tag variant which caught my eye. It not only looks smart but has also drawn several compliments from friends and stranfers alike, which is always a nice touch.</li>
<li><strong>Designed for Tide Accessories Bag Integration</strong>: Combining the backpack with the Tide Lunch Bag and Accessories Bag has been a game-changer for staying organized during my travels. The Accessories Bag provides a dedicated space for my electrical items, keeping everything tidy and easily accessible.</li>
<li><strong>Convenient Suitcase Compatibility</strong>: The inclusion of a horizontal strap on the back of the backpack is a neat touch — even if its primary purpose is to hide the anti-theft pocket. It allows me to easily slip the bag over the handle of my suitcase, making it a breeze to navigate through airports or train stations.</li>
<li><strong>Plenty of Pockets</strong>: The backpack features several internal pockets: laptop sleeve, tablet / notebook sleeve, two stitched pockets at the bottom and a zip pocket at the top for easy access. I often use the zipped pocket for my AirPods, field notes book, pen and an iPhone cable so I can plug into the USB port and charge my phone.</li>
</ul>
<img alt="Three Peaks GBR Commuter 22L backpack open" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fopen.f3691b4b.webp&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fopen.f3691b4b.webp&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fopen.f3691b4b.webp&amp;w=3840&amp;q=75" width="1400" height="1880" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<h3>Cons:</h3>
<ul>
<li><strong>Water Bottle Storage</strong>: The back pack features pocket-like sections on both sides. I&#x27;m not even sure if they are pockets? Enlarging these sections would allow me to carry a medium-sized water bottle comfortably, adding more practicality to the design without compromising the internal space which, being 22L is already limited.</li>
<li><strong>Breathable Rear Padding</strong>: Despite being described as &quot;breathable,&quot; I didn&#x27;t find the rear padding exceptional in terms of breathability during extended use.</li>
<li><strong>Slippery Internal Pockets</strong>: The two internal pockets face inwards from the outside of the bag, which is convenient. However, I noticed that my power bank often slips out of these pockets when I unzip the bag, which is a bit frustrating when it&#x27;s attached to the internal USB port.</li>
<li><strong>Chest Strap</strong>: I&#x27;ve worn the bag while walking several miles and cycling. One thing I&#x27;d love is an <em>optional</em> chest strap, to keep it snug while moving around.</li>
<li><strong>Size</strong>: I&#x27;m not sure if I&#x27;d call this a con, but I do think a 25L / 30L version of this back pack (with the issues above addressed) would make for some interesting bags.</li>
</ul>
<h2>Conclusion</h2>
<p>Overall, the <a href="https://threepeaksgbr.com/collections/all-bags?sca_ref=5317084.FPwBAY65oY">Three Peaks GBR Commuter 22L backpack</a> is a highly recommended option for commuters and travelers seeking a reliable, stylish, and eco-friendly companion. While it has some areas that could be improved, such as the side section size and rear padding, its numerous advantages make it a great choice. I find it perfect for my daily needs, and the integration with the Tide Lunch Bag and Accessories Bag adds a whole new level of convenience and organization. With a very attractive price point of £60 ($78~) and <strong>77 5-star reviews</strong>, it&#x27;s evident that many others share my positive sentiment about this backpack.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[My Git Aliases]]></title>
            <link>https://james.brooks.page/blog/my-git-aliases</link>
            <guid>https://james.brooks.page/blog/my-git-aliases</guid>
            <pubDate>Tue, 14 Feb 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Here’s how I alias Git commands and increase my productivity.]]></description>
            <content:encoded><![CDATA[<p>Did you know that you can add custom aliases to the <code>git</code> command? I recently switched to a new Macbook and realised that I was missing some commands that it turns out I was using daily. Manually restoring them showed me two things:</p>
<ol>
<li>I need to use sync my <code>dotfiles</code> repository more.</li>
<li>Git aliases are a huge part of my daily development workflow.</li>
</ol>
<p>Before you add any aliases, you’ll need a <code>.gitconfig</code> file in your home directory, (for example, <code>~/.gitconfig</code>). This file will often already exist to configure the name and email address to make commits with.</p>
<h2>My Aliases</h2>
<pre class="language-ini"><code class="language-ini"><span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">user</span><span class="token punctuation">]</span></span>
  <span class="token key attr-name">name</span> <span class="token punctuation">=</span> <span class="token value attr-value">James Brooks</span>
  <span class="token key attr-name">email</span> <span class="token punctuation">=</span> <span class="token value attr-value">james@alt-three.com</span>

<span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">core</span><span class="token punctuation">]</span></span>
  <span class="token key attr-name">editor</span> <span class="token punctuation">=</span> <span class="token value attr-value">nano</span>

<span class="token section"><span class="token punctuation">[</span><span class="token section-name selector">alias</span><span class="token punctuation">]</span></span>
  <span class="token comment">; List all branches.</span>
  <span class="token key attr-name">branches</span> <span class="token punctuation">=</span> <span class="token value attr-value">branch --format=&#x27;%(HEAD) %(color:yellow)%(refname:short)%(color:reset) - %(contents:subject) %(color:green)(%(committerdate:relative)) [%(authorname)]&#x27; --sort=-committerdate</span>

  <span class="token comment">; Delete a branch.</span>
  <span class="token key attr-name">del</span> <span class="token punctuation">=</span> <span class="token value attr-value">branch -D</span>

  <span class="token comment">; Show the last commit.</span>
  <span class="token key attr-name">last</span> <span class="token punctuation">=</span> <span class="token value attr-value">log -1 HEAD --stat</span>

  <span class="token comment">; Undo the last commit.</span>
  <span class="token key attr-name">undo</span> <span class="token punctuation">=</span> <span class="token value attr-value">reset HEAD~1 --mixed</span>

  <span class="token comment">; Add and commit everything with &quot;wip&quot; as the commit message.</span>
  <span class="token key attr-name">wip</span> <span class="token punctuation">=</span> <span class="token value attr-value">!git add --all &amp;&amp; git commit -m &#x27;wip&#x27;</span>
</code></pre>
<p>As you can see, I don’t actually have a lot of aliases. I’ve tried to keep it simple and only have aliases that I use regularly. I also don’t add aliases to shorten existing commands (without passing options).</p>
<h2>Adding Aliases</h2>
<p>To add an alias, we need to append commands to the <code>[alias]</code> section of our config file. The format is <code>aliasName = aliasCommand</code>.</p>
<p>You may also add an alias using the <code>git config</code> command:</p>
<pre class="language-shell"><code class="language-shell">$ <span class="token function">git</span> config --global alias.undo <span class="token string">&#x27;reset HEAD~1 --mixed&#x27;</span>
</code></pre>
<p>There are a few things to know about when creating Git aliases:</p>
<ol>
<li>Aliases can run more than just one command. Take a look at the <code>wip</code> alias; it calls <code>git add --all</code> and then <code>git commit -m &#x27;wip&#x27;</code>. You can also call other Git subcommands, such as <code>git log</code> or <code>git status</code>.</li>
<li>You may’ve noticed that the <code>undo</code> alias does not call the <code>git</code> subcommand, whilst <code>wip</code> does. For aliases that simply run another Git command but with additional parameters, this is absolutely fine. However, if you want to run a command that is not a Git subcommand (or you&#x27;re running multiple commands), you should prefix it with <code>!</code>. This stops Git from trying to run the command as a subcommand.</li>
<li>Aliases don&#x27;t <em>need</em> to run a Git subcommand. You could run another application for example, <code>visual = !gitk</code>.</li>
<li>You could use an alias to make a shortcut for another command, for example <code>p = push</code> so you can now run <code>git p</code>.</li>
</ol>
<blockquote>
<p>You can learn more about how aliases work at the <a href="https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases">Git Aliases documentation</a>.</p>
</blockquote>
<p>Are you using aliases? Let me know <a href="https://twitter.com/jbrooksuk">@jbrooksuk</a> or <a href="https://phpc.social/@james">@james@phpc.social</a>.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Extending Laravel’s "about" Command]]></title>
            <link>https://james.brooks.page/blog/extending-laravel-about-command</link>
            <guid>https://james.brooks.page/blog/extending-laravel-about-command</guid>
            <pubDate>Mon, 13 Feb 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Let’s take a look into Laravel’s "about" command and how we can extend it to add our own application / package information.]]></description>
            <content:encoded><![CDATA[<p>Last year, I contributed a new <a href="https://laravel.com/docs/9.x/packages#about-artisan-command"><code>about</code></a> command to the Laravel framework. This command prints out information about your application:</p>
<pre><code>$ php artisan about

Environment .........................................................  
Application Name .............................................. Forge  
Laravel Version .............................................. 9.51.0  
PHP Version ................................................... 8.2.1  
Composer Version .............................................. 2.5.1  
Environment ................................................... local  
Debug Mode .................................................. ENABLED  
URL ...................................................... forge.test  
Maintenance Mode ................................................ OFF  

Cache ...............................................................  
Config ................................................... NOT CACHED  
Events ................................................... NOT CACHED  
Routes ................................................... NOT CACHED  
Views ........................................................ CACHED  

Drivers .............................................................  
Broadcasting ................................................. pusher  
Cache .......................................................... file  
Database ...................................................... mysql  
Logs .......................................................... daily  
Mail ........................................................... smtp  
Queue ......................................................... redis  
Session .................................................... database  
</code></pre>
<h2>Extra things you can do with this command</h2>
<p>Using the <code>--only</code> option, you may specify a comma-separated list of sections to display:</p>
<pre><code>$ php artisan about --only=cache,drivers

Cache ...............................................................  
Config ................................................... NOT CACHED  
Events ................................................... NOT CACHED  
Routes ................................................... NOT CACHED  
Views ........................................................ CACHED  

Drivers .............................................................  
Broadcasting ................................................. pusher  
Cache .......................................................... file  
Database ...................................................... mysql  
Logs .......................................................... daily  
Mail ........................................................... smtp  
Queue ......................................................... redis  
Session .................................................... database 
</code></pre>
<p>Of course, you can also get the information in JSON format using the <code>--json</code> option:</p>
<pre class="language-shell"><code class="language-shell">$ php artisan about --json

<span class="token punctuation">{</span><span class="token string">&quot;environment&quot;</span>:<span class="token punctuation">{</span><span class="token string">&quot;application_name&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;Forge&quot;</span>,<span class="token string">&quot;laravel_version&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;9.51.0&quot;</span>,<span class="token string">&quot;php_version&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;8.2.1&quot;</span>,<span class="token string">&quot;composer_version&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;2.5.1&quot;</span>,<span class="token string">&quot;environment&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;local&quot;</span>,<span class="token string">&quot;debug_mode&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;ENABLED&quot;</span>,<span class="token string">&quot;url&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;forge.test&quot;</span>,<span class="token string">&quot;maintenance_mode&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;OFF&quot;</span><span class="token punctuation">}</span>,<span class="token string">&quot;cache&quot;</span>:<span class="token punctuation">{</span><span class="token string">&quot;config&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;NOT CACHED&quot;</span>,<span class="token string">&quot;events&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;NOT CACHED&quot;</span>,<span class="token string">&quot;routes&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;NOT CACHED&quot;</span>,<span class="token string">&quot;views&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;CACHED&quot;</span><span class="token punctuation">}</span>,<span class="token string">&quot;drivers&quot;</span>:<span class="token punctuation">{</span><span class="token string">&quot;broadcasting&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;pusher&quot;</span>,<span class="token string">&quot;cache&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;file&quot;</span>,<span class="token string">&quot;database&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;mysql&quot;</span>,<span class="token string">&quot;logs&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;daily&quot;</span>,<span class="token string">&quot;mail&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;smtp&quot;</span>,<span class="token string">&quot;queue&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;redis&quot;</span>,<span class="token string">&quot;session&quot;</span><span class="token builtin class-name">:</span><span class="token string">&quot;database&quot;</span><span class="token punctuation">}</span><span class="token punctuation">}</span>
</code></pre>
<h2>Appending your own information</h2>
<p>What you may not know is that you can also add your own application or package information into the command’s output. For package developers, this allows you to share additional information such as drivers, versions etc. and for application developers, this allows you to add application-specific information.</p>
<p>To share information with the <code>about</code> command, you need to update your service provider’s <code>boot</code> method:</p>
<pre class="language-php"><code class="language-php"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Foundation<span class="token punctuation">\</span>Console<span class="token punctuation">\</span>AboutCommand</span><span class="token punctuation">;</span>
 
<span class="token doc-comment comment">/**
 * Bootstrap any application services.
 *
 * <span class="token keyword">@return</span> <span class="token class-name"><span class="token keyword">void</span></span>
 */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">boot</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token scope">AboutCommand<span class="token punctuation">::</span></span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;My Package&#x27;</span><span class="token punctuation">,</span> <span class="token keyword">fn</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token string single-quoted-string">&#x27;Version&#x27;</span> <span class="token operator">=&gt;</span> <span class="token string single-quoted-string">&#x27;1.0.0&#x27;</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Yes, that’s all it takes.</p>
<p>Firstly, we pass through the &quot;section&quot; name. This is how the command will separate the output and of course, we can add to the default sections by passing the same names.</p>
<pre><code>$ php artisan about --only=my_package

My Package ..........................................................  
Version ....................................................... 1.0.0
</code></pre>
<blockquote>
<p>Notice how we can also specify <code>my_package</code> in the <code>--only</code> option?</p>
</blockquote>
<p>Next, we&#x27;re passing a callable that returns an array of key/value pairs for information add. Alternatively, we can pass an array of data, However, when passing a callable, Laravel will only evaluate the information <em>when</em> it executes the <code>about</code> command. This is a performance optimisation to prevent applications from <em>always</em> loading the information, even when it’s not needed.</p>
<p>Some packages are already extending the <code>about</code> command:</p>
<ul>
<li><a href="https://github.com/statamic/cms/blob/cd72c923a893eb618ad36098a324f53b4882857e/src/Providers/AppServiceProvider.php#L180-L186">Statamic</a></li>
<li><a href="https://github.com/filamentphp/filament/blob/67e483e3bae66646c6d62a12d4f7f60790e680c2/packages/support/src/SupportServiceProvider.php#L79-L94">Filament</a></li>
<li><a href="https://github.com/orchestral/testbench-dusk/blob/00655f8d71389e8bd9c391fd7f2a5c8eb5c9463d/src/Foundation/TestbenchServiceProvider.php#L20-L24"><code>orchestral/testbench-dusk</code></a></li>
</ul>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[2022 Recap]]></title>
            <link>https://james.brooks.page/blog/2022-recap</link>
            <guid>https://james.brooks.page/blog/2022-recap</guid>
            <pubDate>Sun, 01 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[With 2022 already being all but a distant memory, I thought it’d be nice to look back at the last year and reflect on the year long journey.]]></description>
            <content:encoded><![CDATA[<p>With 2022 already being all but a distant memory, I thought it’d be nice to look back at the last year and reflect on the year long journey.</p>
<h2>Laravel</h2>
<p><a href="https://forge.laravel.com">Laravel Forge</a> saw a lot of updates in 2022. Here are just <em>some</em> of the highlights:</p>
<ul>
<li><a href="https://blog.laravel.com/laravel-forge-command-palette">Command Palette</a></li>
<li><a href="https://blog.laravel.com/manage-composer-credentials-with-laravel-forge">Manage Composer Credentials</a></li>
<li><a href="https://blog.laravel.com/forge-ubuntu-2204-support">Ubuntu 22.04 Support</a></li>
<li><a href="https://blog.laravel.com/laravel-forge-realtime-deployment-output">Real-Time Deployment Output</a></li>
<li><a href="https://blog.laravel.com/ditching-google-analytics-for-fathom">We ditched Google Analytics for Fathom Analytics</a></li>
<li><a href="https://blog.laravel.com/forge-dark-mode">Dark Mode</a></li>
<li><a href="https://blog.laravel.com/forge-circle-permissions">Circle Permissions</a></li>
<li><a href="https://blog.laravel.com/laravel-loves-php-82">PHP 8.2 Support</a></li>
</ul>
<p>We also shipped <a href="https://twitter.com/jbrooksuk/status/1568233126400335874">a lot</a> <a href="https://twitter.com/jbrooksuk/status/1581914514425274368">of</a> <a href="https://twitter.com/jbrooksuk/status/1500906364603875332">minor</a> <a href="https://twitter.com/jbrooksuk/status/1554469863401324547">features</a> <a href="https://twitter.com/jbrooksuk/status/1592474514138763265">and</a> <a href="https://twitter.com/jbrooksuk/status/1590992696898129920">papercuts</a> <a href="https://twitter.com/jbrooksuk/status/1599753171617730560">along</a> the way, which really excites me. We sweat the details 🥵</p>
<p>I’m really proud of the work we do on Forge and the amount of time it saves developers every single day. As a customer, I couldn’t live without it! Oh, and there is a lot more to come here 😉</p>
<p>I also contributed a couple of features to the Laravel framework too:</p>
<ul>
<li><a href="https://github.com/laravel/framework/pull/43147">Artisan <code>about</code> Command</a></li>
<li><a href="https://github.com/laravel/framework/pull/43367">New <code>db:show</code>, <code>db:table</code> and <code>db:monitor</code> Commands</a> — these were particularly fun as my friend &amp; colleague, <a href="https://twitter.com/_joedixon">Joe Dixon</a> came to visit me and we hacked on these features together.</li>
</ul>
<h2>Becoming a Laracasts Educator</h2>
<p>In June, Laracasts released an <a href="https://laracasts.com/series/learn-laravel-forge-2022-edition">updated series on Laravel Forge</a>, created by me!</p>
<img alt="Laracasts Hero" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flaracasts.4f6a9869.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flaracasts.4f6a9869.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flaracasts.4f6a9869.png&amp;w=3840&amp;q=75" width="1740" height="700" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<p>Being asked to create a complete series (there are 24 videos) for Laracasts was an honour for me. Without Laracasts, I genuinely don’t believe I’d be working at Laravel now.</p>
<p>Also, being able to create this series was eye-opening. It’s extremely rewarding, but it’s also <strong>a lot</strong> of work. I already admired Jeffrey, but I have a new-found respect for the amount of effort he (and other educators) put into creating the material we take for granted.</p>
<h2>Side Projects</h2>
<p>I’ve continued working on <a href="https://checkmango.com">Checkmango</a>, the full-stack A/B testing platform I’ve been working on. Progress has been slow as I’ve been focused on lots of things in 2022, but it’s moving forward and that is all that matters.</p>
<p>The <a href="https://artisan.page">Laravel Artisan Cheatsheet</a> saw a few updates, mostly to improve SEO. This year, I’d love to give it a visual refresh and upgrade to Nuxt.js v3.</p>
<p>I also released the <a href="https://polestar-finder.brooks.page">Polestar Finder</a>, a small web-app that notifies you when your Polestar 2 configuration becomes available. It was pretty popular on the Reddit and Facebook groups, and notified over 100 people that their configuration became available.</p>
<h2>Speaking</h2>
<p>I didn’t give any talks in 2022, but I did finally pluck up enough courage to submit a proposal to <a href="https://laracon.in">Laracon India</a>, which was accepted!</p>
<p>I also started a new meet-up, <a href="/blog/php-stoke">PHP Stoke</a> which will have its first meeting on the 14th January. PHP Stoke will be the first meet-up I’ve organised and my first talk of 2023.</p>
<p>Although I won’t be speaking at all of them, I’ll be attending:</p>
<ul>
<li><a href="https://php-stoke.eventbrite.com">PHP Stoke</a></li>
<li><a href="https://laracon.eu">Laracon EU</a></li>
<li><a href="https://laracon.in">Laracon India</a></li>
<li><a href="https://laracon.us">Laracon US</a></li>
</ul>
<p>I’d love to attend more meet-ups this year. If you’re organising a meet-up, please <a href="https://twitter.com/jbrooksuk">let me know</a> and I’ll do my best to attend.</p>
<h2>Education</h2>
<p>I completed the St John’s Ambulance Mental Health Workplace Responder course in August. A good friend of mine asked whether I’d like to do the course and hold the position of &quot;Mental Health Responder&quot; for his business, which I jumped at the chance to do.</p>
<p>Aside from providing me with the skills for my friend, I also very much intend on restarting the <a href="https://happydev.fm">Happy Dev</a> podcast. <em>I know, I’ve said this a few times now, but it really is true!</em></p>
<h2>Running</h2>
<p>2022 was the year that I really got into running. <a href="https://www.strava.com/athletes/57029399">Strava</a> says I ran a total of <strong>165.7km</strong> over <strong>38 activities</strong> and I spent <strong>15 hours</strong> running... This is well over 100km more than I’ve run for any previous year.</p>
<p>I started attending our local park run (a 5k run) and that really got me going. I ran <strong>14 park runs</strong> in 2022. Unfortunately, I did injure myself by pushing too hard, too quickly around August time and that led to shin splints which really set me back — I’ve learnt from that now.</p>
<p>For anyone interested, my current PBs:</p>
<ul>
<li>5k: 25:07</li>
<li>10k: 57:13</li>
</ul>
<p>I’ve also started 2023 by signing up for my first half-marathon (<a href="https://www.stoke.gov.uk/info/20035/sports/397/hanley_economic_potters_arf_marathon_2023">Potters ’Arf</a>). The Potters ’Arf marathon is notorious for its hills and difficulty, so the training begins now!</p>
<h2>Conclusion</h2>
<p>These are just some of my 2022 highlights. I have a strong feeling that 2023 will be an even bigger year, packed full of excitement. I’m travelling a lot more this year and I can’t wait to see your beautiful faces.</p>
<h2>Your Recaps</h2>
<p>If you’ve written a 2022 recap, <a href="https://twitter.com/jbrooksuk/status/1610246263311683584">let me know</a> and I’ll add it here:</p>
<ul>
<li><a href="https://nunomaduro.com/a_recap_of_2022">Nuno Maduro</a></li>
<li><a href="https://freek.dev/2400-a-recap-of-2022">Freek Van der Herten</a></li>
<li><a href="https://jasonmccreary.me/articles/2022-lookbehind-2023-lookahead/">Jason McCreary</a></li>
</ul>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Upgrading macOS with Homebrew]]></title>
            <link>https://james.brooks.page/blog/upgrading-macos-with-homebrew</link>
            <guid>https://james.brooks.page/blog/upgrading-macos-with-homebrew</guid>
            <pubDate>Thu, 24 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Updates that break Homebrew happen so frequently and I forget how to solve it, that I’ve finally caved and documented it.]]></description>
            <content:encoded><![CDATA[<p>After having my M1 Macbook Air for 2 years, I’ve upgraded to a refurbed M2.
The form factor of the 2022 model is really nice and I love the slightly larger screen space. As much as I loved the old one, I was finding that having only two
USB-C ports a pain. Now I charge <em>and</em> have two devices plugged in. I didn’t think it’d be a problem until I had started using it.</p>
<p>When it arrived, the first thing I did was install all the developer tools that I needed and got to work. But then I realised it didn’t come with Ventura,
and now it’s upgraded <a href="https://brew.sh">Homebrew</a> has fallen apart again.</p>
<pre><code>git clone git@github.com:jbrooksuk/artisan.page.git
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
</code></pre>
<p>To fix this:</p>
<pre><code>xcode-select --install
</code></pre>
<p>But sometimes, this doesn’t work and then instead, you need to run:</p>
<pre><code>sudo xcode-select --reset
</code></pre>
<p>Which should fix it.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[PHP Stoke]]></title>
            <link>https://james.brooks.page/blog/php-stoke</link>
            <guid>https://james.brooks.page/blog/php-stoke</guid>
            <pubDate>Sun, 20 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Introducing PHP Stoke, a meetup for PHP developers in Stoke-on-Trent.]]></description>
            <content:encoded><![CDATA[<p>It’s with great excitement that I can say I’m hosting my first meetup; <a href="https://php-stoke.eventbrite.com">PHP Stoke</a>.</p>
<img alt="PHP Stoke Hero" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fphpstoke.6f7d4d74.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fphpstoke.6f7d4d74.png&amp;w=3840&amp;q=75" width="2160" height="1080" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<p>PHP Stoke is a joint venture with my friends at <a href="https://awaredigital.co.uk">Aware Digital</a>. Aware are a local Magento e-commerce agency based in Stoke.</p>
<p>I’ve wanted to host a local PHP meetup for a while, and it’s through knowing Aware that we’ve been able to make this work. We’ve been thinking about how we can make this a great event and I’m confident that we’ll achieve this on the day!</p>
<p>If you’re in Stoke-on-Trent and free on January 12th then <a href="https://php-stoke.eventbrite.com">please register</a> and come say hi!</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Deployment Hook Error Handling in Envoyer]]></title>
            <link>https://james.brooks.page/blog/deployment-hook-error-handling-in-envoyer</link>
            <guid>https://james.brooks.page/blog/deployment-hook-error-handling-in-envoyer</guid>
            <pubDate>Thu, 12 Aug 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Laravel’s Envoyer service allows you to break up your deployment process into multiple steps, which makes it really easy to manage deployments. Envoyer runs each step individually, checking the exit code of the last command within the step. Because each deployment step is Bash, if it’s a non-zero exit code then it gets reported back as a failure.]]></description>
            <content:encoded><![CDATA[<p>Laravel’s <a href="https://envoyer.io">Envoyer</a> service allows you to break up your deployment process into multiple steps, which makes it really easy to manage deployments. Envoyer runs each step individually, checking the exit code of the last command within the step. Because each deployment step is Bash, if it’s a non-zero exit code then it gets reported back as a failure.</p>
<p>A question I’ve been asked a few times is &quot;why does Envoyer report this deployment step successful, when it actually failed?&quot;. Let’s look at an example of when this may happen, and then two ways we can resolve it.</p>
<h2>The Deployment Step Example</h2>
<pre class="language-bash"><code class="language-bash">php artisan migrate

<span class="token builtin class-name">echo</span> <span class="token string">&quot;Hey, beautiful reader!&quot;</span>
</code></pre>
<p>In this example, let’s say that <code>php artisan migrate</code> may fail for some reason.</p>
<h2>The Simple Solution</h2>
<p>The simplest solution is to split the deployment step into multiple steps:</p>
<p><strong>Run Migrations</strong></p>
<pre class="language-bash"><code class="language-bash">php artisan migrate
</code></pre>
<p><strong>Compliment The Reader</strong></p>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">echo</span> <span class="token string">&quot;Hey, beautiful reader!&quot;</span>
</code></pre>
<p>But this isn’t always the best solution, perhaps because we need to know the output of the response and do something else with it before reporting a failure.</p>
<h2>The Correct Solution</h2>
<p>The right way of handling this particular issue is to use more of Bash’s power, using the <code>set</code> builtin. We can update our deployment script:</p>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">set</span> -e

php artisan migrate

<span class="token builtin class-name">echo</span> <span class="token string">&quot;Hey, beautiful reader!&quot;</span>
</code></pre>
<p><code>set -e</code> tells the script to exit immediately if a command (or pipeline of commands) returns a non-zero exit code.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[A GitHub Action for Laravel Forge]]></title>
            <link>https://james.brooks.page/blog/a-github-action-for-laravel-forge</link>
            <guid>https://james.brooks.page/blog/a-github-action-for-laravel-forge</guid>
            <pubDate>Mon, 10 May 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Today I’m pleased to announce the availability of a new GitHub Action for Laravel Forge deployments.]]></description>
            <content:encoded><![CDATA[<p>Today I’m pleased to announce the availability of a new <a href="https://github.com/jbrooksuk/laravel-forge-action">GitHub Action</a> for <a href="https://forge.laravel.com">Laravel Forge</a> deployments.</p>
<p>I want to thank <a href="https://github.com/Glennmen">@Glennmen</a> for the equivalent Ploi action, as it’s heavily based on it.</p>
<p>Here is an example GitHub Workflow for deploying your application:</p>
<pre class="language-yml"><code class="language-yml"><span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">&#x27;Deploy on push&#x27;</span>

<span class="token key atrule">on</span><span class="token punctuation">:</span>
  <span class="token key atrule">push</span><span class="token punctuation">:</span>
    <span class="token key atrule">branches</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> master

<span class="token key atrule">jobs</span><span class="token punctuation">:</span>
  <span class="token key atrule">forge-deploy</span><span class="token punctuation">:</span>
    <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">&#x27;Laravel Forge Deploy&#x27;</span>
    <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest

    <span class="token key atrule">steps</span><span class="token punctuation">:</span>
      <span class="token comment"># Checkout the repository to the GitHub Actions runner</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2

      <span class="token comment"># Trigger Laravel Forge Deploy</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Deploy
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> jbrooksuk/laravel<span class="token punctuation">-</span>forge<span class="token punctuation">-</span>action@v1.0.1
        <span class="token key atrule">with</span><span class="token punctuation">:</span>
          <span class="token key atrule">trigger_url</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.TRIGGER_URL <span class="token punctuation">}</span><span class="token punctuation">}</span>
</code></pre>
<p>Please let me know if you’re using it!</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Injecting Additional Data into Laravel Queued Jobs]]></title>
            <link>https://james.brooks.page/blog/injecting-additional-data-into-laravel-queued-jobs</link>
            <guid>https://james.brooks.page/blog/injecting-additional-data-into-laravel-queued-jobs</guid>
            <pubDate>Tue, 13 Apr 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[We've shipped an enhancement to Laravel Forge that stores the ID of the user who initiated an event.]]></description>
            <content:encoded><![CDATA[<p>Earlier today we shipped an enhancement to <a href="https://forge.laravel.com/">Laravel Forge</a> that stores the ID of the user who initiated an event.</p>
<p>At first glance, you may be wondering why this is a big deal and you&#x27;re probably asking yourself why I&#x27;m writing a blog post about it. All good thoughts. Let&#x27;s dive into it!</p>
<p>In Forge, almost everything you can do to a server is handled within a queued job. Additionally, some of these jobs will dispatch other jobs via the sync connection. Forge has a <code>ServerRepository</code> class, most methods in the class are responsible for dispatching the relevant job, for example:</p>
<pre class="language-php"><code class="language-php"><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">addJob</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Server</span> <span class="token variable">$server</span><span class="token punctuation">,</span> <span class="token variable">$command</span><span class="token punctuation">,</span> <span class="token variable">$user</span><span class="token punctuation">,</span> <span class="token variable">$frequency</span><span class="token punctuation">,</span> <span class="token variable">$minute</span><span class="token punctuation">,</span> <span class="token variable">$hour</span><span class="token punctuation">,</span> <span class="token variable">$day</span><span class="token punctuation">,</span> <span class="token variable">$month</span><span class="token punctuation">,</span> <span class="token variable">$weekday</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token variable">$cron</span> <span class="token operator">=</span> <span class="token this keyword">$this</span><span class="token operator">-&gt;</span><span class="token function">formatCronExpression</span><span class="token punctuation">(</span>
        <span class="token comment">// ...</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token variable">$job</span> <span class="token operator">=</span> <span class="token variable">$server</span><span class="token operator">-&gt;</span><span class="token function">jobs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
        <span class="token comment">// ...</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token scope">Queue<span class="token punctuation">::</span></span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;Forge\Jobs\AddJob&#x27;</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>
        <span class="token string single-quoted-string">&#x27;server_id&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$server</span><span class="token operator">-&gt;</span><span class="token property">id</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;job_id&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$job</span><span class="token operator">-&gt;</span><span class="token property">id</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token variable">$job</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>The most obvious way to achieve this is to go through every single method and add a User $initiator parameter, then pass that value in every place it&#x27;s called. In the case of Forge, that would be very tedious, time consuming and not a great solution.</p>
<p>If, like me, you&#x27;ve read <a href="https://twitter.com/themsaid">Mohamed Said&#x27;s</a> <a href="https://learn-laravel-queues.com/">Laravel Queues</a> in Action book, then you already know how powerful the Queue system is.</p>
<p>The <code>Queue</code> class contains a method named <code>createPayloadUsing</code> which allows you to register a callback that is executed when creating job payloads. This is exactly what we&#x27;re after!</p>
<pre class="language-php"><code class="language-php"><span class="token scope">Queue<span class="token punctuation">::</span></span><span class="token function">createPayloadUsing</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$connection</span><span class="token punctuation">,</span> <span class="token variable">$queue</span><span class="token punctuation">,</span> <span class="token variable">$payload</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// $payload contains all of the job information, including the supplied data.</span>
    <span class="token variable">$jobData</span> <span class="token operator">=</span> <span class="token variable">$payload</span><span class="token punctuation">[</span><span class="token string single-quoted-string">&#x27;data&#x27;</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span> <span class="token keyword">isset</span><span class="token punctuation">(</span><span class="token variable">$jobData</span><span class="token punctuation">[</span><span class="token string single-quoted-string">&#x27;initiated_by&#x27;</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token variable">$jobData</span> <span class="token operator">=</span> <span class="token function">array_merge</span><span class="token punctuation">(</span><span class="token variable">$payload</span><span class="token punctuation">[</span><span class="token string single-quoted-string">&#x27;data&#x27;</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function">array_filter</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
            <span class="token string single-quoted-string">&#x27;initiated_by&#x27;</span> <span class="token operator">=&gt;</span> <span class="token function">request</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token property">id</span> <span class="token operator">??</span> <span class="token constant">null</span><span class="token punctuation">,</span>
        <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token string single-quoted-string">&#x27;data&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$jobData</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>This code is placed in the boot method of the <code>AppServiceProvider</code>.</p>
<p>We check whether the job&#x27;s payload contains a key called <code>initiated_by</code>, if not then we add it to the array with the value being the current request&#x27;s user&#x27;s id.</p>
<p>Every single job initiated by Forge automatically knows who initiated it, and we can use this value when we&#x27;re recording the event in the database.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Installing Private Repositories using GitHub Actions]]></title>
            <link>https://james.brooks.page/blog/installing-private-repositories-using-github-actions</link>
            <guid>https://james.brooks.page/blog/installing-private-repositories-using-github-actions</guid>
            <pubDate>Fri, 12 Feb 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[I've been testing out the new Laravel Spark package before it gets launched and implemented it into my new side project (more details coming soon).]]></description>
            <content:encoded><![CDATA[<p>I&#x27;ve been testing out the new <a href="https://spark.laravel.com">Laravel Spark</a> package before it gets launched and implemented it into <a href="https://twitter.com/jbrooksuk/status/1359204582656778240">my new side project</a> (more details coming soon).</p>
<p>Because I&#x27;m part of the Laravel GitHub organisation, I&#x27;m able to install the repository into my <code>composer.json</code> file like so:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
    <span class="token property">&quot;repositories&quot;</span><span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token property">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;vcs&quot;</span><span class="token punctuation">,</span>
            <span class="token property">&quot;url&quot;</span><span class="token operator">:</span> <span class="token string">&quot;git@github.com:laravel/spark-paddle.git&quot;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token property">&quot;require&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token property">&quot;laravel/spark-paddle&quot;</span><span class="token operator">:</span> <span class="token string">&quot;@dev&quot;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>This all works great and I&#x27;ve been able to use Spark Paddle just fine however, when I pushed my changes to GitHub the tests immediately started failing because of the private repository.</p>
<p>My first thought was to use the <code>${{ secrets.GITHUB_TOKEN }}</code> secret but this didn&#x27;t work. After a lot of trial and error, this is the solution I came up with:</p>
<p>First, we need to <a href="https://github.com/settings/tokens/new?scopes=repo&amp;description=GitHub%20Actions%20Composer">generate a new token</a>. This token needs full control of private repositories. You&#x27;ll need to copy this token as it won&#x27;t be displayed again.
In our repository&#x27;s secrets section, we need to create a new Repository secret. I called mine <code>COMPOSER_AUTH</code> and copy the token we generated into the value.</p>
<p>In our tests file, we need to tell Composer to use the new token:</p>
<pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install Dependencies
  <span class="token key atrule">env</span><span class="token punctuation">:</span>
    <span class="token key atrule">COMPOSER_AUTH</span><span class="token punctuation">:</span> <span class="token string">&#x27;{&quot;github-oauth&quot;: {&quot;github.com&quot;: &quot;${{secrets.COMPOSER_AUTH}}&quot;} }&#x27;</span> <span class="token comment"># [tl! **]</span>
  <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
    composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest</span>
</code></pre>
<p>That&#x27;s it. Our tests will now use the token we generated and we&#x27;ll be able to access all of the repositories that we have access to.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Laravel Form Request Tip]]></title>
            <link>https://james.brooks.page/blog/laravel-form-request-tip</link>
            <guid>https://james.brooks.page/blog/laravel-form-request-tip</guid>
            <pubDate>Thu, 28 Jan 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Over Christmas I started tinkering with a little project to learn about some of the emerging technologies and frameworks that I don't have a chance to play with day to day. For this project, I've been using Jetstream 2 and Inertia.js, which I've loved! I'd like to write a bit more about these when I get chance.]]></description>
            <content:encoded><![CDATA[<p>Over Christmas I started tinkering with a little project to learn about some of the emerging technologies and frameworks that I don&#x27;t have a chance to play with day to day. For this project, I&#x27;ve been using <a href="https://jetstream.laravel.com/">Jetstream 2</a> and <a href="https://inertiajs.com">Inertia.js</a>, which I&#x27;ve loved! I&#x27;d like to write a bit more about these when I get chance.</p>
<p>Whilst I&#x27;ve been working on this, I&#x27;ve been trying to use some of Laravel&#x27;s features that I&#x27;ve not really used much before. One of these is the <code>FormRequest</code> feature. A quick <code>php artisan make:request</code> <code>CreateCampaignRequest</code> command and all I need to do is authorize the request and add my rules — how great is that?!</p>
<p>Part of my choice in using Jetstream (aside from, well, everything about it) is that I was able to support teams out of the box. That&#x27;s great, but now my app needs to validate differently based on the team. For example, two teams can use the same &quot;Campaign&quot; name, but one team can&#x27;t have two campaigns with the same name.</p>
<p>As an aside, I always create a app/helpers.php file in my project and have started adding this to my Jetstream projects:</p>
<pre class="language-php"><code class="language-php"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span> <span class="token function">function_exists</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;current_team&#x27;</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token doc-comment comment">/**
     * Get the user&#x27;s current team.
     *
     * <span class="token keyword">@return</span> <span class="token class-name"><span class="token punctuation">\</span>App<span class="token punctuation">\</span>Models<span class="token punctuation">\</span>Team<span class="token punctuation">|</span><span class="token keyword">null</span></span>
     */</span>
    <span class="token keyword">function</span> <span class="token function-definition function">current_team</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token operator">?</span><span class="token class-name return-type">Team</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token function">optional</span><span class="token punctuation">(</span><span class="token function">auth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">user</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token property">currentTeam</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>In my requests I need to validate that the campaign name is unique for the current team. That&#x27;s easy enough:</p>
<pre class="language-php"><code class="language-php"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Validation<span class="token punctuation">\</span>Rule</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">rules</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token variable">$currentTeam</span> <span class="token operator">=</span> <span class="token this keyword">$this</span><span class="token operator">-&gt;</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;team&#x27;</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token function">current_team</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token string single-quoted-string">&#x27;name&#x27;</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span>
            <span class="token string single-quoted-string">&#x27;required&#x27;</span><span class="token punctuation">,</span>
            <span class="token scope">Rule<span class="token punctuation">::</span></span><span class="token function">unique</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;experiments&#x27;</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">where</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$query</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">return</span> <span class="token variable">$query</span><span class="token operator">-&gt;</span><span class="token function">where</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;team_id&#x27;</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">&#x27;=&#x27;</span><span class="token punctuation">,</span> <span class="token function">optional</span><span class="token punctuation">(</span><span class="token variable">$currentTeam</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token property">id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>It&#x27;s not awful, but it&#x27;s not great either. And what about if I need to access other route bindings or values?</p>
<p>To solve this, I created a new <code>BaseRequest</code> class, which overrides the <code>prepareForValidation</code> method to merge in data that won&#x27;t exist in the original request itself. Because I&#x27;m also using route model bindings, I can easily pull each model back from the request:</p>
<pre class="language-php"><code class="language-php"><span class="token keyword">class</span> <span class="token class-name-definition class-name">BaseRequest</span> <span class="token keyword">extends</span> <span class="token class-name">FormRequest</span>
<span class="token punctuation">{</span>
    <span class="token doc-comment comment">/**
     * Prepare the data for validation.
     *
     * <span class="token keyword">@return</span> <span class="token class-name"><span class="token keyword">void</span></span>
     */</span>
    <span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function-definition function">prepareForValidation</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token variable">$currentTeam</span> <span class="token operator">=</span> <span class="token this keyword">$this</span><span class="token operator">-&gt;</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;team&#x27;</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token function">current_team</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token this keyword">$this</span><span class="token operator">-&gt;</span><span class="token function">merge</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
            <span class="token string single-quoted-string">&#x27;campaign_id&#x27;</span> <span class="token operator">=&gt;</span> <span class="token function">optional</span><span class="token punctuation">(</span><span class="token this keyword">$this</span><span class="token operator">-&gt;</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;campaign&#x27;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token property">id</span><span class="token punctuation">,</span>
            <span class="token string single-quoted-string">&#x27;subscriber_id&#x27;</span> <span class="token operator">=&gt;</span> <span class="token function">optional</span><span class="token punctuation">(</span><span class="token this keyword">$this</span><span class="token operator">-&gt;</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;subscriber&#x27;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token property">id</span><span class="token punctuation">,</span>
            <span class="token string single-quoted-string">&#x27;sub_campaign_id&#x27;</span> <span class="token operator">=&gt;</span> <span class="token function">optional</span><span class="token punctuation">(</span><span class="token this keyword">$this</span><span class="token operator">-&gt;</span><span class="token function">route</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;sub_campaign&#x27;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token property">id</span><span class="token punctuation">,</span>
            <span class="token string single-quoted-string">&#x27;team_id&#x27;</span> <span class="token operator">=&gt;</span> <span class="token function">optional</span><span class="token punctuation">(</span><span class="token variable">$currentTeam</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token property">id</span><span class="token punctuation">,</span>
        <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Now I can quickly access any of this data within the rules. Our form request now becomes:</p>
<pre class="language-php"><code class="language-php"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Validation<span class="token punctuation">\</span>Rule</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">rules</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token string single-quoted-string">&#x27;name&#x27;</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span>
            <span class="token string single-quoted-string">&#x27;required&#x27;</span><span class="token punctuation">,</span>
            <span class="token scope">Rule<span class="token punctuation">::</span></span><span class="token function">unique</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;experiments&#x27;</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">where</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$query</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">return</span> <span class="token variable">$query</span><span class="token operator">-&gt;</span><span class="token function">where</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;team_id&#x27;</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">&#x27;=&#x27;</span><span class="token punctuation">,</span> <span class="token this keyword">$this</span><span class="token operator">-&gt;</span><span class="token property">team_id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Of course, the <code>prepareForValidation</code> method can be used for a lot more, but this is just one use-case where I found it particulary nice.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Building The Laravel Artisan Cheatsheet]]></title>
            <link>https://james.brooks.page/blog/building-the-laravel-artisan-cheatsheet</link>
            <guid>https://james.brooks.page/blog/building-the-laravel-artisan-cheatsheet</guid>
            <pubDate>Mon, 18 Jan 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Introducing the Laravel Artisan Cheatsheet, a bookmarkable and shareable resource for all Laravel's default artisan commands. The source code for this resource is available on GitHub]]></description>
            <content:encoded><![CDATA[<p>Today I released the <a href="https://artisan.page/">Laravel Artisan Cheatsheet</a>, a bookmarkable and shareable resource for all Laravel&#x27;s default artisan commands. The source code for this resource is available on <a href="https://github.com/jbrooksuk/artisan.page">GitHub</a>.</p>
<p>In the past I&#x27;ve tweeted several tips for artisan and the many unknown features and secrets it holds. I&#x27;ve had the idea to create this resource for a while and last night I finally made the time turn it into something I can share.</p>
<p>After make some last-minute adjustments, I finally deployed it. This evening, I&#x27;ve switched the project to use <a href="https://nuxtjs.org/">Nuxt.js</a>. This brings a couple of benefits:</p>
<ul>
<li>Generated output - Google loves this!</li>
<li>Work on additional features such as; keyboard shortcuts, multiple versions and synopsis tooltips.</li>
<li>Easier for other people to contribute.</li>
<li>The core</li>
<li>The core of the resource is a JSON file that holds all of the data needed to generate the command output. To generate this file I used <a href="https://tinkerwell.app">Tinkerwell</a> and this snippet:</li>
</ul>
<pre class="language-php"><code class="language-php"><span class="token variable">$commands</span> <span class="token operator">=</span> <span class="token scope">Artisan<span class="token punctuation">::</span></span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token variable">$output</span> <span class="token operator">=</span> <span class="token function">collect</span><span class="token punctuation">(</span><span class="token variable">$commands</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">sortBy</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$command</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token variable">$command</span><span class="token operator">-&gt;</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$command</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">[</span>
    <span class="token string single-quoted-string">&#x27;name&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$command</span><span class="token operator">-&gt;</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string single-quoted-string">&#x27;description&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$command</span><span class="token operator">-&gt;</span><span class="token function">getDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string single-quoted-string">&#x27;synopsis&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$command</span><span class="token operator">-&gt;</span><span class="token function">getSynopsis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string single-quoted-string">&#x27;definition&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$command</span><span class="token operator">-&gt;</span><span class="token function">getDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string single-quoted-string">&#x27;aliases&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$command</span><span class="token operator">-&gt;</span><span class="token function">getAliases</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string single-quoted-string">&#x27;arguments&#x27;</span> <span class="token operator">=&gt;</span> <span class="token function">collect</span><span class="token punctuation">(</span><span class="token variable">$command</span><span class="token operator">-&gt;</span><span class="token function">getDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">getArguments</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$argument</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token string single-quoted-string">&#x27;name&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$argument</span><span class="token operator">-&gt;</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;description&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$argument</span><span class="token operator">-&gt;</span><span class="token function">getDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;default&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$argument</span><span class="token operator">-&gt;</span><span class="token function">getDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;required&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$argument</span><span class="token operator">-&gt;</span><span class="token function">isRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string single-quoted-string">&#x27;options&#x27;</span> <span class="token operator">=&gt;</span> <span class="token function">collect</span><span class="token punctuation">(</span><span class="token variable">$command</span><span class="token operator">-&gt;</span><span class="token function">getDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">getOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$option</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token string single-quoted-string">&#x27;name&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$option</span><span class="token operator">-&gt;</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;description&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$option</span><span class="token operator">-&gt;</span><span class="token function">getDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;value_required&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$option</span><span class="token operator">-&gt;</span><span class="token function">isValueRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;value_optional&#x27;</span> <span class="token operator">=&gt;</span> <span class="token variable">$option</span><span class="token operator">-&gt;</span><span class="token function">isValueOptional</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">toJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>Not all of this data is currently used, but the resulting JSON file should contain enough information to future proof the resource.</p>
<p>Anyway, let me know what you think and if you have some ideas, I&#x27;d love to see a PR!</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[AWS CLI - S3 and Alibaba Object Storage Service]]></title>
            <link>https://james.brooks.page/blog/aws-cli-alibaba-object-storage-service-oss</link>
            <guid>https://james.brooks.page/blog/aws-cli-alibaba-object-storage-service-oss</guid>
            <pubDate>Mon, 11 Jan 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[How to configure Laravel Forge’s database backups with Alibaba OSS.]]></description>
            <content:encoded><![CDATA[<p>A <a href="https://forge.laravel.com">Laravel Forge</a> customer recently reached out to us asking whether the Database Backup feature supported <a href="https://www.alibabacloud.com/product/oss">Alibiba OSS</a> (Object Storage Service).</p>
<p>Since OSS is S3 compatible, the answer is happily &quot;yes&quot; however, in this case the S3 compatibility requires a custom configuration change which we can make very quickly. Without the below change, you&#x27;ll get this error:</p>
<pre><code>upload failed: - to s3://bucket/directory/file An error occured (SecondLevelDomainForbidden) when calling the PutObject operation: Please use virtual hosted style to access.
</code></pre>
<p>When Forge configures database backups, it creates a <code>/root/.aws/config</code> file that is used by <code>awscli</code> to configure how S3 settings. For OSS to work, I had to make the following one-line adjustment:</p>
<pre class="language-diff"><code class="language-diff">[profile backup-xxxx]
output=text
region=
s3 =
<span class="token unchanged"><span class="token prefix unchanged"> </span><span class="token line">   signature_version = s3v4
</span></span><span class="token inserted-sign inserted"><span class="token prefix inserted">+</span><span class="token line">   addressing_style = virtual
</span></span>
</code></pre>
<p>The backup can now finish successfully.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[2020 Recap]]></title>
            <link>https://james.brooks.page/blog/2020-recap</link>
            <guid>https://james.brooks.page/blog/2020-recap</guid>
            <pubDate>Wed, 23 Dec 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[I recap on what happened over the year of 2020.]]></description>
            <content:encoded><![CDATA[<p>Well, 2020 has been a rollercoaster of a year hasn&#x27;t it 😬</p>
<p>Luckily my family, friends and I have mostly avoided catching the virus so far. I&#x27;ve been fortunate that I&#x27;ve been able to continue my job at Laravel with (almost) zero disruptions to my daily work life.</p>
<p>I&#x27;d like to take a look back at 2020, both professionally and personally.</p>
<h2>At Work</h2>
<p>I feel like this year I&#x27;ve really settled into my role at Laravel. I&#x27;ve crafted, deployed and supported several large features both in <a href="https://forge.laravel.com">Forge</a> and <a href="https://envoyer.io">Envoyer</a>.</p>
<p>Sharing these developments with the community via Twitter and the Laravel Blog has been important to me this year. I&#x27;ve made an effort to document and share more.</p>
<p>I previously reviewed my first year Laravel, but since then we have done so much more.</p>
<h3>Laravel Internals Podcast</h3>
<p>On the 18th November, my colleague <a href="https://twitter.com/enunomaduro">Nuno Maduro</a> and I recorded the first <a href="https://www.youtube.com/watch?v=B57vddRZPC8">&quot;Laravel Internals&quot; live podcast on YouTube</a>.</p>
<p>Laravel Internals is a new format where members of the team will discuss what they&#x27;ve been working on.</p>
<h3>Forge</h3>
<p>Most of my time this year has been spent working on Forge, which I&#x27;ve absolutely loved. Here are just some of the things I&#x27;ve worked on this year:</p>
<ul>
<li>
<a href="https://blog.laravel.com/forge-database-backups-now-supported">Database Backups</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-metric-monitoring">Metric Monitoring</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-telegram-notifications">Telegram Notifications</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-deploy-script-environment-variables">Deploy Script Environment Variables</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-custom-backup-providers">Custom Backup Providers</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-multiple-php-installations">Multiple PHP Installations</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-password-protected-paths">Password Protected Paths</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-load-balancer-methods">Load Balancer Methods</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-new-load-balancer-features">Even more Load Balancer Features</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-phpmyadmin-one-click-install">phpMyAdmin One Click Installs</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-updates-september-8">TLS v1.3, Redis Passwords and more</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-self-hosted-gitlab-support">Self-Hosted GitLab Support</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-nginx-templates">Nginx Templates</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-streaming-backups">Streaming Backups &amp; More</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-database-management-improvements">Database Management Improvements</a>
</li>
<li>
<a href="https://blog.laravel.com/forge-php-80-is-now-supported">PHP 8.0 Support</a>
</li>
</ul>
<p>I&#x27;ve also <a href="https://twitter.com/jbrooksuk">tweeted</a> about a lot of the smaller changes, features and enhancements that we&#x27;ve deployed. Some of these changes include:</p>
<ul>
<li>
<a href="https://twitter.com/jbrooksuk/status/1323554756242120705?s=20">Deployments Tab</a>
</li>
<li>
<a href="https://twitter.com/jbrooksuk/status/1332349466645553152?s=20">Database Connection URL</a>
</li>
<li>
<a href="https://twitter.com/jbrooksuk/status/1321020062875136002?s=20">Overhauled Provisioning Progress</a>
</li>
</ul>
<h3>Envoyer</h3>
<p>Envoyer has received a lot of love this year too:</p>
<ul>
<li>
<a href="https://blog.laravel.com/envoyer-healthchecker-upgrades">Healthchecker Upgrades</a>
</li>
<li>
<a href="https://blog.laravel.com/envoyer-extended-deployment-timeouts-and-new-notification-channels">Extended Deployment Timeouts and New Notification Channels</a>
</li>
<li>
<a href="https://blog.laravel.com/envoyer-refreshed-deployment-steps">Refreshed Deployment Steps</a>
</li>
<li>
<a href="https://blog.laravel.com/introducing-the-envoyer-api">Envoyer API</a>
</li>
<li>
<a href="https://blog.laravel.com/envoyer-selectable-deployment-hooks">Selectable Deployment Hooks</a>
</li>
<li>
<a href="https://blog.laravel.com/envoyer-vapor-automatically-email-invoices">Email Invoices</a>
</li>
</ul>
<h2>At Home</h2>
<p>A lot has happened in my personal life.</p>
<h3>A New House</h3>
<p>After a turbulent few months at the beginning of this year, we were unsure if we&#x27;d be able to move. It look a lot of persistency and taking matters into our own hands, but we were able to move house in July.</p>
<p>This is the first home I&#x27;ve ever bought and I&#x27;m really proud of Katie and I for making it happen. We have a lot of jobs to get through, but thankfully lockdown has given me a lot of time to make a start on them and it&#x27;s been fun!</p>
<h3>A New (Home) Office</h3>
<p>Having worked from home full-time since July 2019, I knew that wherever we moved to, I&#x27;d want a permanent &quot;home-office&quot; setup. We&#x27;d looked at places with enough outdoor space that I could have a separate working space, but in the end we settled for me using a spare bedroom and having a sofa-bed in there.</p>
<p>This was the first room to be fully decorated and, once life returns to more of what it was, we&#x27;ll order the sofa bed to go in there.</p>
<p>Katie switched job this year but has also started working from home during the pandemic, so we&#x27;re now sharing a desk and office.</p>
<p>I really need to take some new pictures of the office! We have a new carpet and some pictures up.</p>
<h3>Celebrating Our First Wedding Anniversary</h3>
<p>We celebrated our first wedding anniversary in July! I continue to feel very lucky that we chose to marry last year (we had considered waiting till this year) and I know of many weddings that have been affected by the pandemic.</p>
<p>Unfortunately, we were unable to do anything big, but we had a lot going on already (what with moving house), so we enjoyed our new home together.</p>
<h3>A New Van</h3>
<p>Katie and I have wanted to buy a campervan for years and the opportunity came this year.</p>
<p>We bought Birtha (a VW T25) in August with our friends. Sadly, we haven&#x27;t had much chance to use her yet and when we did want to use her, we found she&#x27;d been leaking petrol!</p>
<p>Since replacing the petrol tank, our friends took her on a roadtrip to Scotland and back.</p>
<p>Next year, we need to spend a bit of time working on her and fixing up a few things.</p>
<blockquote><p>Kelly and I sitting on Birtha!</p></blockquote>
<h3>A New Baby</h3>
<p>We&#x27;re expecting our second baby in June 2021! Katie is doing well and is having less morning sickness than she did with Harriet.</p>
<h2>Music</h2>
<p>Music is a big part of my life. I tend to obsessively stick with a few songs for a while and then spend a month or so rotating them as part of my previously obsessed songs.</p>
<p>This year has been no different. Here are some of the songs I&#x27;ve been listening to on repeat (not all new music):</p>
<ul>
<li>AJR, Bang!</li>
<li>Frozen, Into The Unknown (yay for having a 2 year old daughter)</li>
<li>Freddie Mercury, Living On My Own - No More Brothers</li>
<li>Jake Bugg, All I need</li>
<li>Twenty One Pilots, Level Of Concern</li>
<li>You me At Six - SUCKAPUNCH</li>
<li>Hollywood Undead - Ghost Out</li>
<li>Madeon - The Prince</li>
<li>Bastille - WHAT YOU GONNA DO???</li>
</ul>
<p>And I think that wraps up 2020 for me! Please do share your recaps with me, <a href="https://twitter.com/jbrooksuk">@jbrooksuk</a>.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Laravel Localization Case Tips]]></title>
            <link>https://james.brooks.page/blog/laravel-localization-case-tips</link>
            <guid>https://james.brooks.page/blog/laravel-localization-case-tips</guid>
            <pubDate>Wed, 15 Jul 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Here is a little tip for Laravel’s Localization that I discovered today whilst looking through the open Laravel Nova issues. Although it’s documented it’s not well known - at least, I didn’t know about it before today!]]></description>
            <content:encoded><![CDATA[<p>Here is a little tip for Laravel&#x27;s Localization that I discovered today whilst looking through the open Laravel Nova issues. Although it&#x27;s <a href="https://laravel.com/docs/7.x/localization#replacing-parameters-in-translation-strings">documented</a> it&#x27;s not well known - at least, I didn&#x27;t know about it before today!</p>
<p>Say you have an <code>en.json</code> translation file like this:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
    <span class="token property">&quot;The :resource was created!&quot;</span><span class="token operator">:</span> <span class="token string">&quot;The :resource was created!&quot;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>You can now use this like so:</p>
<pre class="language-php"><code class="language-php"><span class="token function">__</span><span class="token punctuation">(</span><span class="token string double-quoted-string">&quot;The :resource was created!&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string single-quoted-string">&#x27;resource&#x27;</span> <span class="token operator">=&gt;</span> <span class="token string single-quoted-string">&#x27;category&#x27;</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// &quot;The category was created!&quot;</span>
</code></pre>
<p>But let&#x27;s say you need to translate this to a language where the resource name must be in capitals. In our locale file, we say:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
    <span class="token property">&quot;The :resource was created!&quot;</span><span class="token operator">:</span> <span class="token string">&quot;Die :Resource wurde erstellt!&quot;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>And when we use this, we&#x27;d now get:</p>
<pre class="language-php"><code class="language-php"><span class="token function">__</span><span class="token punctuation">(</span><span class="token string double-quoted-string">&quot;The :resource was created!&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string single-quoted-string">&#x27;resource&#x27;</span> <span class="token operator">=&gt;</span> <span class="token string single-quoted-string">&#x27;kategorie&#x27;</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// &quot;Die Kategorie wurde erstellt!&quot;</span>
</code></pre>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Implementing RICE in Trello]]></title>
            <link>https://james.brooks.page/blog/implementing-rice-in-trello</link>
            <guid>https://james.brooks.page/blog/implementing-rice-in-trello</guid>
            <pubDate>Sun, 12 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[At Laravel we use Trello to store all of the ideas we come up with and also any suggestions that are sent to us.]]></description>
            <content:encoded><![CDATA[<p>At Laravel we use <a href="https://trello.com/jbrooksuk/recommend">Trello</a> to store all of the ideas we come up with and also any suggestions that are sent to us.</p>
<p>Recently, Taylor shared an article about <a href="https://www.intercom.com/blog/rice-simple-prioritization-for-product-managers/">RICE</a> with the team and we could immediately see the benefits. RICE is described as:</p>
<blockquote>
<p>Simple prioritization for product managers</p>
</blockquote>
<p>Essentially, by scoring four different factors, you can calculate a score that you use to determine how important the feature is.</p>
<blockquote>
<p><strong>Reach:</strong> how many people will this impact? (Estimate within a defined time period.)</p>
<p><strong>Impact:</strong> how much will this impact each person? (Massive = 3x, High = 2x, Medium = 1x, Low = 0.5x, Minimal = 0.25x.)</p>
<p><strong>Confidence:</strong> how confident are you in your estimates? (High = 100%, Medium = 80%, Low = 50%.)</p>
<p><strong>Effort:</strong> how many “person-months” will this take? (Use whole numbers and minimum of half a month – don’t get into the weeds of estimation.)</p>
</blockquote>
<p>Now, having recently planned for the arrival of our baby and wedding using Trello, I&#x27;m a self-described Trello nerd. When used properly, Trello can become a super-power, especially when you&#x27;re using with <a href="https://butlerfortrello.com">Butler</a> enabled.</p>
<p>After reading the article, I was immediately drawn to whether we could build this into Trello. <span class="italic">If you haven&#x27;t guessed already, the answer is yes, you can.</span></p>
<p>To implement this yourself, you must have the Butler and Custom Fields powerups available and enabled.</p>
<h2>Custom Fields</h2>
<p>Before we can calculate the RICE score, we need to be able to enter the individual scores. To do this, I created five fields:</p>
<ul>
<li><strong>Reach</strong> - This should be a Number field.</li>
<li><strong>Impact</strong> - This should be a Dropdown field with the options; 3, 2, 1, 0.50, 0.25. I also coloured the options; green, yellow, orange, red and black.</li>
<li><strong>Confidence</strong> - This should be a Dropdown field with the options; 100, 80, 50, 20. Again, I coloured these; green, yellow, orange and red.</li>
<li><strong>Effort</strong> - This should be a Number field.</li>
<li><strong>RICE Score</strong> - This should be a Number field. I also checked the <strong>Show field on front of card</strong> option so that it&#x27;s quickly visible without needing to open the card.</li>
</ul>
<h2>Butler</h2>
<p>We can now calculate the RICE score. To do this we use a custom Butler Card Button with an action that sets the custom field <code>RICE Score</code> to:</p>
<pre><code>{{%Reach}}*{{%Impact}}*{{%Confidence}}/{{%Effort}}
</code></pre>
<h2>Done!</h2>
<p>And that&#x27;s it, we can now see the RICE score for a task on the front of a card, just by providing the individual scores.</p>
<p>If you wanted to, you could take this further by using more of the features available in Butler, such as sorting by score, auto-labelling priorities based on the calculated RICE score and more!</p>
<p>I&#x27;d love to know if you&#x27;re using this in your Trello boards, so please tweet me <a href="https://twitter.com/jbrooksuk">@jbrooksuk</a>.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Creating Happiness Out Of Sadness]]></title>
            <link>https://james.brooks.page/blog/creating-happiness-out-of-sadness</link>
            <guid>https://james.brooks.page/blog/creating-happiness-out-of-sadness</guid>
            <pubDate>Fri, 03 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[On the 30th October 2019, I launched Happy Dev a podcast in which I interview software developers and we discuss mental health.]]></description>
            <content:encoded><![CDATA[<p>On the 30th October 2019, I launched <a href="https://www.happydev.fm">Happy Dev</a> a podcast in which I interview software developers and we discuss mental health. I see it as a platform in which we can share the guests&#x27; story; when they first became aware of their mental health, how it has manifested over the years and what we as an industry can do to help each other, among other things.</p>
<p>After my brother took his own life in 2018, I found myself at a bit of a loss. I&#x27;d lost someone very important to me and I didn&#x27;t have an outlet for these strange and confusing feelings that I now had. I knew that I wanted to do something, but I wasn&#x27;t sure what. Out of this came the idea of Happy Dev.</p>
<p>I found myself thinking about it more and more and eventually I <em>settled</em> on the idea of making a podcast. However, I kept putting it off because, in some ways, the idea of recording a podcast was embarrassing to me. It&#x27;s silly in hindsight, but it&#x27;s honestly how I felt at the time.</p>
<p>When I joined Laravel in July 2019, I was lucky to gain an audience of software developers and importantly, an audience of people who are likely to listen to podcasts. I&#x27;d also been listening to a lot of podcasts myself, which made me feel more positive about recording myself.</p>
<p>Around this time, I also realised that if I wanted to make a podcast, I could and I should do it. Nobody knew I had been thinking about it and so of course, nobody was going to encourage me to do it. I finally spoke to my wife, Katie about it and although she liked the idea of it, she suggested that I ask people in the industry what they think, so I did. I messaged the Laravel team about my idea and was honestly blown away by their overwhelming support and positivity towards it. So much so that <a href="https://www.happydev.fm/1">Dries Vints</a> was my first confirmed guest, before I&#x27;d even committed to making it.</p>
<p>With all of this positivity came the drive to commit myself to the idea and start making it.</p>
<p>I created a demo <span class="bg-yellow-200">(I&#x27;ve always wanted to be in a band making demo tapes)</span> and played it for Katie. She cried and I was worried that I&#x27;d made a huge mistake, but apparently, she was crying with pride, so that was a relief!</p>
<p>Here is the <strong>original</strong> demo in all its raw glory.</p>
<audio controls=""><source src="/audio/happy-dev-demo.mp3" type="audio/mpeg"/><p>Your browser does not support the audio element.</p></audio>
<p>When listening to this, keep in mind that I recorded it using the tools I had available at the time which were; Beats Headphones microphone, GarageBand and some royalty-free music.</p>
<p>This was it, it was finally happening! I ordered a microphone and re-recorded the introduction, this time with some words I&#x27;d thought about and written down instead of making it up.</p>
<p>I&#x27;d like to point out that even if I wasn&#x27;t in the fortunate position to buy a microphone on a whim of an idea, I could&#x27;ve still produced Happy Dev without it. The point to this is that just because you don&#x27;t have &quot;professional&quot; tools available, you can still make it.</p>
<h2>What Happened Next</h2>
<p>Well, it&#x27;s fair to say that I&#x27;m constantly blown away by the overwhelming love and support for the podcast. As of writing, I&#x27;ve already released seven episodes (one trailer and one bonus) and has accumulated over 1300 downloads.</p>
<p>I started with an incredibly strong line up and there&#x27;s still other amazing guests coming, which is very exciting!</p>
<p>I&#x27;m now releasing an episode every other Wednesday, which just about gives me enough time to record, edit and publish in time. I was ahead for a while, but the Christmas break has messed that up a bit.</p>
<h2>Sponsorship Disclosure</h2>
<p>Although I&#x27;ve not yet confirmed any sponsorship slots, I want to be very clear about how it&#x27;ll work upfront.</p>
<p>I cover all of the Happy Dev costs personally. The cost equates to about $40 / mo and is made up of:</p>
<ul>
<li>$19.00 / mo for <a href="https://transistor.fm/?via=james-brooks">Transistor.fm</a>. Transistor hosts the podcast, website and makes it easy to create a draft episode.</li>
<li>£10.00 ($13 ish) / mo for <a href="https://www.epidemicsound.com/referral/1rztjs/">Epidemic Sound</a>. They license the intro and outro music. I would love to either purchase the use of the sounds for the entire show or record my own.</li>
<li>$89.99 / yr for <a href="https://hover.com/WZrLNupd">Hover.com</a>. Hover registered the <a href="https://www.happydev.fm">HappyDev.fm</a> domain.</li>
</ul>
<p>Should I be fortunate enough to have sponsorship on the show, then this is the deal:</p>
<ul>
<li>I want to be able to cover the costs of the show.</li>
<li>The remainder of any money made from sponsorships will be split and divided to a few different mental health charities, from Happy Dev.</li>
<li>Sponsorship <strong>must</strong> be relevant to the show and audience. I <strong>will only</strong> place advertisements where I believe the company supports mental health positively.</li>
</ul>
<p>So for example, if Happy Dev had sponsorship of $500, I would cover the $40 costs and donate the remaining $460 to charity.</p>
<p>The charities I have picked are:</p>
<ul>
<li><a href="https://osmihelp.org">Open Source Mental Health</a> - Raising awareness and ending the stigma of mental illness in the developer/tech community.</li>
<li><a href="https://boysgetsadtoo.com">Boys Get Sad Too</a> - 20% of their profits actually go towards CALM, but I believe strongly in their message. BGST supply apparel with conversation provoking designs. Donations would go directly to BGST so that they can promote their message louder instead of purchasing apparel.</li>
<li><a href="https://papyrus-uk.org">PAPYRUS</a> - Provides suicide prevention support and a hotline. I&#x27;ve previously raised funds for PAPYRUS.</li>
</ul>
<p>From time to time I may evaluate the list and add or remove charities.</p>
<p>My hope is that this post has inspired you in some way. If you&#x27;ve been thinking about starting something but been too scared or put off for some reason, my advice to you is that you go for it and see what comes from it!</p>
<p>If you haven&#x27;t already, I&#x27;d love for you to give <a href="https://www.happydev.fm">Happy Dev</a> a listen, subscribe for future episodes and follow <a href="https://twitter.com/happydevfm">@HappyDevFM</a> on Twitter.</p>
<div class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 px-4 py-2" role="alert"><p class="font-bold my-4">Sponsor Happy Dev on Patreon</p>
<p class="-mt-2">You can now sponsor Happy Dev on <a href="https://patreon.com/happydev" target="_blank" class="underline text-orange-700 hover:text-orange-500 font-bold">Patreon!</a></p></div>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Nova Customisable Resource Fields]]></title>
            <link>https://james.brooks.page/blog/nova-customisable-resource-fields</link>
            <guid>https://james.brooks.page/blog/nova-customisable-resource-fields</guid>
            <pubDate>Fri, 03 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[This week I’m back working on Laravel Nova, which includes working my way through the nova-issues repo and seeing what’s what.]]></description>
            <content:encoded><![CDATA[<p>This week I&#x27;m back working on <a href="https://nova.laravel.com">Laravel Nova</a>, which includes working my way through the <a href="https://github.com/laravel/nova-issues">nova-issues</a> repo and seeing what&#x27;s what.</p>
<p>One issue that caught my eye was a feature request from <span class="bg-yellow-200">@Grayda</span>, <a href="https://github.com/laravel/nova-issues/issues/2622">https://github.com/laravel/nova-issues/issues/2622</a>. They asked that we provide functionality in Nova that allows users to show or hide fields based on their selection.</p>
<p>I immediately had a feeling this may be possible using the <a href="https://nova.laravel.com/docs/3.0/filters/defining-filters.html">Filters feature</a> of Nova, so I gave it a quick go and it worked!</p>
<img alt="Demo" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdemo.b955fb1b.gif&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdemo.b955fb1b.gif&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdemo.b955fb1b.gif&amp;w=3840&amp;q=75" width="1380" height="996" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<h2>The Code</h2>
<p>First thing&#x27;s first, we start by creating a boolean filter:</p>
<pre class="language-bash"><code class="language-bash">php artisan nova:filter FieldsFilter --boolean
</code></pre>
<p>We use a boolean filter here so that we can display checkboxes to the user:</p>
<img alt="Filters" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ffilters.d1738607.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ffilters.d1738607.png&amp;w=750&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ffilters.d1738607.png&amp;w=750&amp;q=75" width="348" height="400" decoding="async" data-nimg="1" loading="lazy" style="color:transparent"/>
<p>Now that we have our filter, we can start implementing the logic that we need. Within the <code>options</code> method, we define the array of fields that we want the user to be able to toggle:</p>
<pre class="language-php"><code class="language-php"><span class="token doc-comment comment">/**
 * Get the filter&#x27;s available options.
 *
 * <span class="token keyword">@param</span>  <span class="token class-name"><span class="token punctuation">\</span>Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span>  <span class="token parameter">$request</span>
 * <span class="token keyword">@return</span> <span class="token class-name"><span class="token keyword">array</span></span>
 */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">options</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token string single-quoted-string">&#x27;Name&#x27;</span> <span class="token operator">=&gt;</span> <span class="token string single-quoted-string">&#x27;name&#x27;</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;Gravatar&#x27;</span> <span class="token operator">=&gt;</span> <span class="token string single-quoted-string">&#x27;gravatar&#x27;</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;Email&#x27;</span> <span class="token operator">=&gt;</span> <span class="token string single-quoted-string">&#x27;email&#x27;</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<blockquote>
<p>Note that with this method the options array uses a <code>value/key</code> syntax.</p>
</blockquote>
<p>If we want to enable these fields by default, we&#x27;ll also need to override the <code>default</code> method. You can do this by adding this code to your filter:</p>
<pre class="language-php"><code class="language-php"><span class="token doc-comment comment">/**
 * Set the default options for the filter.
 *
 * <span class="token keyword">@return</span> <span class="token class-name"><span class="token keyword">array</span></span>
 */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token string single-quoted-string">&#x27;name&#x27;</span> <span class="token operator">=&gt;</span> <span class="token constant boolean">true</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;gravatar&#x27;</span> <span class="token operator">=&gt;</span> <span class="token constant boolean">true</span><span class="token punctuation">,</span>
        <span class="token string single-quoted-string">&#x27;email&#x27;</span> <span class="token operator">=&gt;</span> <span class="token constant boolean">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Once we&#x27;ve decided which fields the user should be allowed to toggle, we need to add the filter to our Resource. In the examples I&#x27;ve used above, we&#x27;re modifying the <code>User</code> resource:</p>
<pre class="language-php"><code class="language-php"><span class="token keyword">use</span> <span class="token package">App<span class="token punctuation">\</span>Nova<span class="token punctuation">\</span>Filters<span class="token punctuation">\</span>FieldsFilter</span><span class="token punctuation">;</span>

<span class="token doc-comment comment">/**
 * Get the filters available for the resource.
 *
 * <span class="token keyword">@param</span>  <span class="token class-name"><span class="token punctuation">\</span>Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span>  <span class="token parameter">$request</span>
 * <span class="token keyword">@return</span> <span class="token class-name"><span class="token keyword">array</span></span>
 */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">filters</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token keyword">new</span> <span class="token class-name">FieldsFilter</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>We&#x27;re almost there, stay with me!</p>
<p>All we need to do now is toggle the field if the user wants to display it. Usually we&#x27;d use the <code>fields</code> method to describe the fields that you wish to display to a user. Since we only want to allow users to customise which fields are displayed on the index listing, we can use the <code>fieldsForIndex</code> method instead. This is a two part change. Firstly, we need to store the <code>FieldsFilter</code> in a variable so we can access it later:</p>
<pre class="language-php"><code class="language-php"><span class="token doc-comment comment">/**
 * Get the fields displayed by the resource index.
 *
 * <span class="token keyword">@param</span>  <span class="token class-name"><span class="token punctuation">\</span>Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span>  <span class="token parameter">$request</span>
 * <span class="token keyword">@return</span> <span class="token class-name"><span class="token keyword">array</span></span>
 */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">fieldsForIndex</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Request</span> <span class="token variable">$request</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token variable">$fieldFilter</span> <span class="token operator">=</span> <span class="token variable">$request</span><span class="token operator">-&gt;</span><span class="token function">filters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">first</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$filter</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token variable">$filter</span><span class="token operator">-&gt;</span><span class="token property">filter</span> <span class="token keyword">instanceof</span> <span class="token class-name">FieldsFilter</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token comment">// Existing fields...</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>And finally, we need to modify our fields so that we hide it if the user no longer wants to see it:</p>
<pre class="language-php"><code class="language-php"><span class="token keyword">return</span> <span class="token punctuation">[</span>
    <span class="token scope">ID<span class="token punctuation">::</span></span><span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-&gt;</span><span class="token function">sortable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

    <span class="token scope">Gravatar<span class="token punctuation">::</span></span><span class="token function">make</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;Gravatar&#x27;</span><span class="token punctuation">)</span>
            <span class="token operator">-&gt;</span><span class="token function">showOnIndex</span><span class="token punctuation">(</span><span class="token scope">Arr<span class="token punctuation">::</span></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token variable">$fieldFilter</span><span class="token operator">-&gt;</span><span class="token property">value</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">&#x27;gravatar&#x27;</span><span class="token punctuation">,</span> <span class="token constant boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

    <span class="token scope">Text<span class="token punctuation">::</span></span><span class="token function">make</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;Name&#x27;</span><span class="token punctuation">)</span>
        <span class="token operator">-&gt;</span><span class="token function">showOnIndex</span><span class="token punctuation">(</span><span class="token scope">Arr<span class="token punctuation">::</span></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token variable">$fieldFilter</span><span class="token operator">-&gt;</span><span class="token property">value</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">&#x27;name&#x27;</span><span class="token punctuation">,</span> <span class="token constant boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

    <span class="token scope">Text<span class="token punctuation">::</span></span><span class="token function">make</span><span class="token punctuation">(</span><span class="token string single-quoted-string">&#x27;Email&#x27;</span><span class="token punctuation">)</span>
        <span class="token operator">-&gt;</span><span class="token function">sortable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token operator">-&gt;</span><span class="token function">showOnIndex</span><span class="token punctuation">(</span><span class="token scope">Arr<span class="token punctuation">::</span></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token variable">$fieldFilter</span><span class="token operator">-&gt;</span><span class="token property">value</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">&#x27;email&#x27;</span><span class="token punctuation">,</span> <span class="token constant boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>
</code></pre>
<blockquote>
<p>Notice above how our fields don&#x27;t contain any <code>rules</code> or <code>help</code> calls? This is because the index listing doesn&#x27;t need to know any of that! We can make this method a lot cleaner than the usual <code>fields</code> method.</p>
</blockquote>
<p>And we&#x27;re done! Your users can now select which fields they want to display. You could take this further and have fields which are hidden by default, but then allow users to display them.</p>
<p>As a side note, you should be aware that each change will result in a fresh network request. This is required by Nova as changes to filters may result in a callback being handled differently, as demonstrated above with the use of <code>$request-&gt;filters</code>.</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Tighten’s Jigsaw, GitHub Pages and GitHub Actions]]></title>
            <link>https://james.brooks.page/blog/jigsaw-github-actions</link>
            <guid>https://james.brooks.page/blog/jigsaw-github-actions</guid>
            <pubDate>Wed, 01 Jan 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[How to configure Tighten Jigsaw to deploy to GitHub Pages with a custom GitHub Action.]]></description>
            <content:encoded><![CDATA[<p>This blog was previously powered by <a href="https://jigsaw.tighten.co">Tighten’s Jigsaw project</a>. Even though it’s no longer used, if you’re thinking about starting your own blog, I can still highly recommend it.</p>
<p>Since Jigsaw generates a static website, I chose to host it with <a href="https://pages.github.com">GitHub Pages</a> as a &quot;user site&quot; as they allow for top-level domains. GitHub Pages only serve static sites, but as Jigsaw is a dynamic system we need to generate the site. I was able to automate this process with <a href="https://github.com/features/actions">GitHub Actions</a>. It took some trial and error <span class="italic">(read: a lot of <strong>wip</strong> commits)</span> but I was finally able to make it work and wanted to share this setup as I think it’s pretty sweet.</p>
<p>I’ll break this down into two sections, the first, using Jigsaw and GitHub Pages and the second, publishing with GitHub Actions.</p>
<h2>Jigsaw &amp; GitHub Pages</h2>
<p>Jigsaw provides <a href="https://jigsaw.tighten.co/docs/deploying-your-site/">instructions</a> on how to use GitHub Pages, but it’s written for project sites and not user sites. If you’re not sure why this is a problem, it’s because user sites require that the site contents are in the <code>master</code> branch, whereas repository sites default to <code>gh-pages</code> - though that can be changed.</p>
<p>To get this working, I setup Jigsaw under a <code>source</code> branch which contains all of the code, assets and post content. I then push the contents of the <code>build_production</code> directory to the <code>master</code> branch, which GitHub happily serves up for you to read.</p>
<p>It’s a small change, but it may not immediately be obvious, so it’s worth clarifying!</p>
<h2>Jigsaw &amp; GitHub Actions</h2>
<p>Jigsaw is really, really good but I’m lazy and don’t want to manually be building my website, committing and publishing it after each post.</p>
<p>Admittedly, it took me a lot of time to figure these steps out, but now I’m able to write a new post and have GitHub automatically build and publish it for me!</p>
<p>I created a new workflow under <code>.github/workflows/build-publish.yml</code> with the following:</p>
<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Build &amp; Publish

<span class="token key atrule">on</span><span class="token punctuation">:</span>
  <span class="token key atrule">push</span><span class="token punctuation">:</span>
    <span class="token key atrule">branches</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> source
  <span class="token key atrule">schedule</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">cron</span><span class="token punctuation">:</span> <span class="token string">&quot;0 2 * * 1-5&quot;</span>

<span class="token key atrule">jobs</span><span class="token punctuation">:</span>
  <span class="token key atrule">build-site</span><span class="token punctuation">:</span>
    <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest
    <span class="token key atrule">steps</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install Composer Dependencies
      <span class="token key atrule">run</span><span class="token punctuation">:</span> composer install <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>ansi <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>interaction <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>scripts <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>suggest <span class="token punctuation">-</span><span class="token punctuation">-</span>no<span class="token punctuation">-</span>progress <span class="token punctuation">-</span><span class="token punctuation">-</span>prefer<span class="token punctuation">-</span>dist
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install NPM Dependencies
      <span class="token key atrule">run</span><span class="token punctuation">:</span> npm install
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build Site
      <span class="token key atrule">run</span><span class="token punctuation">:</span> npm run production
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Create CNAME File
      <span class="token key atrule">run</span><span class="token punctuation">:</span> echo &quot;james.brooks.page&quot; <span class="token punctuation">&gt;</span><span class="token punctuation">&gt;</span> build_production/CNAME
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Stage Files
      <span class="token key atrule">run</span><span class="token punctuation">:</span> git add <span class="token punctuation">-</span>f build_production
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Commit files
      <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        git config --local user.email &quot;actions@github.com&quot;
        git config --local user.name &quot;GitHub Actions&quot;
        git commit -m &quot;Build for deploy&quot;</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Publish
      <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
        git subtree split --prefix build_production -b master
        git push -f origin master:master
        git branch -D master</span>
</code></pre>
<p>It may look complicated, but we can break it down.</p>
<ol>
<li>We tell GitHub to only run the workflow on pushes to the <code>source</code> branch. I also opted to automatically run the workflow at 2PM Monday - Friday, just in case 🤷🏻‍♂️</li>
<li>Next we’re installing the Composer and NPM dependencies.</li>
<li>Once the dependencies have been installed, we build the site using the production environment configuration.</li>
<li><strong>Optional!</strong> I’m using a CNAME on GitHub Pages so that I can use my own domain. I create a new file and fill it with the domain name I want to use.</li>
<li>I forcefully stage the <code>build_production</code> directory, this is because Jigsaw defaults to ignoring <code>build_*</code> directories from Git and I don’t want to change this and accidentally publish it into the <code>source</code> branch.</li>
<li>Next we configure Git’s email and name, I chose to use <strong>GitHub Actions</strong> as the author so that I know it’s an automatic commit. We then commit the contents with a pre-defined commit message.</li>
<li>Finally, we’re going to publish the site by pushing the changes from within the GitHub Action itself! This is a bit complicated if you’re not confident with Git, but let’s try to keep it simple. We create a subtree of the <code>build_production</code> directory and pushing it into a temporary <code>master</code> branch. Now we can force push the branch to GitHub and clean up by deleting the <code>master</code> branch<sup>1</sup>.</li>
</ol>
<p>And that’s it, I’m now able to write new blog posts without my laptop (using just the GitHub website) and have them immediately published for me.</p>
<p>Let me know <a href="https://twitter.com/jbrooksuk">@jbrooksuk</a> if you’re using this technique or have any suggestions on how to improve it.</p>
<h3>Footnotes</h3>
<ol>
<li>Because we’re force-pushing to <code>master</code> from a branch which contains only one commit, all history is lost for the published website. This is a trade-off that I’m happy with because the real history is in the <code>source</code> directory.</li>
</ol>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Not My First Blog Post]]></title>
            <link>https://james.brooks.page/blog/my-first-blog-post</link>
            <guid>https://james.brooks.page/blog/my-first-blog-post</guid>
            <pubDate>Tue, 31 Dec 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[We’re about to enter a new decade, so let’s start it with a new blog.]]></description>
            <content:encoded><![CDATA[<p>Tomorrow brings with it a new year and the start of scribbling out <del>2019</del> and writing 2020 for at least the first 4 months.</p>
<div class="flex justify-center w-full"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">I&#x27;m not cool enough to have a blog, so here is my year in review in Twitter form.</p>— James Brooks (@jbrooksuk) <a href="https://twitter.com/jbrooksuk/status/1212035385104224256?ref_src=twsrc%5Etfw">December 31, 2019</a></blockquote></div>
<p>Earlier today I <a href="https://twitter.com/jbrooksuk/status/1212035385104224256">tweeted my reflection on 2019</a>, starting with the fact that I&#x27;m not cool enough to have a blog. I&#x27;m a dad now, I need to earn back some cool points, so I thought what better way to start a new decade <em class="bg-yellow-200">(yes, it&#x27;s a new decade...)</em> than to start it with a habit of blogging?</p>
<p>When I was in college, I used to write a blog and filled it with the things I had been learning about and news on projects I&#x27;d been working on. Nobody read it, but I felt better for getting my thoughts out. I guess, I treated it like a very nerdy diary.</p>
<p>I&#x27;d like to think that I&#x27;ll fill this with the same kind of useful information, even if it&#x27;s only useful to me. But of course, it&#x27;s yet to be seen whether I post beyond today!</p>
<blockquote>
<p>Hello World!</p>
</blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
        <item>
            <title><![CDATA[Laravel 4 & Dokku: Queue Workers]]></title>
            <link>https://james.brooks.page/blog/laravel-4-dokku-queue-workers</link>
            <guid>https://james.brooks.page/blog/laravel-4-dokku-queue-workers</guid>
            <pubDate>Thu, 27 Mar 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[How we implemented Laravel 4’s Queue Component in our CRM system for improved email handling and UI responsiveness.]]></description>
            <content:encoded><![CDATA[<p>As I mentioned in my last post we&#x27;ve been developing our new CRM system in Laravel 4 which has been great! One of the big features I&#x27;m particularly in love with is the Queue component. As we all know, a queue allows us to push code into a separate thread so that our main code will not block for as long. An example of where this is useful is Emailing.</p>
<p>One of the new features we have allows management to flag actions and ask staff for feedback on why they did something. This is really useful to gain a bit more information from them when they&#x27;re unable to get in touch with a customer etc. When management click the flag buttons, we update the database &amp; the UI to inform them that the action has succeeded, and we also queue up an email to the agent that alerts them that further feedback is required. Sending an email can take a couple of seconds, so we queue up this job and send it separately. In my testing I found that queuing reduced the time from 5-8 seconds down to 1s, depending on load and network speed etc. This obviously makes for a massive improvement on the client side as the UI is now a lot more responsive to actions.</p>
<p>Whilst I was developing this feature locally I was running php artisan queue:listen and then every time I flagged something, the email would be queued and I&#x27;d receive my email shortly afterwards.</p>
<p>I soon realised that I wouldn&#x27;t be able to run the queue from our Dokku server, since it&#x27;s just a PHP build pack with no extra or processes I can run. I could run the queue command on another server under supervisor but that would be lame and require me to run two copies of our project. No way!</p>
<p>If anyone has ever used Heroku they&#x27;ll know that you can make use of a Procfile. These define what processes the server should run. Usually every app will use a web process, which is simply the main server code that handles your website. Another process type is the worker process. This will run alongside your web process and run the command forever.</p>
<p>Remembering this I figured that since Dokku is similar to Heroku I could do the same thing... It turns out that you can&#x27;t. Dokku only supports the web process type and it took me a while to figure out what the process should even be running! Once I was able to override the default PHP buildpack behaviour using web: bin/run and get our application running again I simply tried adding: worker: php artisan queue:listen. Nadda. Our application was still up, but the queue wasn&#x27;t being processed.</p>
<p>After a bit of digging I found that Dokku (and therefore Docker?) only supports the web process type, as I said before. My first thought was &quot;oh god, this isn&#x27;t going to end well...&quot;. Was that months of work wasted? Would I seriously have to maintain two copies of the app so that the worker could run elsewhere?</p>
<p>Thankfully not! There is a Dokku plugin that runs all process types! dokku-shoreman came to my rescue.</p>
<p>Once I&#x27;d installed the plugin:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/statianzo/dokku-shoreman.git /var/lib/dokku/plugins/dokku-shoreman
</code></pre>
<p>I pushed the code again, the queue job started being processed!</p>
<p>Voila!</p>
<p>Now I&#x27;ve got Dokku setup with dokku-shoreman and my Procfile looks like:</p>
<pre><code>web: bin/run  
worker: php artisan queue:listen  
</code></pre>
<p>There is little to no information about this on the Internet, so hopefully this will be of use to someone!</p>]]></content:encoded>
            <author>james@alt-three.com (James Brooks)</author>
        </item>
    </channel>
</rss>