<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matthew Daly</title>
    <description>The latest articles on DEV Community by Matthew Daly (@matthewbdaly).</description>
    <link>https://dev.to/matthewbdaly</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F99508%2Fec16dc79-3aa7-4ead-95b0-f7c4d4afdc14.jpeg</url>
      <title>DEV Community: Matthew Daly</title>
      <link>https://dev.to/matthewbdaly</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matthewbdaly"/>
    <language>en</language>
    <item>
      <title>30 Plants a Week Tracker - React Native app built for the GitHub Copilot Challenge</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Sun, 19 Jan 2025 21:27:40 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/30-plants-a-week-tracker-react-native-app-built-for-the-github-copilot-challenge-5gp4</link>
      <guid>https://dev.to/matthewbdaly/30-plants-a-week-tracker-react-native-app-built-for-the-github-copilot-challenge-5gp4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github"&gt;GitHub Copilot Challenge &lt;/a&gt;: New Beginnings&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;For a year or so now, I've been doing the "30 plants a week" thing, whereby I try to make sure that every week, I eat at least 30 different plants over the course of a week. This has been noticeably beneficial for my health.&lt;/p&gt;

&lt;p&gt;In the past I've used the Android notes app to manage this, which works, but is less than optimal for this use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's easy for duplicates to sneak in&lt;/li&gt;
&lt;li&gt;Clearing the list at the start of a new week is a chore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;30 Plants a Week Tracker&lt;/em&gt; is the solution I came up with. It's a very simple app that works entirely offline and stores the data locally.&lt;/p&gt;

&lt;p&gt;It was also an opportunity to stretch my wings a bit. I used to build mobile apps for a living with Phonegap, but that's no longer maintained, and I haven't worked on a mobile app for nearly a decade. I also wanted to work with React Native and so I decided that this would be my first app built with that&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb825epz862zjpzvt2b45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb825epz862zjpzvt2b45.png" alt="Screenshot of the main page of the 30 Plants a Week Tracker app" width="498" height="832"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://30-plants-demo.netlify.app/" rel="noopener noreferrer"&gt;Check it out&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/matthewbdaly" rel="noopener noreferrer"&gt;
        matthewbdaly
      &lt;/a&gt; / &lt;a href="https://github.com/matthewbdaly/30-plants-tracker" rel="noopener noreferrer"&gt;
        30-plants-tracker
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      React Native app for tracking consumption of 30 plants a week
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Welcome to your Expo app 👋&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This is an &lt;a href="https://expo.dev" rel="nofollow noopener noreferrer"&gt;Expo&lt;/a&gt; project created with &lt;a href="https://www.npmjs.com/package/create-expo-app" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;create-expo-app&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Get started&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install dependencies&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start the app&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt; npx expo start&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the output, you'll find options to open the app in a&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.expo.dev/develop/development-builds/introduction/" rel="nofollow noopener noreferrer"&gt;development build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.expo.dev/workflow/android-studio-emulator/" rel="nofollow noopener noreferrer"&gt;Android emulator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.expo.dev/workflow/ios-simulator/" rel="nofollow noopener noreferrer"&gt;iOS simulator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://expo.dev/go" rel="nofollow noopener noreferrer"&gt;Expo Go&lt;/a&gt;, a limited sandbox for trying out app development with Expo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can start developing by editing the files inside the &lt;strong&gt;app&lt;/strong&gt; directory. This project uses &lt;a href="https://docs.expo.dev/router/introduction" rel="nofollow noopener noreferrer"&gt;file-based routing&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Get a fresh project&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;When you're ready, run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run reset-project&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This command will move the starter code to the &lt;strong&gt;app-example&lt;/strong&gt; directory and create a blank &lt;strong&gt;app&lt;/strong&gt; directory where you can start developing.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Learn more&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;To learn more about developing your project with Expo, look at the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.expo.dev/" rel="nofollow noopener noreferrer"&gt;Expo documentation&lt;/a&gt;: Learn fundamentals, or go into advanced topics with our &lt;a href="https://docs.expo.dev/guides" rel="nofollow noopener noreferrer"&gt;guides&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.expo.dev/tutorial/introduction/" rel="nofollow noopener noreferrer"&gt;Learn Expo tutorial&lt;/a&gt;: Follow a step-by-step tutorial where you'll create…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/matthewbdaly/30-plants-tracker" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Copilot Experience
&lt;/h2&gt;

&lt;p&gt;On this project, I mostly used Copilot to help me get up and running with a new framework. While I was familiar with React, I had to learn both React Native and Expo from scratch on this project. Copilot definitely helped make this easier.&lt;/p&gt;

&lt;p&gt;A lot of the most useful help I had from Copilot was finding suitable off-the-shelf solutions for various problems, such as a storage system. It was also useful for getting basic styles set up to get me started with an unfamiliar styling system.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Models
&lt;/h2&gt;

&lt;p&gt;No, I didn't use GitHub Models for this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building my first toy project with React Native was an interesting way to learn the basics of the framework. There's clearly a lot more to learn, but I'm eager to carry on working with it in future, and developing this app further.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Dynamic image handling with Glide and GraphQL</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Sat, 21 Aug 2021 16:20:00 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/dynamic-image-handling-with-glide-and-graphql-2n3l</link>
      <guid>https://dev.to/matthewbdaly/dynamic-image-handling-with-glide-and-graphql-2n3l</guid>
      <description>&lt;p&gt;I've used &lt;a href="https://glide.thephpleague.com/"&gt;Glide&lt;/a&gt; on several PHP projects in the past. It's a great package that makes it really easy to dynamically generate images on the fly. For instance, if you need a particular image at both a thumbnail size and a full-screen size, it means you have the flexibility to request it at the correct size for any one part of the page. This can potentially save bandwidth since you never have to request a larger image than you technically need and scale it down with HTML attributes or CSS, nor do you have to take steps to generate thumbnails separately before they're needed. Frontend developers can adjust their code to request exactly the version they need at any one time, and can even apply certain effects dynamically.&lt;/p&gt;

&lt;p&gt;However, by default it's a bit too open. A malicious user could request an image at an excessive size as part of a denial of service attack. For that reason, it's considered good practice to set a maximum image size, and sign all requests so that you can be sure they're authorized by your application. This works fine if your images are being requested somewhere you can sign them easily, such as in a Blade template. However, doing so in the context of a React or Vue application can potentially be much harder because they're working on the front end and so can't sign requests that are made dynamically in the same way, at least not without you exposing your application's key to the front end, which would be &lt;em&gt;really&lt;/em&gt; risky. An API endpoint can return URLs for pre-signed specific versions of the image, but that's not as flexible as being able to adjust what you get back via query parameters on the fly.&lt;/p&gt;

&lt;p&gt;I'm currently working on an application that uses GraphQL for the API, and for that I wanted to use Glide to enable &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images"&gt;responsive images&lt;/a&gt; and minimise the size of the payload. Having recently rewritten this blog in Gatsby, I'd had some exposure to the Sharp plugin, which allows you to query for an image at specific dimensions. It struck me that I could probably do something similar with GraphQL in the context of a Laravel application. Since the GraphQL queries that would return the image URLs and other data were being handled server side, they could in theory accept parameters for the required images, validate that the specified values were acceptable, and return a secure, signed URL for that image for consumption by the front end. Since the frontend was having to make an AJAX request to fetch the items to show anyway, it could request the URLs as part of the same AJAX request as the rest of the items on a page, then render the images along with the rest of the content. Here I'll demonstrate how to do this.&lt;/p&gt;

&lt;p&gt;The first thing to do is implement a controller for returning images via Glide. This will differ between frameworks so you'd need to check the Glide documentation, but a typical Laravel version would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Filesystem\Filesystem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;League\Glide\Filesystem\FileNotFoundException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;League\Glide\Responses\LaravelResponseFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;League\Glide\ServerFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;League\Glide\Signatures\SignatureException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;League\Glide\Signatures\SignatureFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\StreamedResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlideController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Storage
     *
     * @var Filesystem
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$filesystem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Filesystem&lt;/span&gt; &lt;span class="nv"&gt;$filesystem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;filesystem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$filesystem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;StreamedResponse&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;SignatureFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'APP_KEY'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/images/"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="nv"&gt;$server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ServerFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'response'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LaravelResponseFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'source'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDriver&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'cache'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDriver&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'cache_path_prefix'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'.cache'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'base_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'max_image_size'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$server&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getImageResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"images/"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SignatureException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FileNotFoundException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I'm using a &lt;a href="https://laravel.com/docs/8.x/controllers#single-action-controllers"&gt;single action controller&lt;/a&gt; here. I've found myself gravitating more and more towards these for certain use cases, and &lt;a href="https://driesvints.com/blog/the-beauty-of-single-action-controllers/"&gt;I'm not the only one&lt;/a&gt;. For me, the biggest benefit of these is probably more concise naming - if a controller does one thing and one thing only, and the class name adequately describes that, you end up naming it something generic, like &lt;code&gt;call()&lt;/code&gt;. By making the controller a single callable, you don't have to make that decision since it's taken out of your hands (it must be &lt;code&gt;__invoke()&lt;/code&gt;). I'm also a big fan of callable classes in general - they're essentially closures on steroids since you can still pass dependencies to the constructor.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SignatureFactory&lt;/code&gt; line is what handles validating the signature. It's based on the app key, and checks that the path and all its parameters are correctly signed. If the signature isn't valid, it will throw an exception, thus protecting the endpoint from requests that weren't already approved. We also specify a cache location, and a maximum image size to help prevent mass-resize attacks.&lt;/p&gt;

&lt;p&gt;You can then register this controller in the usual way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/images/{path}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;GlideController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'glide'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the images aren't going to be accessible to users who aren't logged in, it makes sense to apply whatever authentication middleware you're using to this route as well.&lt;/p&gt;

&lt;p&gt;Now, depending on which GraphQL package you're using, defining the schema may be different. In this case, I'm using &lt;a href="https://lighthouse-php.com/"&gt;Lighthouse&lt;/a&gt; and the schema definition for an Eloquent model with an image would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageFormat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;pjpg&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;webp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;dynamicImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;numeric&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="err"&gt;:1000"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;numeric&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="err"&gt;:1000"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;numeric&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="err"&gt;:100"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageFormat&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we're specifying what parameters the &lt;code&gt;dynamicImage()&lt;/code&gt; field accepts, as well as applying some of Laravel's validation rules to the field to ensure it remains within acceptable ranges. We also use an enum to specify the supported image formats - these are actually limited to the options Glide provdes, but by specifying this as an enum, GraphQL tooling like GraphiQL can provide autocompletion for this parameter for a better developer experience when constructing queries. Note that we also use the &lt;code&gt;@method&lt;/code&gt; directive to tell Lighthouse that this field maps to a method, not a property. If the method name differs from the field name, you would also need to specify that method name, eg &lt;code&gt;@method(name: "myMethod")&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, assuming your image was stored on the filesystem and the appropriate model field was called &lt;code&gt;image_path&lt;/code&gt;, the method to retrieve the image URL on the model would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;League\Glide\Urls\UrlBuilderFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Define model class...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;dynamicImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UrlBuilderFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/images/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'APP_KEY'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'w'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'h'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'q'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$quality&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'fm'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the parameters on the model must be specified in the same order as they are defined in the GraphQL schema for them to be lined up correctly, and must have the same names. I've only added the most obviously useful parameters here, namely height, width, quality and format, but if you need them there's nothing stopping you from adding further parameters - just don't forget to update the GraphQL schema to include them too.&lt;/p&gt;

&lt;p&gt;We can then write GraphQL queries to call &lt;code&gt;dynamicImage()&lt;/code&gt; with whatever parameters we wish to pass through, and will get back an appropriate URL in response. For instance, consider this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;dynamicImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;webp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we assume a query has been defined called &lt;code&gt;items&lt;/code&gt; which returns all instances of the &lt;code&gt;Item&lt;/code&gt; GraphQL type. This would return, for each instance of the &lt;code&gt;Item&lt;/code&gt; model, the ID, caption and the URL for an image of 100% quality, 400x400 pixels, in WebP format. Please also note that none of these arguments to &lt;code&gt;dynamicImage()&lt;/code&gt; are required - if you leave one out, Glide will use the default value.&lt;/p&gt;

&lt;p&gt;By calling &lt;code&gt;dynamicImage()&lt;/code&gt; separately with different aliases, we can even fetch multiple versions of the image. In this example, we fetch it at two different sizes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;large_image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dynamicImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;webp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;small_image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dynamicImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;webp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, when rendering a component, you could use the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements to show different versions based on media queries, as in this example of a simple React component used to render individual instances of &lt;code&gt;Item&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt; &lt;span class="na"&gt;media&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{max-width: 768px}"&lt;/span&gt; &lt;span class="na"&gt;srcSet&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;small_image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt; &lt;span class="na"&gt;media&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"{min-width: 769px}"&lt;/span&gt; &lt;span class="na"&gt;srcSet&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;large_image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;small_image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables us to serve responsive images that are appropriately sized for the current screen resolution. On mobile devices, which may not always have a connection as fast as a desktop or laptop, it also means we aren't wasting bandwidth downloading images which are larger than necessary.&lt;/p&gt;

&lt;p&gt;Along similar lines, you could fetch both WebP and JPEG versions of an image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;id&lt;/span&gt;
      &lt;span class="nx"&gt;caption&lt;/span&gt;
      &lt;span class="nx"&gt;jpeg_image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamicImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jpeg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;webp_image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamicImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we can use the WebP version of the image if the web browser supports it, falling back to JPEG if it doesn't, by using the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements again in our React component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"image/webp"&lt;/span&gt; &lt;span class="na"&gt;srcSet&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webp_image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jpeg_image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By doing this we aren't forced to work with the lowest common denominator in terms of image formats. We can instead offer WebP to users whose browsers support it, without locking out users on older browsers.&lt;/p&gt;

&lt;p&gt;This technique should be easy enough to apply to other PHP frameworks since Glide is fairly framework agnostic and there are GraphQL implementations for most frameworks. It should also be applicable in other languages - for example, while I'm not aware of a direct equivalent of Glide in Node.js, you could conceivably use &lt;a href="https://sharp.pixelplumbing.com/"&gt;Sharp&lt;/a&gt; as the basis of your own custom endpoint to serve up dynamic images based on query parameters, and then serve signed URLs for it via GraphQL.&lt;/p&gt;

&lt;p&gt;Responsive images are something that's often overlooked when trying to build a modern web app. An approach like this makes it an awful lot easier to serve appropriately-sized images on demand, without locking front-end devs into specific known dimensions that might not fit their use case.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Lightweight Laravel - deconstructing a full stack framework</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Wed, 30 Dec 2020 17:00:00 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/lightweight-laravel-deconstructing-a-full-stack-framework-m2m</link>
      <guid>https://dev.to/matthewbdaly/lightweight-laravel-deconstructing-a-full-stack-framework-m2m</guid>
      <description>&lt;p&gt;Back when I used to work with Django, I read the book &lt;a href="https://www.oreilly.com/library/view/lightweight-django/9781491946275/"&gt;Lightweight Django&lt;/a&gt;, and it completely changed the way I thought about building web applications. For years I’d heard the same lines parroted about how Django was too large and bloated, and something like Flask was a better bet for many applications, and this book completely blew this misconception away. By demonstrating how it was possible to break the framework apart, use just what you need, and leave out what you don’t, it showed how I could benefit from my familiarity with Django, while making it more suitable for smaller applications.&lt;/p&gt;

&lt;p&gt;Laravel, like Django, is a full stack framework, and is often subject to similar misconceptions about bloat. But just because the framework ships with all this stuff, doesn’t mean you’re obliged to use it all. If you know you aren’t going to need all of a framework’s functionality, there’s nothing stopping you getting rid of what you don’t need, or even replacing it with something else. In this article, I’ll show you how to apply the same methodology to a Laravel application to remove what you don’t need. As part of this, we’ll be building a simple placeholder image service. This was used in Lightweight Django as it’s a good example of an application that is completely stateless, and doesn’t need sessions or a database, so it’s often seen as a bad fit for a full stack framework. Since the same applies here, it’s a good example for us too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Run the following command in the shell to create a new Laravel application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;composer create-project &lt;span class="nt"&gt;--prefer-dist&lt;/span&gt; laravel/laravel lightweight-laravel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this actually does is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resolve the latest release of the package &lt;code&gt;laravel/laravel&lt;/code&gt; that will work on your system&lt;/li&gt;
&lt;li&gt;Copy it from the &lt;a href="https://github.com/laravel/laravel"&gt;repository&lt;/a&gt; to the specified location&lt;/li&gt;
&lt;li&gt;Carry out any post-install scripts specified, such as creating the &lt;code&gt;.env&lt;/code&gt; file and generating a key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, that’s just a standardised boilerplate for Laravel applications. Most of the functionality of the framework is in the package &lt;code&gt;laravel/framework&lt;/code&gt;, which is included as a dependency in your &lt;code&gt;composer.json&lt;/code&gt;. This makes sense, because by keeping as much of the actual framework out of the starter boilerplate and in a separate repository, it minimises the work required to update the application to a new version. It also means you can strip that boilerplate down to remove references to things you don’t need, and even create your own custom boilerplates to save you work in future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripping down the boilerplate
&lt;/h2&gt;

&lt;p&gt;Let’s start stripping out the things we don’t need. Since our application is stateless, we have no need whatsoever of a database, so we can delete the &lt;code&gt;app/Models&lt;/code&gt; and &lt;code&gt;database&lt;/code&gt; folders. We’ll want to support Redis for the cache, so we can’t delete the file &lt;code&gt;config/database.php&lt;/code&gt;, but we can remove any references to the database other than Redis from that file. We can delete some other files from the &lt;code&gt;config/&lt;/code&gt; folder, namely &lt;code&gt;auth.php&lt;/code&gt;, &lt;code&gt;broadcasting.php&lt;/code&gt;, &lt;code&gt;filesystems.php&lt;/code&gt;, &lt;code&gt;mail.php&lt;/code&gt;, &lt;code&gt;queue.php&lt;/code&gt;, &lt;code&gt;services.php&lt;/code&gt; and &lt;code&gt;session.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We also don’t need a lot of the middleware that ships with Laravel. If you go into the file &lt;code&gt;app/Http/Kernel.php&lt;/code&gt; you’ll see that it assigns some middleware as global, some to the &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;api&lt;/code&gt; groups, and some as optional route middleware. In this file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We don’t need to make any POST requests to this application, so we can lose the &lt;code&gt;ValidatePostSize&lt;/code&gt; middleware from the global middleware entirely&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;web&lt;/code&gt; group relates to cookies, sessions, CSRF, authentication and handling routing with substitute bindings. Since we don’t need any of that we can empty this group entirely&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;auth.basic&lt;/code&gt;, &lt;code&gt;can&lt;/code&gt;, &lt;code&gt;guest&lt;/code&gt;, &lt;code&gt;password.confirm&lt;/code&gt;, and &lt;code&gt;verified&lt;/code&gt; route middleware is also surplus to requirements and can go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As this change is a bit fiddly, here’s a patch, which may be easier to read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;From 6bc87e9602e839d5635963b6d740279b2dbcf16b Mon Sep 17 00:00:00 2001
From: Matthew Daly &amp;lt;Matthew Daly 450801+matthewbdaly@users.noreply.github.com&amp;gt;
Date: Wed, 30 Dec 2020 11:54:56 +0000
Subject: [PATCH] Removed unwanted middleware
&lt;/span&gt;
---
 app/Http/Kernel.php | 14 --------------
 1 file changed, 14 deletions(-)

diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
&lt;span class="gh"&gt;index 30020a5..10e150d 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/app/Http/Kernel.php
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/app/Http/Kernel.php
&lt;/span&gt;&lt;span class="p"&gt;@@ -18,7 +18,6 @@&lt;/span&gt; class Kernel extends HttpKernel
         \App\Http\Middleware\TrustProxies::class,
         \Fruitcake\Cors\HandleCors::class,
         \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
&lt;span class="gd"&gt;- \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
&lt;/span&gt;         \App\Http\Middleware\TrimStrings::class,
         \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
     ];
&lt;span class="p"&gt;@@ -30,13 +29,6 @@&lt;/span&gt; class Kernel extends HttpKernel
      */
     protected $middlewareGroups = [
         'web' =&amp;gt; [
&lt;span class="gd"&gt;- \App\Http\Middleware\EncryptCookies::class,
- \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
- \Illuminate\Session\Middleware\StartSession::class,
- // \Illuminate\Session\Middleware\AuthenticateSession::class,
- \Illuminate\View\Middleware\ShareErrorsFromSession::class,
- \App\Http\Middleware\VerifyCsrfToken::class,
- \Illuminate\Routing\Middleware\SubstituteBindings::class,
&lt;/span&gt;         ],

         'api' =&amp;gt; [
&lt;span class="p"&gt;@@ -53,14 +45,8 @@&lt;/span&gt; class Kernel extends HttpKernel
      * @var array
      */
     protected $routeMiddleware = [
&lt;span class="gd"&gt;- 'auth' =&amp;gt; \App\Http\Middleware\Authenticate::class,
- 'auth.basic' =&amp;gt; \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
&lt;/span&gt;         'cache.headers' =&amp;gt; \Illuminate\Http\Middleware\SetCacheHeaders::class,
&lt;span class="gd"&gt;- 'can' =&amp;gt; \Illuminate\Auth\Middleware\Authorize::class,
- 'guest' =&amp;gt; \App\Http\Middleware\RedirectIfAuthenticated::class,
- 'password.confirm' =&amp;gt; \Illuminate\Auth\Middleware\RequirePassword::class,
&lt;/span&gt;         'signed' =&amp;gt; \Illuminate\Routing\Middleware\ValidateSignature::class,
         'throttle' =&amp;gt; \Illuminate\Routing\Middleware\ThrottleRequests::class,
&lt;span class="gd"&gt;- 'verified' =&amp;gt; \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
&lt;/span&gt;     ];
 }
&lt;span class="gd"&gt;-- 
&lt;/span&gt;&lt;span class="p"&gt;2.28.0
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These changes also mean a lot of the service providers and facades are now redundant and can be removed from the application. If you go into &lt;code&gt;config/app.php&lt;/code&gt; you can remove &lt;code&gt;AuthServiceProvider&lt;/code&gt;, &lt;code&gt;BroadcastServiceProvider&lt;/code&gt;, &lt;code&gt;CookieServiceProvider&lt;/code&gt;, &lt;code&gt;MailServiceProvider&lt;/code&gt;, &lt;code&gt;NotificationServiceProvider&lt;/code&gt;, &lt;code&gt;PaginationServiceProvider&lt;/code&gt;, &lt;code&gt;PasswordResetServiceProvider&lt;/code&gt;, &lt;code&gt;SessionServiceProvider&lt;/code&gt; and &lt;code&gt;TranslationServiceProvider&lt;/code&gt; from the providers section, as well as the commented-out local &lt;code&gt;BroadcastServiceProvider&lt;/code&gt;. You can also delete the facades for &lt;code&gt;Auth&lt;/code&gt;, &lt;code&gt;Cookie&lt;/code&gt;, &lt;code&gt;DB&lt;/code&gt;, &lt;code&gt;Eloquent&lt;/code&gt;, &lt;code&gt;Gate&lt;/code&gt;, &lt;code&gt;Lang&lt;/code&gt;, &lt;code&gt;Mail&lt;/code&gt;, &lt;code&gt;Notification&lt;/code&gt;, &lt;code&gt;Password&lt;/code&gt;, &lt;code&gt;Queue&lt;/code&gt;, &lt;code&gt;Schema&lt;/code&gt;, &lt;code&gt;Session&lt;/code&gt;, and &lt;code&gt;Storage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Again, here’s a patch of the required changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;From 66be3b836706ef488b890cdae6e97d4fc6195dd6 Mon Sep 17 00:00:00 2001
From: Matthew Daly &amp;lt;Matthew Daly 450801+matthewbdaly@users.noreply.github.com&amp;gt;
Date: Wed, 30 Dec 2020 12:10:25 +0000
Subject: [PATCH] Removed unused service providers and facades
&lt;/span&gt;
---
 config/app.php | 26 --------------------------
 1 file changed, 26 deletions(-)

diff --git a/config/app.php b/config/app.php
&lt;span class="gh"&gt;index 2a2f0eb..b7a38c8 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/config/app.php
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/config/app.php
&lt;/span&gt;&lt;span class="p"&gt;@@ -139,26 +139,17 @@&lt;/span&gt; return [
         /*
          * Laravel Framework Service Providers...
          */
&lt;span class="gd"&gt;- Illuminate\Auth\AuthServiceProvider::class,
- Illuminate\Broadcasting\BroadcastServiceProvider::class,
&lt;/span&gt;         Illuminate\Bus\BusServiceProvider::class,
         Illuminate\Cache\CacheServiceProvider::class,
         Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
&lt;span class="gd"&gt;- Illuminate\Cookie\CookieServiceProvider::class,
&lt;/span&gt;         Illuminate\Database\DatabaseServiceProvider::class,
         Illuminate\Encryption\EncryptionServiceProvider::class,
         Illuminate\Filesystem\FilesystemServiceProvider::class,
         Illuminate\Foundation\Providers\FoundationServiceProvider::class,
         Illuminate\Hashing\HashServiceProvider::class,
&lt;span class="gd"&gt;- Illuminate\Mail\MailServiceProvider::class,
- Illuminate\Notifications\NotificationServiceProvider::class,
- Illuminate\Pagination\PaginationServiceProvider::class,
&lt;/span&gt;         Illuminate\Pipeline\PipelineServiceProvider::class,
         Illuminate\Queue\QueueServiceProvider::class,
         Illuminate\Redis\RedisServiceProvider::class,
&lt;span class="gd"&gt;- Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
- Illuminate\Session\SessionServiceProvider::class,
- Illuminate\Translation\TranslationServiceProvider::class,
&lt;/span&gt;         Illuminate\Validation\ValidationServiceProvider::class,
         Illuminate\View\ViewServiceProvider::class,

@@ -170,9 +161,6 @@ return [
          * Application Service Providers...
          */
         App\Providers\AppServiceProvider::class,
&lt;span class="gd"&gt;- App\Providers\AuthServiceProvider::class,
- // App\Providers\BroadcastServiceProvider::class,
- App\Providers\EventServiceProvider::class,
&lt;/span&gt;         App\Providers\RouteServiceProvider::class,

     ],
&lt;span class="p"&gt;@@ -193,35 +181,21 @@&lt;/span&gt; return [
         'App' =&amp;gt; Illuminate\Support\Facades\App::class,
         'Arr' =&amp;gt; Illuminate\Support\Arr::class,
         'Artisan' =&amp;gt; Illuminate\Support\Facades\Artisan::class,
&lt;span class="gd"&gt;- 'Auth' =&amp;gt; Illuminate\Support\Facades\Auth::class,
&lt;/span&gt;         'Blade' =&amp;gt; Illuminate\Support\Facades\Blade::class,
         'Broadcast' =&amp;gt; Illuminate\Support\Facades\Broadcast::class,
         'Bus' =&amp;gt; Illuminate\Support\Facades\Bus::class,
         'Cache' =&amp;gt; Illuminate\Support\Facades\Cache::class,
         'Config' =&amp;gt; Illuminate\Support\Facades\Config::class,
&lt;span class="gd"&gt;- 'Cookie' =&amp;gt; Illuminate\Support\Facades\Cookie::class,
&lt;/span&gt;         'Crypt' =&amp;gt; Illuminate\Support\Facades\Crypt::class,
&lt;span class="gd"&gt;- 'DB' =&amp;gt; Illuminate\Support\Facades\DB::class,
- 'Eloquent' =&amp;gt; Illuminate\Database\Eloquent\Model::class,
- 'Event' =&amp;gt; Illuminate\Support\Facades\Event::class,
&lt;/span&gt;         'File' =&amp;gt; Illuminate\Support\Facades\File::class,
&lt;span class="gd"&gt;- 'Gate' =&amp;gt; Illuminate\Support\Facades\Gate::class,
&lt;/span&gt;         'Hash' =&amp;gt; Illuminate\Support\Facades\Hash::class,
         'Http' =&amp;gt; Illuminate\Support\Facades\Http::class,
&lt;span class="gd"&gt;- 'Lang' =&amp;gt; Illuminate\Support\Facades\Lang::class,
&lt;/span&gt;         'Log' =&amp;gt; Illuminate\Support\Facades\Log::class,
&lt;span class="gd"&gt;- 'Mail' =&amp;gt; Illuminate\Support\Facades\Mail::class,
- 'Notification' =&amp;gt; Illuminate\Support\Facades\Notification::class,
- 'Password' =&amp;gt; Illuminate\Support\Facades\Password::class,
- 'Queue' =&amp;gt; Illuminate\Support\Facades\Queue::class,
&lt;/span&gt;         'Redirect' =&amp;gt; Illuminate\Support\Facades\Redirect::class,
         // 'Redis' =&amp;gt; Illuminate\Support\Facades\Redis::class,
         'Request' =&amp;gt; Illuminate\Support\Facades\Request::class,
         'Response' =&amp;gt; Illuminate\Support\Facades\Response::class,
         'Route' =&amp;gt; Illuminate\Support\Facades\Route::class,
&lt;span class="gd"&gt;- 'Schema' =&amp;gt; Illuminate\Support\Facades\Schema::class,
- 'Session' =&amp;gt; Illuminate\Support\Facades\Session::class,
- 'Storage' =&amp;gt; Illuminate\Support\Facades\Storage::class,
&lt;/span&gt;         'Str' =&amp;gt; Illuminate\Support\Str::class,
         'URL' =&amp;gt; Illuminate\Support\Facades\URL::class,
         'Validator' =&amp;gt; Illuminate\Support\Facades\Validator::class,
&lt;span class="gd"&gt;-- 
&lt;/span&gt;&lt;span class="p"&gt;2.28.0
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few service providers that ideally we’d strip out but are tightly integrated into the framework. For instance, the database and queue service providers are both used by some Artisan commands, and it’s not very practical to disable only those commands, so removing them will stop Artisan from working. If you don’t mind running the development server manually, you can go ahead and remove these.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the application
&lt;/h2&gt;

&lt;p&gt;Now, let’s set out how our application will work. We will have two routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A route that accepts width and height parameters in the route itself, and responds with a PNG response sized accordingly&lt;/li&gt;
&lt;li&gt;A route that returns a simple HTML homepage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ve no doubt seen various novelty placeholder sites like &lt;a href="http://placekitten.com/"&gt;placekitten.com&lt;/a&gt; for use in web projects, and this will be similar to that. We’ll use a simple black image with the dimensions in white text, but you should be able to use this as the basis of a more sophisticated placeholder service, such as if you wanted to use branded images for a particular client.&lt;/p&gt;

&lt;p&gt;Since the home page will be fairly straightforward, let’s do that first. Delete the existing &lt;code&gt;resources/views/welcome.blade.php&lt;/code&gt; file and save this to &lt;code&gt;resources/views/home.blade.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Laravel Placeholder Images&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ mix('css/app.css') }}"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Laravel Placeholder Images&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This server can be used for serving placeholder
    images for any web page.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;To request a placeholder image of a given width and height
    simply include an image with the source pointing to
    &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;/image/&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;width&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;x&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;height&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;/&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;
    on this server such as:&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;
        &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;img src="{{ $example }}" &lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Examples&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"{{{ route('placeholder', ['width' =&amp;gt; 50, 'height' =&amp;gt; 50]) }}}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"{{{ route('placeholder', ['width' =&amp;gt; 100, 'height' =&amp;gt; 50]) }}}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"{{{ route('placeholder', ['width' =&amp;gt; 50, 'height' =&amp;gt; 100]) }}}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note we’re using the &lt;code&gt;route()&lt;/code&gt; helper to add some example images, even though it’s not in place yet. Add this route to your &lt;code&gt;routes/web.php&lt;/code&gt; as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'home'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'example'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'placeholder'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'width'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'height'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, note that we’re using the &lt;code&gt;route()&lt;/code&gt; helper to get the URL for the placeholder image. Next, we need to create the outline of the route for getting the placeholders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/placeholder/{width}x{height}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'width'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'height'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'placeholder'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Due to the limited scope of this application, we won’t bother with full controllers, but you can add them if you wish. Note we’ve specified the name &lt;code&gt;placeholder&lt;/code&gt; and set a regex to validate the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; parameters.&lt;/p&gt;

&lt;p&gt;Now let’s populate the callback to generate a PNG file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/placeholder/{width}x{height}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecreatetruecolor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;$textColour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecolorallocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;imagestring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="s2"&gt; X &lt;/span&gt;&lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$textColour&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;ob_start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;imagepng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ob_get_contents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;ob_end_clean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'Content-type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'image/png'&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'width'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'height'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'placeholder'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll also add some very basic CSS to the provided CSS file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;list-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t forget to build this with &lt;code&gt;npm install &amp;amp;&amp;amp; npm run production&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;If you now run &lt;code&gt;php artisan serve&lt;/code&gt; you should be able to see that it works - the homepage renders, and the embedded images are pulled in OK. However, there are three potential issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The images themselves are regenerated each time. Since they never change, it’s a no-brainer to cache them indefinitely for the best performance, and if we do need to change them in the future we can just flush the cache to resolve this&lt;/li&gt;
&lt;li&gt;Similarly, we should use ETags to allow the application to tell the browser when the image has changed&lt;/li&gt;
&lt;li&gt;There’s no limit on how large images can be, so a malicious user could request a huge image to break the system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s tackle these in order. First, let’s create some middleware to handle the caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Closure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CacheImages&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @return mixed
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%d.%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;rememberForever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We construct a cache key from the request width and height, and use the &lt;code&gt;Cache::rememberForever()&lt;/code&gt; method to cache the response. We then register this middleware as route middleware in &lt;code&gt;app\Http\Kernel.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$routeMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'cache.headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Illuminate\Http\Middleware\SetCacheHeaders&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'signed'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Illuminate\Routing\Middleware\ValidateSignature&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'throttle'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Illuminate\Routing\Middleware\ThrottleRequests&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'cache.images'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;App\Http\Middleware\CacheImages&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And apply it to the image route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/placeholder/{width}x{height}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecreatetruecolor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;$textColour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecolorallocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;imagestring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="s2"&gt; X &lt;/span&gt;&lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$textColour&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;ob_start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;imagepng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ob_get_contents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;ob_end_clean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'Content-type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'image/png'&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'width'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'height'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'placeholder'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cache.images'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s set ETags on our images. Laravel comes with the &lt;code&gt;cache.headers&lt;/code&gt; middleware, which we can easily wrap around our placeholder route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cache.headers:public;etag'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/placeholder/{width}x{height}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecreatetruecolor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$textColour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecolorallocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;imagestring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="s2"&gt; X &lt;/span&gt;&lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$textColour&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;ob_start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nb"&gt;imagepng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ob_get_contents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nb"&gt;ob_end_clean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'Content-type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'image/png'&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'width'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'height'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'placeholder'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cache.images'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let’s handle the dimensions issue. Again, this is something that is probably best handled in middleware since that way it can be rejected before the point it gets to the route handler. All we need to do is to check to see if the width and height parameters exceed the intended value, and throw an error in the middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Closure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Validation\ValidationException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidateImageDimensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @return mixed
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Height and width cannot exceed 2000 pixels'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register this middleware in &lt;code&gt;app/Http/Kernel.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$routeMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'cache.headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Illuminate\Http\Middleware\SetCacheHeaders&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'signed'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Illuminate\Routing\Middleware\ValidateSignature&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'throttle'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Illuminate\Routing\Middleware\ThrottleRequests&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'cache.images'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;App\Http\Middleware\CacheImages&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'validate.images'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;App\Http\Middleware\ValidateImageDimensions&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And apply it to the image route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cache.headers:public;etag'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/placeholder/{width}x{height}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecreatetruecolor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$textColour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecolorallocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;imagestring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="s2"&gt; X &lt;/span&gt;&lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$textColour&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;ob_start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nb"&gt;imagepng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ob_get_contents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nb"&gt;ob_end_clean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'Content-type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'image/png'&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'width'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'height'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'placeholder'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'validate.images'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cache.images'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we’re done! We now have a basic, but functional, stateless Laravel application that’s been stripped of a lot of the unnecessary functionality. There are a few further changes that could be made to expand this if necessary, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amend the project to allow requesting different image formats using an additional route parameter (hint - you’ll want to use something like &lt;a href="http://image.intervention.io/"&gt;Intervention for this&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Serve different images, either by using one as a starting template so they are all branded the same, or specifying one from several options in the URL, such as with &lt;a href="https://www.placecage.com/"&gt;PlaceCage&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, I will leave these as an exercise for the reader. The code for this project is available on &lt;a href="https://github.com/matthewbdaly/lightweight-laravel"&gt;Github&lt;/a&gt; if you get stuck at any point.&lt;/p&gt;

&lt;p&gt;Hopefully, this article has given you some food for thought about how you can use Laravel for applications you might have previously considered too small to use it for. Don’t worry too much about removing something that you need to add later - version control means you can always retrieve it if it turns out you do need it later. I’d also add that potentially the same approach can be applied to other full stack PHP frameworks, though you’ll have to do some exploring on your own to determine this.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
    </item>
    <item>
      <title>What I want in a PHP CMS</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Mon, 28 Sep 2020 14:50:48 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/what-i-want-in-a-php-cms-3m2f</link>
      <guid>https://dev.to/matthewbdaly/what-i-want-in-a-php-cms-3m2f</guid>
      <description>&lt;p&gt;I maintain a custom PHP legacy CMS for a client, and have also been building a micro-CMS as a learning project, so I’ve spent quite a lot of time in the last few years thinking about how content should be managed, and how applications to manage it should work.&lt;/p&gt;

&lt;p&gt;I’ve also at least tinkered with a few different content management systems down the years, and I’ve found it depressing how many times Wordpress has been the default choice, despite it being probably the worst CMS I’ve ever had the gross misfortune to use. The argument that “it’s easy to install and use” doesn’t really hold water given that in my experience most users setting up a new Wordpress site don’t go through the five-minute install, but use their shared hosting provider’s setup wizard, which typically also supports several other content management systems. Also, it just does not make sense to optimise a short five minute install that will never be repeated for that site over the rest of the workflow for maintaining the site, possibly for years - I’d rather have something that takes a bit more time to do the initial set up, but is easier to maintain.&lt;/p&gt;

&lt;p&gt;So, what do I want in a PHP CMS? Here’s my thoughts on my ideal CMS solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed entirely with Composer
&lt;/h2&gt;

&lt;p&gt;Creating a new site using a CMS should be as simple as running something like the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ composer create-project --prefer-dist my/cms newsite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And updating it should be as simple as running the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ composer update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Installing a plugin should be a case of running this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ composer require my/plugin-foo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should then be possible to activate the plugin simply by listing it in a config file.&lt;/p&gt;

&lt;p&gt;As far as possible, &lt;em&gt;all&lt;/em&gt; of the functionality of the CMS should be contained in a single “core” package, and plugins should be their own Composer packages that can be installed, and then switched on and off in a simple config file. The initial creation step should be a case of checking out a boilerplate that contains only the absolute minimum - a front controller, a starting configuration, front end tooling, and some views - and gets the rest of the functionality from the core package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Allow creating custom site boilerplates
&lt;/h2&gt;

&lt;p&gt;It should be possible to create and publish alternative boilerplates.&lt;/p&gt;

&lt;p&gt;For instance, if a CMS provides a default starting boilerplate that ships with Bootstrap, VueJS and Laravel Mix, I should be able to fork it, replace Bootstrap with Tailwind and Vue with React, and then use my version for future projects without having to spend a lot of time maintaining the fork.&lt;/p&gt;

&lt;p&gt;Similarly, if there are certain plugins I use all the time, it should be possible to include those plugins as dependencies in my &lt;code&gt;composer.json&lt;/code&gt; so that when I create a new project from my boilerplate, they’re present right from the start and I don’t have to faff around downloading and configuring them manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugin API should work like a framework
&lt;/h2&gt;

&lt;p&gt;The best practices we’ve all spent years learning shouldn’t go out the window when working with a CMS. A good CMS should feel familiar if you’ve got some experience working in MVC frameworks, and it should embrace PSR standards. Adding a route should largely be a matter of writing a controller, mapping it to a route, and adding a view file, just as it would be in a framework&lt;/p&gt;

&lt;p&gt;There’s always going to be some things that need to be CMS-specific, because registering things like routes is more complex in a general purpose CMS than a custom web app as they can be defined in multiple arbitrary places. These can be handled by triggering events at various points in the CMS application’s lifecycle, so that plugin authors can set up listeners to do things such as register routes, add new view helpers and so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Focused exclusively on content, not presentation
&lt;/h2&gt;

&lt;p&gt;I’m increasingly convinced that the ability to amend presentation in a CMS is a misfeature. The purpose of a CMS is to manage content, not presentation, and making it able to amend presentation potentially gives unskilled site owners enough rope to hang themselves with, while making it actively harder for us devs.&lt;/p&gt;

&lt;p&gt;I’ve certainly seen enough sites that a client has completely messed up after being given access to change the presentation in Wordpress, and because it’s stored in the database it’s not possible to roll back the changes easily the way it would be if the styling was stored in version control. And it’s definitely quicker for an experienced front end developer to edit a CSS file than to use Wordpress’s own tools for amending styling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a proper templating system
&lt;/h2&gt;

&lt;p&gt;As a templating language, PHP &lt;em&gt;sucks&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s too easy to overlook escaping variables properly&lt;/li&gt;
&lt;li&gt;Handling partials is difficult&lt;/li&gt;
&lt;li&gt;There’s always the temptation to put in more logic than is advisable in the view layer, especially when deadlines are tight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using a dedicated templating language, rather than a full programming language, in the view layer, means that entire classes of issues can be completely eradicated from the layer of the application that the developers who work with the CMS have the most dealings with. Developers are forced to move more complex logic into dedicated helpers, and can’t just leave it in the template “until we have time to clear it up”, which is often never.&lt;/p&gt;

&lt;p&gt;Twig is solid, reliable, fast, easy to extend, and similar enough to other templating languages such as Handlebars and Django’s templates that if you’ve used any of those you can adapt easily, and it should probably be your first choice. Blade is also a solid choice, and if you want something whose syntax is not dissimilar to PHP you should probably consider Plates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration with version control in mind
&lt;/h2&gt;

&lt;p&gt;Wordpress does this particularly badly because it actively encourages storing sensitive data, such as database credentials, in a PHP file (which is then kept in the web root…). A good, solid way to store configuration details in PHP is to store generic details (for instance, a list of the active plugins, which will be the same for production and the local copy developers run) for that project in either a YAML or PHP file, and store install-specific details in either a &lt;code&gt;.env&lt;/code&gt; file, or as environment variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom content types
&lt;/h2&gt;

&lt;p&gt;It should be easy to create a new content type, and define specific fields for that content type. For instance, if I’m building a recipe site, I should be able to define a Recipe type that has the following attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ingredients&lt;/li&gt;
&lt;li&gt;Cover image&lt;/li&gt;
&lt;li&gt;Title&lt;/li&gt;
&lt;li&gt;Method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then all Recipe instances should have those attributes, and it shouldn’t be necessary to bastardise a different content type to make it work properly. It should also be possible to lock down the ability to create custom content types so it’s either limited to admins, or they’re defined in code, so end users can’t create arbitrary content types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom taxonomies
&lt;/h2&gt;

&lt;p&gt;It should be possible to define your own custom taxonomies for content. Continuing the Recipe example above, we should be able to define three sorts of taxonomy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dietary requirements (eg vegetarian, vegan, gluten-free etc)&lt;/li&gt;
&lt;li&gt;Meal (eg breakfast, lunch, dinner, snacks)&lt;/li&gt;
&lt;li&gt;Region (eg Indian, Chinese, Italian)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A taxonomy should be appropriately named, and again it shouldn’t be necessary to abuse generic categories and tags to categorise content. As with the content types, it should also be possible to lock them down.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better solution than rich text for managing content
&lt;/h2&gt;

&lt;p&gt;Rich text is not a great solution for more complex page layouts, and tends to be abused horribly to do all sorts of things. There’s a tendency to dump things like snippets for Google Maps, tables, galleries, Javascript widgets and many more into rich text. This means that it also loses the semantic value of the content - rather than being a paragraph, then a map of the local area, then a photo carousel, then another paragraph, it’s just a single blob of text. This can’t be easily migrated to another solution if, say, you decide to swap Google Maps for Open Streetmap, and change one carousel for another, without going through and manually replacing every map and carousel, which is a chore.&lt;/p&gt;

&lt;p&gt;Wagtail isn’t a PHP CMS, but &lt;a href="https://torchbox.com/blog/rich-text-fields-and-faster-horses/"&gt;it has an interesting approach to rich text handling&lt;/a&gt; for complex content, inspired by &lt;a href="https://madebymany.github.io/sir-trevor-js/"&gt;Sir Trevor&lt;/a&gt;, based around blocks of different types. The Gutenberg editor in Wordpress 5.0 and up isn’t a million miles away from this, either. For simpler sites, it’s probably better to limit users to a Markdown editor and add helpers for adding more complex functionality directly in the template, such as a gallery helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decent command-line runner
&lt;/h2&gt;

&lt;p&gt;There are always going to be certain tasks that are best done from the command line. A decent CMS should have a command line tool that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows appropriate admin tasks, such as going into maintenance mode and flushing caches, to be done from the command line&lt;/li&gt;
&lt;li&gt;Can be easily extended by plugin authors to add their own commands&lt;/li&gt;
&lt;li&gt;Assists developers when working locally, such as by generating boilerplate when necessary (so, for instance, you can run a command to generate the skeleton for a new plugin)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s no excuse not to do this when building a CMS. Symfony’s console component is solid, easy to work with, and a good base for whatever commands you need to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless as an option
&lt;/h2&gt;

&lt;p&gt;The rise of headless CMS’s, both as a service and as software packages, hasn’t surprised me. Nowadays it’s quite common to have to publish the same content to multiple channels, which might be one or more websites, as well as mobile apps, and it makes sense to be able to centralise that content in one place rather than have to copy it in some fashion.&lt;/p&gt;

&lt;p&gt;It’s therefore very useful to have an API that can retrieve that content for republishing. The same API can also be used with Javascript libraries like React and Vue to build sophisticated frontends that consume that data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which solutions do this best?
&lt;/h2&gt;

&lt;p&gt;You’ll probably have got the idea at this point that Wordpress isn’t my first choice. It was created in a different era, and hasn’t kept up well compared to many of its contemporaries, and there are many technical issues with it that are at this point effectively impossible to ever fix. For instance, you could potentially store the post meta in the same table as the rest of the post data by using a JSON field in current versions of MySQL, which would make it more performant, but it seems unlikely it could ever be migrated across to use that solution.&lt;/p&gt;

&lt;p&gt;Frustratingly, its mindshare means it’s erroneously seen as some kind of “gold standard” by inexperienced developers and non-technical clients, and there seems to be a common misconception that it’s the only solution that lets users update the content themselves (when in fact that’s the whole point of ANY CMS). Using Bedrock and a theme system like Sage that supports a proper templating system helps solve some of the problems with Wordpress, but not all.&lt;/p&gt;

&lt;p&gt;I have tried a few solutions that come very close to what I want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://bolt.cm/"&gt;Bolt&lt;/a&gt; seems from what I’ve seen so far to be effectively a “better Wordpress” in that the interface and functionality is broadly familiar to anyone already used to Wordpress, but it uses Twig, is built in Symfony, and has a proper command-line runner. I haven’t tried it since version 4 was released a few days back, so I will probably give it a spin before long.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://getgrav.org/"&gt;Grav&lt;/a&gt; looks like a great solution for brochure sites. I’ve long thought that these sites, which often run on shared hosting, don’t really need a database-backed solution, and a flat-file solution is probably a better bet in most cases. Grav is simple to set up and configure, has a decent admin interface, and uses Twig for the views, making it easy to theme.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://statamic.com/"&gt;Statamic&lt;/a&gt; is my current favourite and ticks almost all of the boxes mentioned above. It’s built on Laravel, and can be added to an existing Laravel site if required. It also allows you access to the full power of the underlying framework if you need it, and ships with a decent front-end boilerplate that includes Tailwind. The only downside compared to Wordpress is that it’s a paid-for solution, but the price is entirely reasonable, and if it’s for a client build you’ll not only save on all the premium plugins you don’t need, but you’ll probably save time on the site build.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Payment shouldn’t be an issue if you’re doing client work, unless the cost is huge. You’re getting paid for building something, and if buying an off-the-shelf product saves you time, it’s well worth it. Back when Laravel Nova was first released, a lot of people were complaining that it wasn’t free, but that was neither here nor there - the cost is only equivalent to a few hours of an experienced developer’s time, and it would take a lot longer to build out the same functionality, and the same is true of any half-decent CMS. In the early days of the web, one company I used to work for sold &lt;a href="http://www.wordserver.co.uk/"&gt;a CMS that was considered cheap by the standards of the time&lt;/a&gt; at £495, plus £96 a year, for the entry level version - Statamic is significantly cheaper than that.&lt;/p&gt;

&lt;p&gt;It’s always a good idea to be aware of the various CMS options around. Wordpress isn’t a great solution and there are plenty of options that are technically better, easier to use, more secure, and work out cheaper when you consider the total cost of ownership. I’ll probably be favouring Statamic for the foreseeable future when building content-based websites, but that doesn’t mean I won’t look elsewhere from time to time.&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>Flow typed AJAX responses with React Hooks</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Sat, 13 Jun 2020 12:50:41 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/flow-typed-ajax-responses-with-react-hooks-22lh</link>
      <guid>https://dev.to/matthewbdaly/flow-typed-ajax-responses-with-react-hooks-22lh</guid>
      <description>&lt;p&gt;I’m a big fan of type systems in general. Using Psalm to find missing type declarations and incorrect calls in PHP has helped me out tremendously. However, I’m not a big fan of Typescript. The idea of creating a whole new language, primarily just to add types to Javascript, strikes me as a fundamentally bad idea given how many languages that compile to Javascript have fallen by the wayside. Flow seems like a much better approach since it adds types to the language rather than creating a new language, and I’ve been using it on my React components for a good while now. However, there are a few edge cases that can be difficult to figure out, and one of those is any generic AJAX component that may be reused for different requests.&lt;/p&gt;

&lt;p&gt;A while back I wrote the following custom hook, loosely inspired by axios-hooks (but using the Fetch API) to make a query to a GraphQL endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When called, the hook receives two parameters, the URL to hit, and the query to make, and returns an array that contains a function for reloading, and an object containing the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;loading&lt;/code&gt; - a boolean that specifies if the hook is loading right now&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;error&lt;/code&gt; - a boolean that specifies if an error has occurred&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt; - the response data from the endpoint, or null&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using this hook, it was then possible to make an AJAX request when a component was loaded to populate the data, as in this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Hooks/useFetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;marked&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/graphql`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`query {
    posts {
      title
      slug
      content
      tags {
        name
      }
    }
  }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;marked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hook is simple, and easy to reuse. However, it’s difficult to type the value of &lt;code&gt;data&lt;/code&gt; correctly, since it will be different for different endpoints, and given that it may be reused for almost any endpoint, you can’t cover &lt;em&gt;all&lt;/em&gt; the acceptable response types. We need to be able to specify the response that is acceptable in that particular context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generics to the rescue
&lt;/h2&gt;

&lt;p&gt;Flow provides a solution for this in the shape of &lt;a href="https://flow.org/en/docs/types/generics/"&gt;generic types&lt;/a&gt;. By passing in a polymorphic type using &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; in the function declaration, we can then refer to that type when specifying what &lt;code&gt;data&lt;/code&gt; should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//@flow&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[?&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((?&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, when calling the hook, we can define a type that represents the expected shape of the data (here called &lt;code&gt;&amp;lt;Data&amp;gt;&lt;/code&gt;, and specify that type when calling the hook, as in this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//@flow&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Hooks/useFetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;marked&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/graphql`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`query {
    posts {
      title
      slug
      content
      tags {
        name
      }
    }
  }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useFetch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;marked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way, we can specify a completely different shape for our response data every time we call a different endpoint, without creating a different hook for every different endpoint, and still enjoy properly typed responses from our hook.&lt;/p&gt;

&lt;p&gt;Generics can be useful for many other purposes, such as specifying the contents of collections. For instance, if you had a &lt;code&gt;Collection&lt;/code&gt; object, you could use a generic type to specify that any one instance must consist of instances of a given type. Flow would then flag it as an error if you assigned an item of the wrong type to that collection, thus making some unit tests redundant.&lt;/p&gt;

</description>
      <category>react</category>
      <category>flow</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Does technical debt make burnout more likely?</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Thu, 14 May 2020 09:24:13 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/does-technical-debt-make-burnout-more-likely-425k</link>
      <guid>https://dev.to/matthewbdaly/does-technical-debt-make-burnout-more-likely-425k</guid>
      <description>&lt;p&gt;I've been working on a large legacy project for just over two years, and it's been my primary focus at work. It's built in a now-deprecated PHP framework (Zend 1), and has an awful lot of technical debt.&lt;/p&gt;

&lt;p&gt;I've experienced developer burnout in the past, and mostly know how to avoid it, but I've noticed it's considerably harder to avoid it with this project. The mental burden of trying to figure out what's going on at any point is &lt;em&gt;much&lt;/em&gt; higher than with a project with less technical debt - the inconsistency between how different objects work and confusing names of variables and methods means I spend far more time figuring out what's going on, and it stresses me out. Also the so-called "flow" state of maximum productivity simply isn't possibe to achieve, so I'm far less productive than I would be on a better-written project, and the drop in productivity makes it more likely deadlines are missed, causing even more stress. Tools like Psalm are helpful in finding what items are and adding type hints, but they're not a panacea, and at the current rate it's going to be a &lt;em&gt;long&lt;/em&gt; time before the project is in a state where I'm happy with it.&lt;/p&gt;

&lt;p&gt;Have you noticed a relationship between working on projects with high levels of technical debt and burnout? Has a particularly bad project caused you significant stress, or have you seen a code base you'd be working on and not taken/left a job for that reason? And how can we get across to non-technical colleagues the effect a particularly bad code base can have on developer productivity and well-being?&lt;/p&gt;

</description>
      <category>career</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Caching the Laravel user provider with a decorator</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Wed, 11 Mar 2020 21:20:14 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/caching-the-laravel-user-provider-with-a-decorator-30hh</link>
      <guid>https://dev.to/matthewbdaly/caching-the-laravel-user-provider-with-a-decorator-30hh</guid>
      <description>&lt;p&gt;A couple of years ago I posted &lt;a href="https://matthewdaly.co.uk/blog/2018/01/12/creating-a-caching-user-provider-for-laravel/"&gt;this article&lt;/a&gt; about constructing a caching user provider for Laravel. It worked, but with the benefit of hindsight I can now see that there were a number of issues with this solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because it extended the existing Eloquent user provider, it was dependent on the internals of that remaining largely the same - any change in how that worked could potentially break it&lt;/li&gt;
&lt;li&gt;For the same reason, if you wanted to switch to a different user provider, you’d need to add the same functionality to that provider, either by writing a new provider from scratch or extending an existing one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve used the decorator pattern a few times in the past, and it’s a good fit for situations like this where you want to add functionality to something that implements an interface. It allows you to separate out one part of the functionality (in this case, caching) into its own layer, so it’s not dependent on any one implementation and can wrap any other implementation of that same interface you wish. Also, as long as the interface remains the same, there likely won’t be any need to change it when the implementation that is wrapped changes. Here I’ll demonstrate how to create a decorator to wrap the existing user providers.&lt;/p&gt;

&lt;p&gt;If we only want to cache the &lt;code&gt;retrieveById()&lt;/code&gt; method, like the previous implementation, the decorator class might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Auth\Authenticatable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Auth\UserProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Cache\Repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProviderDecorator&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;UserProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * @var UserProvider
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @var Repository
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UserProvider&lt;/span&gt; &lt;span class="nv"&gt;$provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Repository&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * {@inheritDoc}
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;retrieveById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id-'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;retrieveById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * {@inheritDoc}
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;retrieveByToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;retrieveById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * {@inheritDoc}
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;updateRememberToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Authenticatable&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateRememberToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * {@inheritDoc}
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;retrieveByCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;retrieveByCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * {@inheritDoc}
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Authenticatable&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It implements the same interface as the user providers, but accepts two arguments in the constructor, which are injected and stored as properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Another instance of &lt;code&gt;Illuminate\Contracts\Auth\UserProvider&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;An instance of the cache repository &lt;code&gt;Illuminate\Contracts\Cache\Repository&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the methods just defer to their counterparts on the wrapped instance - in this example I have cached the response to &lt;code&gt;retrieveById()&lt;/code&gt; only, but you can add caching to the other methods easily enough if need be. You do of course still need to flush the cache at appropriate times, which is out of scope for this example, but can be handled by model events as appropriate, as described in the prior article.&lt;/p&gt;

&lt;p&gt;Then you add the new decorator as a custom user provider, but crucially, you need to first resolve the provider you’re going to use, then wrap it in the decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Providers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Gate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Support\Providers\AuthServiceProvider&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Auth\UserProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Auth\EloquentUserProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Contracts\Cache\Repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Auth\UserProviderDecorator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthServiceProvider&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ServiceProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * The policy mappings for the application.
     *
     * @var array
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$policies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'App\Model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'App\Policies\ModelPolicy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Register any authentication / authorization services.
     *
     * @return void
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;registerPolicies&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cached'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EloquentUserProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'hash'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'model'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="nv"&gt;$cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Repository&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserProviderDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, set up the config to use the caching provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="s1"&gt;'providers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'users'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'cached'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;App\Eloquent\Models\User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty rough and ready, and could possibly be improved upon by allowing you to specify a particular provider to wrap in the config, as well as caching more of the methods, but it demonstrates the principle effectively.&lt;/p&gt;

&lt;p&gt;By wrapping the existing providers, you can change the behaviour of the user provider without touching the existing implementation, which is in line with the idea of composition over inheritance. Arguably it’s more complex, but it’s also more flexible - if need be you can swap out the wrapped user provider easily, and still retain the same caching functionality.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
    </item>
    <item>
      <title>The trouble with integrated static analysis</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Wed, 12 Feb 2020 22:40:15 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/the-trouble-with-integrated-static-analysis-3n53</link>
      <guid>https://dev.to/matthewbdaly/the-trouble-with-integrated-static-analysis-3n53</guid>
      <description>&lt;p&gt;I’ve always been a big fan in general of tools that provide feedback about the quality of my code. The development role in which I spent the most time was one in which I had no peer feedback or mentoring at all, and while I could definitely have done with more peer review than I had, automated tools helped fill the gap a little bit. When I started building my first Phonegap app, about a year after I started programming professionally, it used &lt;em&gt;far&lt;/em&gt; more Javascript than I’d ever used before, and JSLint was very helpful in instilling good practices at that early stage in my career.&lt;/p&gt;

&lt;p&gt;In addition, I often find that using an automated tool largely eliminates the issue of ego - if your colleague Bob tells you something is a bad practice, you can potentially dismiss it as “That’s just Bob’s preferences”, whereas an automated tool is potentially much more objective. Nowadays, my typical set of static analysis tools on a project includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;Flow&lt;/li&gt;
&lt;li&gt;PHP CodeSniffer&lt;/li&gt;
&lt;li&gt;Psalm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, I’m always dubious of using any static analysis tool that’s tightly integrated with a particular editor or IDE. In this post, I will explain my reasoning.&lt;/p&gt;

&lt;h1&gt;
  
  
  In-editor feedback
&lt;/h1&gt;

&lt;p&gt;Having instant feedback on the quality of your code is tremendously useful. Sure, you can run something like CodeSniffer from the command line and see what the problems are, but that’s nowhere near as useful as having it actually &lt;em&gt;in&lt;/em&gt; your code. If you work on a legacy code base, there’s no way in hell you can wade through a long list of output in the terminal and fix them without losing the will to live. By comparison, actually seeing something flagged as an error where it actually occurs makes the mental cost of fixing it much smaller - you can see it in context, and can usually therefore resolve it more easily.&lt;/p&gt;

&lt;p&gt;However, that doesn’t explicitly require that any one tool form an integral part of the editor. Most editors can hand off linting and static analysis to other, standalone tools, and doing so offers the following advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less dependence on a given development environment - it’s always a struggle if you wind up stuck using a development environment you dislike (I grew to utterly despise Netbeans in my first role), but if you can use generic feedback tools that can be integrated with just about any editor, your team can use the development environment that suits them most, while still all benefiting from the feedback these tools provide&lt;/li&gt;
&lt;li&gt;These tools tend to be open source, meaning you have the security of knowing that if the creator ceases maintaining it, either someone else may pick up the baton, or you can choose to fork it yourself. If a commercial IDE provider ceases trading, it’s likely you won’t be able to use their offering at all at some point in the future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nowadays I use vim-ale in Neovim, and that provides real-time feedback for a number of linters and static analysis tools, including all those I mentioned above. I have comprehensive information on any issues in my code, and because any configuration is in simple text files that form part of the repository, it’s easy to update those settings for all developers working on the project to ensure consistency.&lt;/p&gt;

&lt;p&gt;It’s possible that an integrated solution &lt;em&gt;might&lt;/em&gt; offer a few advantages in terms of tighter integration with autocompletion and other functionality allowing for it, but whether they outweigh the tradeoffs mentioned here is dependent entirely on the implementation and how useful it is for any one team.&lt;/p&gt;

&lt;h1&gt;
  
  
  Continous integration to the rescue
&lt;/h1&gt;

&lt;p&gt;There’s another issue I have with this sort of tightly integrated static analysis, which is probably the biggest, and that is that the feedback is available only at the level of an individual developer, not the team.&lt;/p&gt;

&lt;p&gt;It’s great providing all this feedback to developers, but what if they just ignore it? Not all developers have had the sort of experience that leads one to really appreciate the value of coding standards and type hints, particularly if they’ve worked primarily on small or greenfield projects, or in environments where the emphasis was on churning out large quantities of work, and getting developers to tidy up the sort of issues these tools identify can sometimes be a tough sell when faced with code which, at least superficially, works.&lt;/p&gt;

&lt;p&gt;Suppose you take on a new developer and ask them to work alone on a particular project for several months. Due to your own workload you can’t easily schedule code reviews with them, so you don’t see what they’re writing until they’re done. Then you take a look at what they’ve written and it’s full of issues that the IDE caught, but the developer either didn’t bother to fix, or didn’t know how to. What they’ve done may well work, but they’ve introduced a huge morass of technical debt that will slow down future development for the foreseeable future.&lt;/p&gt;

&lt;p&gt;If your static analysis tools work only in the context of a given editor or IDE, then if the new dev introduce issues in the code base and doesn’t resolve them because they don’t know how, or don’t see the value, then the first you knows about it is when you clone the repo yourself and open it up. With a solution that runs in a CI environment, you can catch any reduction in code quality when it’s pushed. Sure, code reviews can do that too, but that requires manual input in a way that not every team is willing to spare, whereas a CI server, once set up, is largely self sustaining. And you could run one tool locally and another in a CI environment, but you can’t be sure they’ll necessarily catch all the same issues.&lt;/p&gt;

&lt;p&gt;Now consider the same scenario if you’re using a separate code quality tool that’s integrated both into the editor, and your continuous integration workflow. Obviously, it will depend on your personal CI setup, but once code quality either begins to drop, or drops below a given level, the CI server will mark the build as failed, and you’ll be alerted. You can therefore then raise the issue with the new dev before it gets out of hand, and provide whatever support they need to resolve the problem there and then.&lt;/p&gt;

&lt;p&gt;I personally maintain a legacy project in which, at one point prior to my arrival, a junior dev introduced an enormous amount of technical debt after working on it alone for six months. An integrated linter or static analysis tool probably wouldn’t have stopped that from happening, for the reasons stated above, but if a similar tool were part of the CI workflow, it could have been flagged as an issue much earlier and dealt with. Yes, leaving a junior dev unsupported and unsupervised for that length of time isn’t a great idea, but it happens, particularly in busy environments such as agencies. A good CI setup lets you see if someone is adding these kinds of issues, and act to nip it in the bud before it becomes too much of a problem, which is ultimately good for that developer’s career.&lt;/p&gt;

&lt;p&gt;Peer pressure can also be a strong motivating factor under these circumstances. By simply displaying a metric, you encourage people’s natural competitiveness, so displaying code quality stats in your CI dashboard will encourage your team to do better in this regard, and no-one wants to be visibly seen to be letting the team down by producing substandard code.&lt;/p&gt;

&lt;p&gt;For these reasons, where possible for feedback on code quality, I would always prefer to rely on a standalone tool that can be integrated with an editor, or used as part of a continuous integration workflow, as opposed to any IDE-specific functionality.&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>Don't use stdClass</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Sun, 09 Feb 2020 10:10:48 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/don-t-use-stdclass-4d0g</link>
      <guid>https://dev.to/matthewbdaly/don-t-use-stdclass-4d0g</guid>
      <description>&lt;p&gt;The digital agency I work for specialises in marketing, so some of my work tends to relate to mailing lists. This week I was asked to look at an issue on a Laravel-based export system built by a co-worker who left a few months ago. This particular application pulled data from the Campaign Monitor API about campaigns, and it returned the data as instances of &lt;code&gt;stdClass&lt;/code&gt;, something that I’m not terribly happy about.&lt;/p&gt;

&lt;p&gt;Now, this was an implementation detail of the Campaign Monitor PHP SDK, which is old and creaky (to say the least…) and doesn’t exactly abide by modern best practices. However, the legacy application I maintain also does this (there’s a &lt;em&gt;lot&lt;/em&gt; of stuff that needs sorting out on it, and sadly replacing the &lt;code&gt;stdClass&lt;/code&gt; instances is a &lt;em&gt;long&lt;/em&gt; way down the list), so I thought it was an interesting subject for a blog post. I consider using &lt;code&gt;stdClass&lt;/code&gt;, even just as a dumb container for data, to be an antipattern, and I thought it would be useful to explain my thoughts on this.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why shouldn’t I use stdClass?
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Readability
&lt;/h2&gt;

&lt;p&gt;One of the first things I learned about throwing exceptions is that they should be meaningful. It’s trivial to define a named exception and use that to specify the type of exception, and you can then capture exceptions to handle them differently elsewhere in the application. For instance, a validation error is entirely due to a user submitting the wrong details, and should therefore be handled in an entirely different manner to the database going down.&lt;/p&gt;

&lt;p&gt;The same is applicable to an object. If an API client returns an instance of &lt;code&gt;stdClass&lt;/code&gt;, that doesn’t tell me anything about what that object is. If I need to pass it elsewhere in a large application, it may become very difficult to understand what it represents without tracking it back to where it came from, which will slow me down. If instead I use a named class, the name can convey what it represents. It may seem like overkill to create a class that adds no new functionality, but the mere fact that it has a name makes your code more meaningful and easier to understand. I can also add DocBlock comments to describe it further.&lt;/p&gt;

&lt;p&gt;Of course, just giving something a generic name like &lt;code&gt;Container&lt;/code&gt; isn’t much of an improvement, and coming up with meaningful names for classes and methods is notoriously hard. As always, give some serious thoughts into what your objects represent and attempt to give them names that will make it easy to understand what they are if you look at the code base again six months down the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type hinting
&lt;/h2&gt;

&lt;p&gt;A related argument is that it makes type hinting more useful. You &lt;em&gt;can&lt;/em&gt; type hint &lt;code&gt;stdClass&lt;/code&gt;, but as noted above it tells someone working on the code receiving it very little about where it’s come from, or what it represents, and it doesn’t offer much value since an &lt;code&gt;stdClass&lt;/code&gt; could mean anything, and could be created anywhere in the appication.&lt;/p&gt;

&lt;p&gt;By contrast, named classes provides much more information about what the type hinted parameter represents. For instance, naming your class something such as &lt;code&gt;App\Api\Response\Item&lt;/code&gt;, makes it much clearer that that object represents an individual item returned from an API, and others developers working on the same code base (including your future self, who may not necessarily remember all of the details of how you’re implementing it now), will have less trouble understanding what is going on. There’s also a much-reduced likelihood of the same class being used to represent completely different types of data.&lt;/p&gt;

&lt;h2&gt;
  
  
  New functionality
&lt;/h2&gt;

&lt;p&gt;Finally, are you sure you don’t want to add any functionality? PHP includes a number of interfaces that can be &lt;em&gt;extremely&lt;/em&gt; useful for working with this sort of data.&lt;/p&gt;

&lt;p&gt;First off, the &lt;code&gt;ArrayAccess&lt;/code&gt; interface allows you to access an object’s values using array syntax, which can be useful. Also, implementing either &lt;code&gt;Iterator&lt;/code&gt; or &lt;code&gt;IteratorAggregate&lt;/code&gt; will allow you to iterate over the object using a &lt;code&gt;foreach&lt;/code&gt; loop. The &lt;code&gt;Countable&lt;/code&gt; interface is less useful, since all it does is let you get the number of items, but it’s sometimes handy. Finally, the &lt;code&gt;Serializable&lt;/code&gt; interface lets you serialize an object so it can be stored in a suitable medium, which can sometimes be useful.&lt;/p&gt;

&lt;p&gt;The same also applies to some of the magic methods. The &lt;code&gt;__toString()&lt;/code&gt; method, in particular, can be useful for returning a suitable string-based representation of an object - for instance, if it represents an item in a database, it might be appropriate to use this to return the ID of the item, or a text representation of it (eg title for a blog post, or product name for a product in an e-commerce site). The &lt;code&gt;__get()&lt;/code&gt; and &lt;code&gt;__set()&lt;/code&gt; magic methods may be a bit more dubious, but they can be useful if your object is intended to just be a dumb container as they allow you to make the properties on the object private, but keep them accessible without writing explicit getters and setters. I’d also suggest adding &lt;code&gt;__debugInfo()&lt;/code&gt; to objects unless you have a good reason not to, as when you’re debugging it can be hard to see the wood for the trees, and returning only the most pertinent data can make your job a &lt;em&gt;lot&lt;/em&gt; easier.&lt;/p&gt;

&lt;p&gt;Of course, you don’t have to implement all this functionality from scratch for every class. It often makes sense to create an abstract class that implements this sort of basic container functionality, and then base all your container classes on that, overriding it when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Hopefully, this has made it clear how compelling it is to use named classes instead of &lt;code&gt;stdClass&lt;/code&gt;, and how much benefit you can get from not just using named classes, but creating your own base container class for them. I’m of the opinion that PHP should probably make &lt;code&gt;stdClass&lt;/code&gt; abstract to prevent them from being used like this, and indeed I’m seriously considering the idea of creating a Codesniffer sniff to detect instances of &lt;code&gt;stdClass&lt;/code&gt; being instantiated and raise them as an error.&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>F*** PHPStorm Man and the high horse he rode in on</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Sat, 25 Jan 2020 22:25:17 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/f-phpstorm-man-and-the-high-horse-he-rode-in-on-58h</link>
      <guid>https://dev.to/matthewbdaly/f-phpstorm-man-and-the-high-horse-he-rode-in-on-58h</guid>
      <description>&lt;p&gt;There’s a particularly unpleasant type of programmer that exists, and you’ve probably met him, either online or in person. I call him &lt;strong&gt;PHPStorm Man&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;NB: Despite the name I’ve chosen, I’m not singling out users of PHPStorm in particular about this. The first time I encountered PHPStorm Man in person, he was using Sublime Text, and you will find PHPStorm Men using all different editors and IDEs. PHPStorm Man is an archetype defined not by any particular piece of software, but by a common bad attitude, and given that I work primarily with PHP these days, I’ve most often encountered this kind of behaviour from PHPStorm users (or at least, people claiming to be PHPStorm users, since at least online you can’t discount the possibility that they’re trolls). Users of other languages may well see this behaviour most prominently from those who use some other editor or IDE, but the same underlying phenomenon is at work, whether we call him PHPStorm Man, Eclipse Man, Vim Man or PyCharm Man.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is PHPStorm Man?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wg41wQU4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://matthewdaly.co.uk/static/images/phpstorm-man.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wg41wQU4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://matthewdaly.co.uk/static/images/phpstorm-man.jpg" alt="The hero we really, really don't need" width="500" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PHPStorm Man (and he &lt;em&gt;will&lt;/em&gt; almost certainly be a man - while it could be just because our industry is male-dominated, I’ve &lt;em&gt;never&lt;/em&gt; known a woman to behave like this, and I strongly suspect it’s nothing more than an industry-specific example of the common phenomenon of &lt;a href="https://en.wikipedia.org/wiki/Mansplaining"&gt;mansplaining&lt;/a&gt;) doesn’t know squat about your editor or IDE. He just knows his is superior, and he wants you to know it, regardless of the evidence.&lt;/p&gt;

&lt;p&gt;His knowledge of your editor is probably either non-existent, grossly outdated, plain ill-informed or second-hand (probably from another PHPStorm Man). If he’s advocating an IDE, he’ll likely equate all text editors with Notepad - he may claim the advantages of his one over yours include such fundamentals as syntax highlighting and autocompletion.&lt;/p&gt;

&lt;p&gt;He’ll boast about some feature his editor has that yours doesn’t, even if it does. If your editor lacks that functionality out of the box, but can add it via a plugin, apparently that doesn’t count because although you’re intelligent enough to build a working web app, somehow installing and configuring that plugin is an insurmountable burden (yet mysteriously turning off all the stuff you don’t need in &lt;em&gt;his&lt;/em&gt; editor is quick and easy). If it does do that out of the box, he’ll probably find some bullshit reason why his editor’s implementation is better, even if it’s something as pointless and irrelevant as “it’s been around longer”. He’ll likely claim, with absolutely no evidence whatsoever, or indeed in the presence of evidence to the contrary, that you’d be more productive if you only used his editor.&lt;/p&gt;

&lt;p&gt;In short, if you aren’t using his editor or IDE of choice, you’re a troglodyte living in a dung hut.&lt;/p&gt;

&lt;h2&gt;
  
  
  PHPStorm man in the wild
&lt;/h2&gt;

&lt;p&gt;I had an encounter with PHPStorm Man in person a while back. Just over two years ago I started a new job, which as it turned out didn’t last long after I caught the flu that was going around in late 2017 my first week. On the second day, shortly after going over something with me, the senior dev sent me the following message on Slack:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I noticed you’re using Vim. Have you tried using Sublime Text?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I responded that I had, and chose not to use it. There followed a long string of messages along the following lines:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Him: Sublime Text has X!&lt;/p&gt;

&lt;p&gt;Me: I have that&lt;/p&gt;

&lt;p&gt;Him: Well Sublime Text also has Y!&lt;/p&gt;

&lt;p&gt;Me: I have that too, via a plugin&lt;/p&gt;

&lt;p&gt;Him: Well, Sublime Text doesn’t need a plugin for that&lt;/p&gt;

&lt;p&gt;Me: Irrelevant since the plugin is already installed and configured, and I know how to use it&lt;/p&gt;

&lt;p&gt;Him: Well, what about this?&lt;/p&gt;

&lt;p&gt;Me: That sounds cool, so I just found a plugin to do that and installed it&lt;/p&gt;

&lt;p&gt;Him: And this?&lt;/p&gt;

&lt;p&gt;Me: I have absolutely no need for that&lt;/p&gt;

&lt;p&gt;Him: Well, Vim is old, Sublime Text is new!&lt;/p&gt;

&lt;p&gt;Me: Actually, this is Neovim, which is technically newer than Sublime Text&lt;/p&gt;

&lt;p&gt;Him: Well, Sublime Text is a GUI application&lt;/p&gt;

&lt;p&gt;Me: Exactly. That makes it slower and forces me to use the mouse, aggravating my RSI. I use the terminal because it’s more efficient&lt;/p&gt;

&lt;p&gt;Him: Well, I don’t mind what you use…(despite the evidence of that entire conversation)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the benefit of hindsight, what I &lt;em&gt;should&lt;/em&gt; have responded with was this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’m an experienced, professional web developer of over six years, and I chose my editor based on years of personal experience, and have chosen my plugins and configuration based on what’s useful to me, and continue to do so to this day. I don’t appreciate you talking down to me like a child.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why I personally don’t use an IDE
&lt;/h2&gt;

&lt;p&gt;In my case, I have a particularly good reason not to use &lt;em&gt;any&lt;/em&gt; GUI application to develop in. Before I was a developer, I worked for an insurance company in a customer service role, and I didn’t have access to the sort of decent quality keyboards and mice developers habitually use, as well as having output goals linked to discipline and bonus/salary raises and having to use custom internal applications on Windows XP, with dreadful keyboard support. As a result I developed a degree of RSI in both hands, which I’ve found is aggravated by using any application that requires me to use a mouse extensively - I’m generally OK if I only have to type, but reaching for the mouse all the time quickly becomes tiring, and soon after painful.&lt;/p&gt;

&lt;p&gt;For that reason I’ve developed a workflow that’s strongly dependent on the command line - I use Neovim in the terminal, alongside Byobu so that I can run multiple tabs and switch between them quickly without touching the mouse. Moving to a more GUI-oriented workflow would require me to use the mouse more than I do now, which would probably become physically painful quite quickly. Using an editor or IDE which I found made me more prone to further flare-ups of RSI could have serious consequences for my long-term health, and could potentially be career-ending. If I worked somewhere that mandated a particular IDE that didn’t work well for me, I’d &lt;em&gt;have&lt;/em&gt; to either negotiate an exception on health and safety grounds or quit.&lt;/p&gt;

&lt;p&gt;I’m also of the personal opinion that much of the functionality of an IDE should not be, in principle, tied to that IDE, but should instead be the province of more general purpose tools that can used, not merely in any editor or IDE but, where appropriate, on a continuous integration server. For instance, language servers provide a tool-agnostic way for any IDE or editor to implement functionality such as completion or navigation, and linters such as ESLint can integrate into any half-decent editor or run in a CI environment. Since these tend to be open source projects, whereas IDE’s are normally commercial offerings, they’re less vulnerable to suddenly disappearing and leaving users high and dry.&lt;/p&gt;

&lt;p&gt;There’s also a lot of functionality in an IDE that I rarely, if ever, use. There’s no point including and starting up an FTP client as part of my editor if I’m never going to use it, as it slows the application down, and nor should I have to root around trying to turn off functionality I’m never going to have to use. For a lot of other functionality, there are more powerful standalone applications that I’m used to such as Postman or MySQL Workbench, and I’ll use them as and when I need them - I gain nothing by having them integrated with my editor.&lt;/p&gt;

&lt;p&gt;I also like to be able to use the same editor everywhere. I still occasionally dabble in Python, so a language-specific IDE wouldn’t be suitable when switching between languages. I also sometimes work on personal projects on an HP Stream netbook running Xubuntu, which is fine for small PHP projects that don’t require a database server or any web server other than the PHP dev server. I can happily run Neovim on that, but there’s no way it could run an IDE at an acceptable speed.&lt;/p&gt;

&lt;p&gt;Last of all, screen real estate is an issue. I don’t like interfaces that are too busy - I &lt;em&gt;cannot stand&lt;/em&gt; having anything, &lt;em&gt;at all&lt;/em&gt; on my desktop for any length of time at all, and any interface that has too much on screen at once is distracting. I will typically have Neovim open in a terminal, with the NERDTree file finder open on the left, and two panels split in the main body, and that’s all. A big factor in my productivity is how much code I can see at once, and having too much screen real estate taken up by menus and sidebars is counterproductive - with Neovim there’s almost nothing getting in the way.&lt;/p&gt;

&lt;p&gt;I personally have had to give this sort of explanation many, many times as to why I use first Vim and then Neovim, and indeed part of the motivation behind writing this post is that I’m sick to death of having to explain myself over and over again and will now be able to merely direct them to this article. Thanks to tools like PHPActor, vim-ale and FZF, I don’t feel like there’s anything I’m missing out on that an IDE would give me, and Psalm is very good at catching type errors without being tied to any one IDE, but that doesn’t stop people telling me I’m missing out on features I already have. Any time I come across a feature I think is cool, I go through the following process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find cool feature&lt;/li&gt;
&lt;li&gt;Find plugin that implements said feature&lt;/li&gt;
&lt;li&gt;Install plugin by adding a single line to my Neovim config and running &lt;code&gt;:PlugUpdate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add a few lines of config&lt;/li&gt;
&lt;li&gt;Start using feature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using an IDE &lt;em&gt;would&lt;/em&gt; eliminate the middle three steps, but I don’t find those onerous - we’re talking about the work of five minutes, which is insignificant compared to the time taken to learn to use the feature effectively. And a feature you don’t use is one that you still have to start up if it’s present, so making it an opt-in plugin is often a better way to go.&lt;/p&gt;

&lt;p&gt;Every other developer will have their own version of this story. Some will have stayed mostly static in their editor choices, while others will be changing all the time - indeed, I’ve sometimes used other editors for specific tasks in the past. In short, everyone has their own reasons for using their editor of choice, and it’s &lt;em&gt;appallingly&lt;/em&gt; arrogant to assume that their reasons for using a particular one are less valid than yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Am I PHPStorm Man?
&lt;/h2&gt;

&lt;p&gt;As I’ve said before, this behaviour is not confined to PHPStorm users, nor is it in any way universal among them. If you use PHPStorm and enjoy it, then fine, rock on. If you use a different editor or IDE, then that’s fine too - I don’t have a problem with that, and nor should your colleagues or line manager. Using any one editor or IDE &lt;em&gt;does not&lt;/em&gt; make you PHPStorm Man. What makes you PHPStorm Man is the patronising attitude.&lt;/p&gt;

&lt;p&gt;In the example given above, what made the senior dev PHPStorm Man was not the initial enquiry as to whether I’d tried Sublime Text, but the fact that he wouldn’t leave it be when confronted with evidence that I either had, could easily obtain, or didn’t need the functionality of his editor in mine, and that he was talking down to an experienced developer like a child.&lt;/p&gt;

&lt;p&gt;Obviously, this isn’t a new development - editor wars have long been a feature of our industry, as has the divide between IDE and editor users. But that doesn’t mean I, and no doubt others, don’t get utterly sick of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How not to be PHPStorm Man
&lt;/h2&gt;

&lt;p&gt;When talking to users of other editors or IDE’s about the subject of those tools, you should always bear this in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If they use a different tool to you, they probably know a hell of a lot more about it than you do, and are unlikely to take kindly to you ignorantly telling them what it can and can’t do&lt;/li&gt;
&lt;li&gt;Mastering an editor or IDE can take years, and if they’re already invested in one, it’s incredibly arrogant to just assume that they’re less productive in it than they would be in yours - even if they would (and that’s almost certainly debatable), it would take some time to adjust.&lt;/li&gt;
&lt;li&gt;They’ve probably had this conversation many times before, and are sick of hearing it, especially if they have a few years experience under their belt&lt;/li&gt;
&lt;li&gt;Not every feature you use is useful to them&lt;/li&gt;
&lt;li&gt;No-one minds seeing a cool feature, so feel free to demonstrate it, but bear in mind that it’s almost certainly not limited to that platform - if it’s suffficiently cool, someone &lt;em&gt;will&lt;/em&gt; have made it available as a plugin on most of the major editors and IDE’s. If they like it, the most likely scenario is that they’ll look to add that feature to their own editor via a plugin&lt;/li&gt;
&lt;li&gt;Just because it makes you more productive, doesn’t mean that it would make them more productive&lt;/li&gt;
&lt;li&gt;It’s perfectly possible to enforce consistent code styles and catch errors using standalone tools such as PHP CodeSniffer, Psalm, or ESLint, and these tools can be integrated in &lt;em&gt;any&lt;/em&gt; editor, triggered with Git hooks, or run with continuous integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, it has to be said that sometimes there &lt;em&gt;are&lt;/em&gt; some people who plod on with painfully outdated tools, like Notepad. But those tools tend to be limited to either commercial offerings that are no longer mainained or supported, or ones that lack any sort of plugin or extension system, making them limited in terms of how they can integrate with other services, so they’re fairly easy to spot. However, making a particular editor or IDE compulsory is going to be disruptive. If you’re in a leadership position, one way to resolve this is to simply require that everyone’s editor have certain functionality - for instance, if you specify that everyone’s editor must allow integration with PHP CodeSniffer and support for &lt;code&gt;.editorconfig&lt;/code&gt;, then anyone using a legacy editor that can’t support those will need to move away from it, but they’ll be able to pick one that suits them, rather than be forced into one they may well dislike. Editors and IDE’s don’t produce proprietary formats the way word processors do - they work with common formats, and if prominent open-source projects can enforce a consistent coding standard with many different editors there’s absolutely no reason why your colleagues can’t do so too,&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This post is a bit of an angry rant, but at the same time it shouldn’t be taken &lt;em&gt;too&lt;/em&gt; seriously. As I said, despite the name &lt;em&gt;PHPStorm Man&lt;/em&gt;, it’s not specifically about users of any one editor or IDE, but about the widespread, patronising attitude many developers have about editors and IDE’s other than their own in general.&lt;/p&gt;

&lt;p&gt;Someone using a different IDE or editor is absolutely none of your business unless you’re their line manager or you work on the same code base, and even then it should only be an issue if it causes a clear effect on their productivity or the quality of their code. If that’s not the case, keep your nose out.&lt;/p&gt;

</description>
      <category>editors</category>
      <category>programming</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Unconventional backends and data sources</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Mon, 16 Dec 2019 21:18:28 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/unconventional-backends-and-data-sources-k8e</link>
      <guid>https://dev.to/matthewbdaly/unconventional-backends-and-data-sources-k8e</guid>
      <description>&lt;p&gt;I've been a web dev for seven years now, and most of the sites I've worked on have used a database, API or a text format like Markdown as the primary source of content.&lt;/p&gt;

&lt;p&gt;However, there's been a few more unusual ones down the years. I once inherited a Tumblr blog for a client that pulled the navigation data from Google Sheets, and once built a static site generator to build a site from two CSV files of equipment listings.&lt;/p&gt;

&lt;p&gt;Do you have a story like this? What unusual data sources or backends have you used or inherited?&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Input components with the useState and useEffect hooks in React</title>
      <dc:creator>Matthew Daly</dc:creator>
      <pubDate>Sun, 27 Oct 2019 21:20:00 +0000</pubDate>
      <link>https://dev.to/matthewbdaly/input-components-with-the-usestate-and-useeffect-hooks-in-react-e58</link>
      <guid>https://dev.to/matthewbdaly/input-components-with-the-usestate-and-useeffect-hooks-in-react-e58</guid>
      <description>&lt;p&gt;Like many developers who use React.js, I’ve been eager to explore the Hooks API in the last year or so. They allow for easier ways to share functionality between components, and can allow for a more expressive syntax that’s a better fit for Javascript than class-based components. Unfortunately, they became production ready around the time I rolled out a new React-based home page, so I didn’t want to jump on them immediately in the context of a legacy application. I’m now finding myself with a bit of breathing space, so I’ve begun refactoring these components, and converting some to use hooks, in order to more easily reuse some code that currently resides in a big higher-order component.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; hooks are by far the most common hooks in most applications. However, I’ve found that the React documentation, while OK at explaining how to use these individually, is not so good at explaining how to use them together, particularly in the case of an input component, which is a common use case when looking to convert existing components. For that reason, I’m going to provide a short example of how you might use them together for that use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple function component
&lt;/h2&gt;

&lt;p&gt;A basic component for an input might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;//@flow&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note I’m using Flow annotations to type the arguments passed to my components. If you prefer Typescript it should be straightforward to convert to that.&lt;/p&gt;

&lt;p&gt;As you can see, this component accepts a name, ID, value and placeholder as props. If you add this to an existing React app, or use &lt;code&gt;create-react-app&lt;/code&gt; to create one and add this to it, you can include it in another component as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding state
&lt;/h2&gt;

&lt;p&gt;This will render, but as the value will never change it’s not actually of any use in a form. If you’ve written class-based React components before, you’ll know that the usual way to handle this is to move the value of the input from props to state. Prior to the introduction of the Hooks API, while you could create a function component, you couldn’t use state with it, making situations like this difficult to handle. Fortunately, the &lt;code&gt;useState&lt;/code&gt; hook now allows you to add state to a function component as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;//@flow&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We import the &lt;code&gt;useState&lt;/code&gt; hook at the top, as usual. Then, within the body of the component, we call &lt;code&gt;useState()&lt;/code&gt;, passing in the initial value of &lt;code&gt;props.value&lt;/code&gt;, and get back two variables in response:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;value&lt;/code&gt; is the value of the state variable, and can be thought of as equivalent to what &lt;code&gt;this.state.value&lt;/code&gt; would be in a class-based component&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setValue&lt;/code&gt; is a function for updating &lt;code&gt;value&lt;/code&gt; - rather than explicitly defining a function for this, we can just get one back from &lt;code&gt;useState()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we can set the value with &lt;code&gt;value={value}&lt;/code&gt;. We also need to handle changes in the state, so we add &lt;code&gt;onChange={(e) =&amp;gt; setValue(e.target.value)}&lt;/code&gt; to call &lt;code&gt;setValue()&lt;/code&gt; on a change event on the input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling effects
&lt;/h2&gt;

&lt;p&gt;The component will now allow you to edit the value. However, one problem remains. If you open the React dev tools, go to the props for this component, and set &lt;code&gt;value&lt;/code&gt; manually, it won’t be reflected in the input’s value, because the state has diverged from the initial value passed in as a prop. We need to be able to pick up on changes in the props and pass them through as state.&lt;/p&gt;

&lt;p&gt;In class-based components, there are lifecycle methods that fire at certain times, such as &lt;code&gt;componentDidMount()&lt;/code&gt; and &lt;code&gt;componentDidUpdate()&lt;/code&gt;, and we would use those to handle that situation. Hooks condense these into a single &lt;code&gt;useEffect&lt;/code&gt; hook that is more widely useful. Here’s how we might overcome this problem in our component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;//@flow&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useEffect&lt;/code&gt; takes one compulsory argument, in the form of a callback. Here we’re using that callback to set our state variable back to the value of the prop passed through.&lt;/p&gt;

&lt;p&gt;Note the second argument, which is an array of variables that should be watched for changes. If we had used the following code instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the callback would fire after every render, reverting the value back and possibly causing an infinite loop. For that reason, we pass through the second argument, which tells React to only fire the callback if one of the specified variables has changed. Here we only want to override the state when the value props passed down to the component changes, so we pass that prop in as an argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This is only a simple example, but it does show how simple and expressive hooks can make your React components, and how to use the &lt;code&gt;useEffect&lt;/code&gt; and &lt;code&gt;useState&lt;/code&gt; hooks together, which was something I found the documentation didn’t make clear. These two hooks cover a large chunk of the functionality of React, and knowledge of them is essential to using React effectively.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
