CINXE.COM
Configure Guzzle via Dependency Injection | Scripting-Base
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Configure Guzzle via Dependency Injection | Scripting-Base</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="generator" content="GravCMS" /> <link rel="icon" type="image/png" href="/user/themes/quark/images/favicon.png" /> <link rel="canonical" href="https://www.scripting-base.de/blog/configure-guzzle-via-dependency-injection" /> <link href="/user/plugins/markdown-notices/assets/notices.css" type="text/css" rel="stylesheet"> <link href="/user/plugins/markdown-spoilers/assets/spoilers.css" type="text/css" rel="stylesheet"> <link href="/user/plugins/breadcrumbs/css/breadcrumbs.css" type="text/css" rel="stylesheet"> <link href="/user/plugins/form/assets/form-styles.css" type="text/css" rel="stylesheet"> <link href="/user/plugins/highlight/css/monokai.css" type="text/css" rel="stylesheet"> <link href="/user/plugins/login/css/login.css" type="text/css" rel="stylesheet"> <link href="/user/themes/quark/css-compiled/spectre.min.css" type="text/css" rel="stylesheet"> <link href="/user/themes/quark/css-compiled/spectre-exp.min.css" type="text/css" rel="stylesheet"> <link href="/user/themes/quark/css-compiled/spectre-icons.min.css" type="text/css" rel="stylesheet"> <link href="/user/themes/quark/css-compiled/theme.min.css" type="text/css" rel="stylesheet"> <link href="/user/themes/quark/css/custom.css" type="text/css" rel="stylesheet"> <link href="/user/themes/quark/css/line-awesome.min.css" type="text/css" rel="stylesheet"> <script src="/system/assets/jquery/jquery-3.x.min.js"></script> <script src="/user/plugins/highlight/js/highlight.pack.js"></script> <script> hljs.initHighlightingOnLoad(); </script> </head> <body id="top" class=" header-fixed header-animated sticky-footer"> <div id="page-wrapper"> <section id="header" class="section"> <section class="container grid-lg"> <nav class="navbar"> <section class="navbar-section logo"> <a href="/" class="navbar-brand mr-10"> <svg xmlns="http://www.w3.org/2000/svg" width="331.278" height="37.4" viewBox="0 0 87.651 9.895"><path d="M1.568 7.437h1.355q.021.455.36.72.339.254.91.254.593 0 .91-.265.318-.265.318-.71 0-.39-.244-.676-.243-.297-.666-.413l-.837-.244q-.973-.264-1.47-.836-.487-.571-.487-1.428 0-.477.169-.868.18-.392.497-.667.318-.275.773-.423.455-.16 1.026-.16 1.09 0 1.747.562.656.56.709 1.524H5.294q-.021-.434-.328-.678-.307-.243-.815-.243t-.815.254q-.297.243-.297.667 0 .349.223.603.222.254.688.392l.889.254q.93.264 1.418.857.497.582.497 1.46 0 .498-.19.9-.18.402-.519.688-.339.286-.815.444-.476.16-1.069.16-.571 0-1.047-.15-.466-.158-.805-.433-.338-.275-.529-.667-.19-.391-.212-.878ZM10.575 9.564q-.572 0-1.037-.159-.466-.158-.794-.455-.328-.296-.508-.709-.18-.423-.18-.942V5.807q0-.519.18-.931.18-.424.508-.72t.794-.455q.465-.17 1.037-.17.508 0 .942.149.434.137.762.413.328.264.54.645.222.37.296.847h-1.323q-.074-.402-.402-.646-.328-.254-.815-.254-.561 0-.879.297-.317.296-.317.825v1.492q0 .519.307.815.317.297.889.297.476 0 .804-.244.339-.254.413-.656h1.323q-.074.476-.297.857-.211.37-.55.646-.328.264-.762.413-.423.137-.931.137zM15.94 3.637v1.006h.096q.106-.53.529-.815.423-.296 1.08-.296.941 0 1.47.582.53.582.53 1.608v.424h-1.387v-.307q0-.561-.307-.868-.307-.318-.846-.318-.508 0-.805.318-.285.317-.285.868v3.62h-1.323V3.636ZM20.915 8.252h2.084V4.833h-1.82V3.637h3.09v4.615h1.683v1.206h-5.037Zm1.703-6.191q0-.307.202-.498.211-.201.55-.201h.275q.339 0 .54.201.211.19.211.498 0 .317-.211.508-.201.19-.55.19h-.265q-.339 0-.55-.19-.202-.191-.202-.508ZM27.159 3.637h1.28v1.006h.096q.105-.519.508-.815.412-.296 1.047-.296.455 0 .826.158.37.16.624.455.265.297.402.72.149.413.149.942v1.45q0 .53-.149.963-.137.423-.402.73-.254.297-.624.455-.37.16-.826.16-.635 0-1.047-.297-.403-.297-.508-.826h-.096q0 .032.01.138.011.095.011.243l.022.318q.01.159.01.317v1.8H27.16zm1.333 2.202v1.418q0 .519.297.836.306.318.825.318.54 0 .847-.318.307-.328.307-.868V5.84q0-.54-.307-.847-.307-.317-.847-.317-.508 0-.815.317-.307.318-.307.847zM33.212 3.637h1.609V1.838h1.323v1.8h2.212v1.195h-2.212V7.66q0 .275.159.445.169.158.465.158h1.482v1.196h-1.577q-.857 0-1.355-.487-.497-.486-.497-1.312V4.833h-1.609ZM39.964 8.252h2.085V4.833h-1.82V3.637h3.09v4.615h1.683v1.206h-5.038Zm1.704-6.191q0-.307.201-.498.212-.201.55-.201h.276q.339 0 .54.201.211.19.211.498 0 .317-.211.508-.201.19-.55.19h-.265q-.339 0-.55-.19-.202-.191-.202-.508ZM46.198 3.637h1.249v1.006h.095q.106-.54.508-.826.402-.285 1.037-.285.9 0 1.408.582.508.582.508 1.545v3.8H49.68V5.817q0-.54-.286-.836-.286-.307-.783-.307-.508 0-.804.317-.286.307-.286.847v3.62h-1.323zM52.4 5.786q0-.508.137-.92.149-.424.413-.72.265-.297.635-.456.37-.158.826-.158.656 0 1.08.317.422.307.528.847h.074V3.637h1.27V9.12q0 .486-.169.878-.17.402-.487.677-.317.276-.772.424-.445.158-.995.158h-1.556v-1.1h1.556q.508 0 .804-.286.296-.275.296-.751v-.138l.043-1.1h-.064q-.106.54-.54.857-.423.307-1.068.307-.455 0-.826-.16-.37-.169-.635-.454-.264-.297-.413-.71-.137-.423-.137-.93Zm1.333.974q0 .529.307.836.307.296.847.296.53 0 .836-.296.317-.307.317-.836v-.942q0-.53-.317-.836-.307-.307-.836-.307-.54 0-.847.307-.307.306-.307.836zM59.194 5.352h4.234v1.217h-4.234zM65.27 1.838h2.423q1.143 0 1.799.53.656.529.656 1.439 0 .698-.392 1.132-.391.434-1.058.487v.095q.74.053 1.164.519.424.466.424 1.228 0 .508-.18.91-.17.402-.498.688-.317.285-.783.444-.466.148-1.048.148H65.27zm1.29 4.234v2.286h1.133q.592 0 .92-.297.328-.307.328-.846 0-.54-.328-.836-.328-.307-.92-.307Zm1.111-1.09q.52 0 .826-.276.307-.275.307-.751t-.307-.74q-.296-.276-.815-.276H66.56v2.043zM71.376 7.828q0-.433.158-.772.16-.35.455-.593.297-.243.71-.37.423-.138.941-.138h1.461v-.497q0-.402-.307-.635-.296-.244-.836-.244-.434 0-.73.16-.297.158-.381.433h-1.291q.063-.381.264-.677.201-.307.519-.519.317-.212.73-.328.413-.116.9-.116.56 0 1.016.137.455.127.772.381.318.244.487.603.18.35.18.784v4.021h-1.249V8.315h-.095q-.095.572-.593.91-.497.34-1.238.34-.857 0-1.365-.467-.508-.476-.508-1.27zm1.323-.158q0 .402.264.635.275.222.762.222.593 0 .984-.286.392-.296.392-.74v-.678h-1.45q-.444 0-.698.222-.254.223-.254.625zM77.8 7.966h1.354q.064.275.328.434.265.148.657.148h.423q.497 0 .762-.19.275-.201.275-.54 0-.582-.9-.71l-.825-.094q-.995-.127-1.471-.55-.476-.424-.476-1.218 0-.825.571-1.27.582-.444 1.662-.444h.434q.963 0 1.545.412.592.403.698 1.133h-1.354q-.053-.244-.297-.381-.233-.148-.592-.148h-.434q-.932 0-.932.656 0 .56.784.656l.857.116q1.037.127 1.534.572.498.434.498 1.228 0 .857-.603 1.322-.604.466-1.736.466h-.423q-1.027 0-1.64-.413-.604-.423-.7-1.185zM84.181 5.807q0-.519.18-.942.18-.423.508-.72.328-.296.794-.455.466-.158 1.048-.158.571 0 1.026.169.466.159.794.455.328.296.508.72.18.423.18.942v1.058h-3.725v.38q0 .594.317.922.328.328.9.328.423 0 .73-.138.307-.137.434-.38h1.302q-.096.359-.318.655-.222.286-.55.498-.318.2-.73.317-.403.106-.868.106-.572 0-1.048-.159-.466-.158-.794-.455-.328-.307-.508-.73t-.18-.942Zm1.313.18h2.434v-.17q0-.581-.328-.899-.318-.328-.89-.328-.57 0-.899.328-.317.318-.317.889z" style="font-style:normal;font-variant:normal;font-weight:700;font-stretch:normal;font-family:'JetBrains Mono';-inkscape-font-specification:'JetBrains Mono Bold';stroke-width:.264583" transform="translate(-1.568 -1.362)"/></svg> </a> </section> <section class="navbar-section desktop-menu"> <nav class="dropmenu animated"> <ul > <li> <a href="/" class=""> Home </a> </li> <li> <a href="/blog" class="active"> Blog </a> <ul> <li> <a href="/blog/requirejs-backend" class=""> RequireJS in backend modules </a> </li> <li> <a href="/blog/psr-7-for-backend-modules" class=""> PSR-7 for backend modules </a> </li> <li> <a href="/blog/ajax-with-psr-7" class=""> AJAX with PSR-7 </a> </li> <li> <a href="/blog/cleaning-the-hood-tca" class=""> Cleaning the hood: TCA </a> </li> <li> <a href="/blog/cleaning-the-hood-record-wizard" class=""> Cleaning the hood: Record wizard </a> </li> <li> <a href="/blog/welcome-to-the-t3editor" class=""> Welcome to the jungle, or: How I survived EXT:t3editor </a> </li> <li> <a href="/blog/history-javascript-typo3" class=""> The history of JavaScript in TYPO3 </a> </li> <li> <a href="/blog/goodbye-jquery-ajax" class=""> Goodbye $.ajax - The clock is ticking </a> </li> <li> <a href="/blog/icons-are-dead-long-live-broken-icons" class=""> Icons are dead, long live broken icons! </a> </li> <li> <a href="/blog/improve-test-speed-in-symfony-with-in-ram-database" class=""> Improve test speed in Symfony with in-RAM database </a> </li> <li> <a href="/blog/configure-guzzle-via-dependency-injection" class="active"> Configure Guzzle via Dependency Injection </a> </li> </ul> </li> <li> <a href="/legal-notice" class=""> Legal Notice </a> </li> </ul> </nav> </section> </nav> </section> </section> <div class="mobile-menu"> <div class="button_container" id="toggle"> <span class="top"></span> <span class="middle"></span> <span class="bottom"></span> </div> </div> <section id="blog-hero" class="section modular-hero hero overlay-dark text-light parallax" style="background-image: url('/user/pages/02.blog/11.configure-guzzle-via-dependency-injection/taylor-vick-M5tzZtFCOfs-unsplash.jpg');"> <div class="image-overlay"></div> <section class="container grid-lg" style="text-align: center"> <h1>Configure Guzzle via Dependency Injection</h1> <h2></h2> <span class="blog-date"> <time class="dt-published" datetime="2022-04-03T07:00:00+02:00"> <i class="fa fa-calendar"></i> 3rd Apr 2022 </time> </span> | <span class="blog-date"> <i class="fa fa-clock-o"></i> 7 minutes </span> <span class="tags"> <a class="label label-rounded label-secondary p-category" href="/blog/tag:symfony#body-wrapper">symfony</a> <a class="label label-rounded label-secondary p-category" href="/blog/tag:guzzle#body-wrapper">guzzle</a> <a class="label label-rounded label-secondary p-category" href="/blog/tag:dependency injection#body-wrapper">dependency injection</a> <a class="label label-rounded label-secondary p-category" href="/blog/tag:tutorial#body-wrapper">tutorial</a> </span> </section> <i id="to-start" class="pulse fa fa-angle-down"></i> </section> <section id="start"> <section id="body-wrapper" class="section blog-listing"> <section class="container grid-lg"> <div id="breadcrumbs" itemscope itemtype="http://schema.org/BreadcrumbList"> <span itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"> <a itemscope itemtype="http://schema.org/Thing" itemprop="item" href="/" itemid="/"> <span itemprop="name">Home</span> </a> <i class=""></i> <meta itemprop="position" content="1" /> </span> <span itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"> <a itemscope itemtype="http://schema.org/Thing" itemprop="item" href="/blog" itemid="/blog"> <span itemprop="name">Blog</span> </a> <i class=""></i> <meta itemprop="position" content="2" /> </span> <span itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"> <span itemscope itemtype="http://schema.org/Thing" itemprop="item" itemid="/blog/configure-guzzle-via-dependency-injection"> <span itemprop="name">Configure Guzzle via Dependency Injection</span> </span> <meta itemprop="position" content="3" /> </span> </div> <div class="columns"> <div id="item" class="column col-9 col-md-12 extra-spacing"> <div class="content-item h-entry"> <div class="e-content"> <p>With Guzzle v7, its class <code>GuzzleHttp\Client</code> became annotated as <code>@final</code> as it will be a real <code>final</code> class in Guzzle v8. Extending Guzzle clients to enrich them with custom functionality or to pass configuration (e.g. API credentials) is now discouraged and static code analysis tools like PHPStan may report this as an error. Depending on how <code>GuzzleHttp\Client</code> is extended, migration may be cumbersome. I got your back, and I'll cover some common cases in this blog post.</p> <h2>Simple client with custom configuration</h2> <p>In a project, we heavily extended <code>GuzzleHttp\Client</code> for all our cases as we wanted to make use of dependency injection via the client's class names. Please see the example below how our clients were implemented.</p> <p>We defined our class <code>App\Client\GithubClient</code> that extends <code>GuzzleHttp\Client</code> without any further logic:</p> <pre><code class="language-php">// src/Client/GithubClient.php namespace App\Client; class GithubClient extends \GuzzleHttp\Client { }</code></pre> <p>The client <code>App\Client\GithubClient</code> is injected into the service <code>App\Service\GithubService</code>:</p> <pre><code class="language-php">// src/Service/GithubService.php namespace App\Service; class GithubService { public function __construct( private readonly GithubClient $client ) { } }</code></pre> <p>The client <code>App\Client\GithubClient</code> is configured with the API key in the project's <code>services.yaml</code> file:</p> <pre><code class="language-yaml"># src/config/services.yaml services: App\Client\GithubClient class: App\Client\GithubClient arguments: $config: headers: Authorization: 'token %app.github.api_key%'</code></pre> <h3>Migration time</h3> <p>This is the most simple way a Guzzle client may be configured. A client just takes some configuration (like the <code>Authorization</code> header from the example) above, and it's ready to use. The whole class can be replaced by a simple <code>GuzzleHttp\Client</code> instance, which is configured in our <code>services.yaml</code> file:</p> <pre><code class="language-yaml">services: guzzle.client.github class: GuzzleHttp\Client arguments: $config: headers: Authorization: 'token %app.github.api_key%'</code></pre> <div class="toast toast-error"> <p>This requires another change! Since we are using <code>GuzzleHttp\Client</code> now, autowiring the correct Guzzle client via its class name does not work anymore.</p> </div> <p>Now, we have to configure our service to use the <code>guzzle.client.github</code> service explicitly:</p> <pre><code class="language-yaml">services: App\Service\GithubService: class: App\Service\GithubService arguments: $client: '@guzzle.client.github'</code></pre> <p>Finally, we have to adjust the <code>constructor</code> of our service to expect an instance <code>\GuzzleHttp\Client</code> as argument:</p> <pre><code class="language-php">// src/Service/GithubService.php namespace App\Service; class GithubService { public function __construct( private readonly \GuzzleHttp\Client $client ) { } }</code></pre> <p>This migration is pretty easy, the key changes are:</p> <ul> <li>the service name is changed to <code>guzzle.client.github</code></li> <li>the class in the service definition is changed to <code>GuzzleHttp\Client</code>, as our custom client <code>App\Client\GithubClient</code> became obsolete</li> <li><code>App\Service\GithubService</code> is configured to use <code>guzzle.client.github</code> explicitly</li> </ul> <h2>Create complex client via factory</h2> <p>In some cases, our clients are more complex and need custom logic besides the configuration. A common use-case is passing an API key via a query parameter in the request URL, for example as required by Google Maps.</p> <p>See an example of how our <code>GoogleMapsClient</code> was implemented before:</p> <pre><code class="language-php">// src/Client/GoogleMapsClient.php namespace App\Client; class GoogleMapsClient extends \GuzzleHttp\Client { public function __construct(array $config, string $apiKey) { $handlerStack = \GuzzleHttp\HandlerStack::create($config['handler'] ?? null); $config = array_merge($config, [ 'base_uri' => rtrim($config['base_uri'] ?? '', '/') . '/', 'handler' => $handlerStack, ]); $handlerStack->unshift(\GuzzleHttp\Middleware::mapRequest(static function (\Psr\Http\Message\RequestInterface $request) use ($apiKey) { return $request->withUri(\GuzzleHttp\Psr7\Uri::withQueryValue($request->getUri(), 'key', $apiKey)); })); parent::__construct($config); } }</code></pre> <p>In this example, we create a <code>GuzzleHttp\HandlerStack</code> and add a middleware to it. The middleware is responsible for adding the <code>key</code> query parameter to the request. Reminder: We're extending <code>\GuzzleHttp\Client</code> which will not work anymore in the future.</p> <h3>Factories to the rescue</h3> <p>To be able to re-implement our custom logic, we use <a href="https://symfony.com/doc/current/service_container/factories.html">service factories</a> to create our client. In this example, we're using an invokable factory:</p> <pre><code class="language-php">// src/Factory/Guzzle/GoogleMapsClientFactory.php namespace App\Factory\Guzzle; class GoogleMapsClientFactory { public function __invoke(array $config, string $apiKey): \GuzzleHttp\ClientInterface { $handlerStack = \GuzzleHttp\HandlerStack::create($config['handler'] ?? null); $config = array_merge($config, [ 'base_uri' => rtrim($config['base_uri'] ?? '', '/') . '/', 'handler' => $handlerStack, ]); $handlerStack->unshift(\GuzzleHttp\Middleware::mapRequest(static function (RequestInterface $request) use ($apiKey) { return $request->withUri(\GuzzleHttp\Psr7\Uri::withQueryValue($request->getUri(), 'key', $apiKey)); })); return new \GuzzleHttp\Client($config); } }</code></pre> <p>The factory now creates an instance of <code>GuzzleHttp\Client</code>, containing <code>GuzzleHttp\HandlerStack</code> with the middleware that adds the <code>key</code> query parameter to the request.</p> <p>We can use the created factory with the <code>factory</code> option in our service definition:</p> <pre><code class="language-yaml">services: App\Factory\Guzzle\GoogleMapsClientFactory: ~ guzzle.client.google_maps: class: GuzzleHttp\Client factory: '@App\Factory\Guzzle\GoogleMapsClientFactory' arguments: $config: base_uri: '%env(GOOGLE_MAPS_BASE_URI)%' $apiKey: '%env(GOOGLE_MAPS_API_KEY_PRIVATE)%'</code></pre> <p>The client can now get passed via dependency injection:</p> <pre><code class="language-yaml">services: App\Service\GoogleMapsService: class: App\Service\GoogleMapsService arguments: $client: '@guzzle.client.google_maps'</code></pre> <p>The key changes are:</p> <ul> <li>the service name is changed to <code>guzzle.client.google_maps</code></li> <li>a factory is introduced that takes care of creating a client instance</li> <li>the class in the service definition is changed to <code>GuzzleHttp\Client</code>, as our custom client <code>App\Client\GoogleMapsClient</code> became obsolete</li> <li>the service definition uses the introduced factory</li> <li><code>App\Service\GoogleMapsService</code> is configured to use <code>guzzle.client.google_maps</code> explicitly</li> </ul> <p>Now, you have only one Guzzle client to rule them all and there's one step less before Guzzle v8 hits the road.</p> <p><small>Header photo by <a href="https://unsplash.com/photos/M5tzZtFCOfs">Taylor Vick</a> on <a href="https://unsplash.com">Unsplash</a>.</small></p> </div> </div> <p class="prev-next text-center"> <a class="btn" href="/blog/improve-test-speed-in-symfony-with-in-ram-database"><i class="fa fa-angle-left"></i> Previous Post</a> </p> </div> <div id="sidebar" class="column col-3 col-md-12"> <div class="sidebar-content syndicate"> <h4>Syndicate</h4> <a class="btn" href="/blog.atom"><i class="fa fa-rss-square"></i> Atom 1.0</a> <a class="btn" href="/blog.rss"><i class="fa fa-rss-square"></i> RSS</a> <a class="btn" href="/blog.json"><i class="fa fa-rss-square"></i> JSON</a></div> </div> </div> </section> </section> </section> </div> <section id="footer" class="section bg-gray"> <section class="container grid-lg"> <p><a href="http://getgrav.org">Grav</a> was <i class="fa fa-code"></i> with <i class="fa fa-heart-o pulse "></i> by <a href="https://trilby.media">Trilby Media</a>.</p> </section> </section> <div class="mobile-container"> <div class="overlay" id="overlay"> <div class="mobile-logo"> <a href="/" class="navbar-brand mr-10"> <svg xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" viewBox="0 0 504 140" clip-rule="evenodd"><path d="M235.83 71.56h-7.98c-1.2 0-2.2 1-2.2 2.2V89.1l-.15.13c-4.7 3.96-10.64 6.14-16.72 6.14-14.36 0-26.04-11.68-26.04-26.04s11.68-26.04 26.04-26.04c5.58 0 10.92 1.76 15.44 5.1.87.66 2.1.57 2.86-.2l5.7-5.7c.44-.44.67-1.05.63-1.68-.02-.62-.32-1.2-.82-1.6-6.76-5.35-15.2-8.3-23.8-8.3-21.18 0-38.42 17.23-38.42 38.4 0 21.2 17.24 38.42 38.42 38.42 10.93 0 21.4-4.7 28.7-12.9.35-.4.55-.93.55-1.47v-19.6c0-1.22-.98-2.2-2.2-2.2M502.8 34.44c-.4-.6-1.1-.98-1.84-.98h-8.7c-.87 0-1.66.52-2 1.32l-24.5 56.84-24.9-56.85c-.36-.8-1.15-1.3-2.02-1.3h-8.72c-.74 0-1.44.36-1.84.98-.4.62-.48 1.4-.17 2.1l30.2 68.85c.34.8 1.13 1.32 2 1.32h11c.88 0 1.67-.53 2.02-1.33l29.66-68.87c.3-.68.22-1.47-.2-2.1"/><path d="M388.68 34.77c-.35-.8-1.14-1.32-2-1.32h-11c-.88 0-1.67.53-2.02 1.33L344 103.64c-.3.68-.22 1.47.18 2.08.4.62 1.1 1 1.84 1h8.7c.86 0 1.66-.53 2-1.33l24.5-56.86 24.9 56.86c.36.8 1.15 1.32 2.02 1.32h8.72c.74 0 1.44-.38 1.84-1 .4-.62.47-1.4.17-2.1l-30.2-68.85zM309.2 81.52l.47-.22c8.68-4.2 14.28-13.1 14.28-22.67 0-13.88-11.3-25.18-25.17-25.18H266.9c-1.2 0-2.2 1-2.2 2.2v68.86c0 1.23 1 2.22 2.2 2.22h8c1.2 0 2.2-1 2.2-2.2V45.8h21.68c7.05 0 12.8 5.75 12.8 12.8 0 5.9-4 11-9.73 12.42-1.04.26-2.07.4-3.07.4h-7.98c-.83 0-1.6.46-1.96 1.2-.38.73-.3 1.62.2 2.3l22.6 30.87c.42.58 1.08.92 1.78.92h9.9c.84 0 1.6-.47 1.97-1.2.37-.75.3-1.64-.2-2.3l-15.9-21.7zM107.2 80.97c-7.26-4.8-11.4-8.85-15.02-16.1-2.47 4.97-8.24 12.37-17.96 18.2-4.86 15.1-27.96 44-35.43 39.9-2.22-1.2-2.64-2.8-2.15-4.45.54-4.13 9.08-13.62 9.08-13.62s.18 2 2.92 6.18c-3.6-11.2 5.96-25.03 8.5-29.73 3.98-1.27 4.27-6.4 4.27-6.4.26-7.9-3.28-13.63-6.7-17.05 2.46 3 3.25 7.54 3.37 11.7v.02c0 .47 0 .93 0 1.4-.12 3.43-1.16 8.18-3.38 8.18v.03c-2.28-.1-5.1.4-7.63 1.18l-5.6 1.34s2.98-.13 4.6 1.25c-1.8 2.9-5.78 6.53-10.22 8.58-6.45 3-8.3-2.96-5.03-6.84.8-.94 1.62-1.74 2.38-2.4-.5-.5-.8-1.2-.88-2.06 0 0 0 0 0-.02-.46-1.97-.2-4.54 2.6-8.62.54-.86 1.2-1.75 2-2.65.02-.04.04-.07.07-.1.03-.04.07-.08.1-.12.02-.02.04-.04.06-.06.2-.23.42-.45.64-.67 3.34-3.4 8.6-6.96 16.9-10.15C64.4 43.68 67.94 41 67.94 41c1.07-1.1 2.94-2.45 3.63-2.8-5.05-8.77-6.07-21.15-4.75-24.5-.1.2-.2.38-.3.57.5-1.14.83-1.5 1.34-2.1 1.38-1.64 6.06-2.5 7.74.96.9 1.84 1.06 4.23 1.03 6.02-3.7-.2-7.06 4.04-7.06 4.04s3.07-1.46 6.88-1.5c0 0 1 .9 2.28 2.56-1.7 3.2-4.52 10.02-2.5 17.16.35 1.4.86 2.62 1.5 3.65.02.05.04.1.07.14.05.07.1.13.14.2 3.37 5.06 9.54 5.66 9.54 5.66-2.9-1.45-5.27-3.76-6.8-6.56-.82-1.5-1.3-2.77-1.6-3.77-1.64-6.3.77-10 2.14-12.47 3.17-4.9 8.95-7.9 15.15-7.18 8.72 1 14.97 8.86 13.98 17.57-.6 5.32-3.78 9.72-8.15 12.12 1.05 2.84-.07 6.28-.07 6.28 2.64 3.32 2.76 5.23 2.67 7-3.36-.55-6.62 1.7-6.62 1.7s6.48-1.53 10.24 1.82c2.44 2.64 4.08 5 5.05 6.77 1.4 2.5 7.86 2.68 7.12 7.2-.74 4.5-5.68 4.53-13.4-.57M69.56 0C31.15 0 0 31.15 0 69.57c0 38.42 31.15 69.57 69.57 69.57 38.42 0 69.57-31.15 69.57-69.57C139.14 31.15 108 0 69.57 0M73.8 51.7c.8-.82.8-2.14 0-2.95-.82-.82-2.14-.82-2.95 0-.82.8-.82 2.13 0 2.94.8.8 2.13.8 2.95 0M66.45 53.15c-.82.8-.82 2.13 0 2.95.8.8 2.13.8 2.94 0 .8-.82.8-2.14 0-2.95-.82-.8-2.14-.8-2.95 0"/><path d="M79.23 54.23c-1.27-1.27-3.34-1.27-4.6 0l-2.72 2.7c-1.27 1.3-1.27 3.35 0 4.63l3 2.97c1.26 1.28 3.32 1.28 4.6 0l2.7-2.7c1.28-1.28 1.28-3.35 0-4.62l-2.97-2.97zM95.76 41.44c-2.15-2.57 1.87-7.25 4.4-4.46 4.64 5.15-2.25 7.04-4.4 4.46m9.24 2.7c3.45-6.56-1.42-10.4-4.77-13.53-5.36-5.03-10.7-7.2-16.8-.23-6.1 6.98-2.24 15.07 3.35 19.06 5.58 4 14.78 1.25 18.22-5.3"/></svg></a> </div> <nav class="overlay-menu"> <ul class="tree"> <li> <a href="/" class=""> Home </a> </li> <li> <a href="/blog" class="active"> Blog </a> <ul> <li> <a href="/blog/requirejs-backend" class=""> RequireJS in backend modules </a> </li> <li> <a href="/blog/psr-7-for-backend-modules" class=""> PSR-7 for backend modules </a> </li> <li> <a href="/blog/ajax-with-psr-7" class=""> AJAX with PSR-7 </a> </li> <li> <a href="/blog/cleaning-the-hood-tca" class=""> Cleaning the hood: TCA </a> </li> <li> <a href="/blog/cleaning-the-hood-record-wizard" class=""> Cleaning the hood: Record wizard </a> </li> <li> <a href="/blog/welcome-to-the-t3editor" class=""> Welcome to the jungle, or: How I survived EXT:t3editor </a> </li> <li> <a href="/blog/history-javascript-typo3" class=""> The history of JavaScript in TYPO3 </a> </li> <li> <a href="/blog/goodbye-jquery-ajax" class=""> Goodbye $.ajax - The clock is ticking </a> </li> <li> <a href="/blog/icons-are-dead-long-live-broken-icons" class=""> Icons are dead, long live broken icons! </a> </li> <li> <a href="/blog/improve-test-speed-in-symfony-with-in-ram-database" class=""> Improve test speed in Symfony with in-RAM database </a> </li> <li> <a href="/blog/configure-guzzle-via-dependency-injection" class="active"> Configure Guzzle via Dependency Injection </a> </li> </ul> </li> <li> <a href="/legal-notice" class=""> Legal Notice </a> </li> </ul> </nav> </div> </div> <script src="/user/themes/quark/js/jquery.treemenu.js"></script> <script src="/user/themes/quark/js/site.js"></script> </body> </html>