<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[BirkAndMe]]></title><description><![CDATA[BirkAndMe]]></description><link>https://blog.birk-jensen.dk</link><generator>RSS for Node</generator><lastBuildDate>Wed, 22 Apr 2026 07:31:28 GMT</lastBuildDate><atom:link href="https://blog.birk-jensen.dk/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Drupal: Get the current entity]]></title><description><![CDATA[First things first, it’s not always possible to get the current entity, some routes will have more than 1 entity, some wont have any, drush wont give a route. But most of the time, what you want when talking about the current entity is the entity bei...]]></description><link>https://blog.birk-jensen.dk/drupal-get-the-current-entity</link><guid isPermaLink="true">https://blog.birk-jensen.dk/drupal-get-the-current-entity</guid><category><![CDATA[Drupal]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Mon, 11 Aug 2025 12:37:37 GMT</pubDate><content:encoded><![CDATA[<p>First things first, it’s not always possible to get the <em>current</em> entity, some routes will have more than 1 entity, some wont have any, <code>drush</code> wont give a route. But most of the time, what you want when talking about the <em>current</em> entity is the entity being viewed by a canonical (or other entity links) route.</p>
<h2 id="heading-snippet">Snippet</h2>
<p>What you’re looking for is most likely this:</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">Core</span>\<span class="hljs-title">Entity</span>\<span class="hljs-title">EntityInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">Core</span>\<span class="hljs-title">Routing</span>\<span class="hljs-title">RouteMatch</span>;

<span class="hljs-comment">/**
 * Get the route entity (defaults to current route).
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">currentEntity</span>(<span class="hljs-params">?RouteMatch $route_match = <span class="hljs-literal">NULL</span></span>): ?<span class="hljs-title">EntityInterface</span> </span>{
  $route_match = $route_match ?? \Drupal::routeMatch();

  <span class="hljs-keyword">foreach</span> ($route_match-&gt;getParameters() <span class="hljs-keyword">as</span> $key =&gt; $value) {
    <span class="hljs-keyword">if</span> (
      $value <span class="hljs-keyword">instanceof</span> EntityInterface
      <span class="hljs-comment">// Make sure the route path matches a link in the entity.</span>
      &amp;&amp; in_array($route_match-&gt;getRouteObject()-&gt;getPath(), $value-&gt;getEntityType()-&gt;getLinkTemplates())
    ) {
      <span class="hljs-keyword">return</span> $value;
    }
  }
}
</code></pre>
<p>I’m not the first to wonder or write about this, check out this forum topic: <a target="_blank" href="https://www.drupal.org/forum/support/module-development-and-code-questions/2014-07-25/drupal-8-get-entity-object-given"><em>Drupal 8: get entity object given system path?</em></a>. And my solution is a lot like <a target="_blank" href="https://www.computerminds.co.uk/drupal-code/get-entity-route"><em>Get the entity for a route</em></a> which also appears in the previous link.</p>
<p>The code is straight forward, it checks if the parameter is an <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityInterface.php/interface/EntityInterface/11.x"><code>EntityInterface</code></a>, and if the entity has the route path as a link, a <a target="_blank" href="https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Entity%21Node.php/class/Node/11.x"><code>Node</code></a> would have these <code>/node/{node}</code>, <code>/node/{node}/edit</code><strong>,</strong> <code>/node/{node}/delete</code> and a bunch more depending on what modules you have.</p>
<h2 id="heading-one-route-two-entities">One route two entities</h2>
<p>The problem is some routes have more than 1 entity in the parameters, like the <code>entity.node.revision</code> route that takes 2 entities <code>/node/{node}/revisions/{node_revision}/view</code>, the first being the node, and the second being a specific revision of the node. A route could have many more parameter entities, but it seems rare to encounter more than 2.<br />Sometimes a route will have 1 entity, but the path is not in the entity links, this usually occurs when it’s a custom route (at least in my cases).</p>
<p>With multiple entities only the <em>primary</em> entity is needed. For this to work need some business logic, that will get us the correct entity.<br />Using the <code>entity.node.revision</code> as an example I would argue that the entity you wanted is the <code>node_revision</code>, but the <code>currentEntity()</code> function would simple return the first entity it encountered.<br />The same is true for an path without an entity link, we need some kind of project specific logic to handle this.</p>
<p>So custom business logic, that can change from project to project. I’d say there’re 3 obvious solutions for this, a <a target="_blank" href="https://www.drupal.org/docs/drupal-apis/plugin-api">plugin</a> system, an <a target="_blank" href="https://api.drupal.org/api/drupal/core%21core.api.php/group/events/11.x">event based</a> system or <a target="_blank" href="https://api.drupal.org/api/drupal/core%21core.api.php/group/hooks/11.x">hooks</a>. Plugins seems overkill, and hooks are <em>persona non grata</em>, so at the moment let’s fix it using events (I would probably have used hooks if it was just me, it’s by far a smaller and easier implementation).</p>
<h3 id="heading-the-fix">The fix</h3>
<p>I don’t want this turning into a how to do events post, so I’ll leave a lot of the parts out, you should read the <a target="_blank" href="https://www.drupal.org/docs/develop/creating-modules/subscribe-to-and-dispatch-events">Subscribe to and dispatch events</a> article for any background on this.<br />I’ve added proof of concept module in following repo <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity">https://github.com/BirkAndMe/poc-primary_entity</a>, which I’ll link to, but I’m also going to include any code of interest in the post.</p>
<pre><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">primary_entity</span>(<span class="hljs-params">?RouteMatchInterface $route_match = <span class="hljs-literal">NULL</span></span>): ?<span class="hljs-title">EntityInterface</span> </span>{
  $route_match = $route_match ?? \Drupal::routeMatch();

  <span class="hljs-comment">/** <span class="hljs-doctag">@var</span> EventDispatcherInterface*/</span>
  $event_dispatcher = \Drupal::service(<span class="hljs-string">'event_dispatcher'</span>);

  $event = <span class="hljs-keyword">new</span> PrimaryEntityEvent($route_match);
  $event_dispatcher-&gt;dispatch($event, PrimaryEntityEvent::GET);

  <span class="hljs-keyword">return</span> $event-&gt;hasPrimaryEntity() ? $event-&gt;getPrimaryEntity() : <span class="hljs-literal">NULL</span>;
}
</code></pre>
<p>The idea is you’ll call the <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity/blob/main/primary_entity.module"><code>primary_entity()</code></a> function and it will return the current / primary <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityInterface.php/interface/EntityInterface/11.x"><code>EntityInterface</code></a> for the given route. The <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity/blob/1e2877694fd89da6528fecf24f81e4fb1f723728/primary_entity.module#L12"><code>primary_entity()</code></a> function dispatches a <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity/blob/main/src/Event/PrimaryEntityEvent.php"><code>PrimaryEntityEvent::GET</code></a> event, and the <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity/blob/main/src/EventSubscriber/PrimaryEntitySubscriber.php"><code>PrimaryEntitySubscriber</code></a> subscribes to the event:</p>
<pre><code class="lang-php">  <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSubscribedEvents</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span> </span>{
    <span class="hljs-keyword">return</span> [
      PrimaryEntityEvent::GET =&gt; [
        [<span class="hljs-string">'checkRevision'</span>, <span class="hljs-number">110</span>],
        [<span class="hljs-string">'checkLinkTemplates'</span>, <span class="hljs-number">100</span>]
      ],
    ];
  }
</code></pre>
<p>Note the <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity/blob/1e2877694fd89da6528fecf24f81e4fb1f723728/src/EventSubscriber/PrimaryEntitySubscriber.php#L26"><code>PrimaryEntitySubscriber::checkRevision()</code></a> has a higher priority, it needs to run first, or the <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity/blob/1e2877694fd89da6528fecf24f81e4fb1f723728/src/EventSubscriber/PrimaryEntitySubscriber.php#L48"><code>PrimaryEntitySubscriber::checkLinkTemplates()</code></a> function will return the entity and not the current revision when viewing revision routes.</p>
<p>The <code>PrimaryEntitySubscriber::checkLinkTemplates()</code> is doing almost the same as the <code>currentEntity()</code> function above, but lets take quick look at the <code>PrimaryEntitySubscriber::checkRevision()</code> that checks to see if the route is a revision route:</p>
<pre><code class="lang-php">  <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkRevision</span>(<span class="hljs-params">PrimaryEntityEvent $event</span>) </span>{
    <span class="hljs-keyword">if</span> (!$event-&gt;getRouteMatch()-&gt;getRouteObject()) {
      <span class="hljs-keyword">return</span>;
    }

    $entities = $event-&gt;getEntities();
    $parameters = $event-&gt;getRouteMatch()-&gt;getRouteObject()-&gt;getOption(<span class="hljs-string">'parameters'</span>) ?? [];

    <span class="hljs-keyword">foreach</span> ($parameters <span class="hljs-keyword">as</span> $key =&gt; $parameter) {
      <span class="hljs-keyword">if</span> (
        !<span class="hljs-keyword">empty</span>($parameter[<span class="hljs-string">'type'</span>])
        &amp;&amp; strpos($parameter[<span class="hljs-string">'type'</span>] ?? <span class="hljs-string">''</span>, <span class="hljs-string">'entity_revision:'</span>) === <span class="hljs-number">0</span>
        &amp;&amp; <span class="hljs-keyword">isset</span>($entities[$key])
      ) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;setPrimaryEntity($event, $entities[$key]);
      }
    }
  }
</code></pre>
<p>The <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity/blob/1e2877694fd89da6528fecf24f81e4fb1f723728/src/Event/PrimaryEntityEvent.php#L39"><code>PrimaryEntityEvent::getEntities()</code></a> is a helper function that will look through the route parameters in the event, and return all route parameters of <code>EntityInterface</code> type.<br />We assume that if there’s a parameter type with <code>entity_revision:*</code> it’s the entity we want. I would guess this is the expected behavior in most use cases.</p>
<p>To override this behavior you would need to create another event subscriber, that subscribes to <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity/blob/main/src/Event/PrimaryEntityEvent.php"><code>PrimaryEntityEvent::GET</code></a> with a higher priority than <em>110</em> which is what the <code>checkRevision()</code> is set to. And simply add more event subscribers to add any custom business logic, you can use the following template (that will return a specific parameter depending on a specific route):</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">primary_entity</span>\<span class="hljs-title">Event</span>\<span class="hljs-title">PrimaryEntityEvent</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">EventDispatcher</span>\<span class="hljs-title">EventSubscriberInterface</span>;

<span class="hljs-comment">/**
 *
 */</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PrimaryEntityEventExample</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">EventSubscriberInterface</span> </span>{

  <span class="hljs-comment">/**
   *
   */</span>
  <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkCustomRoute</span>(<span class="hljs-params">PrimaryEntityEvent $event</span>): <span class="hljs-title">void</span> </span>{
    <span class="hljs-keyword">if</span> ($event-&gt;getRouteMatch()-&gt;getRouteName() === <span class="hljs-string">'some.routename'</span>) {
      $event
        -&gt;setPrimaryEntity($event-&gt;getRouteMatch()-&gt;getParameter(<span class="hljs-string">'entity_parameter_name'</span>))
        -&gt;stopPropagation();
    }
  }

  <span class="hljs-comment">/**
   * {<span class="hljs-doctag">@inheritdoc</span>}
   */</span>
  <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSubscribedEvents</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span> </span>{
    <span class="hljs-keyword">return</span> [
      PrimaryEntityEvent::GET =&gt; <span class="hljs-string">'checkCustomRoute'</span>
    ];
  }

}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Getting the current entity is a trivial problem 95% of the time, but the last 5% needs a dedicated module with custom per project logic to work.</p>
<p>I have a variant of the first <code>currentEntity()</code> function I copy from project to project. It is a bit hackish, but it made sure all my <em>guess current entity</em> logic was in one place, so I (and others) could quickly get an overview.</p>
<p>But after diving a bit deeper into it all I think I’ll include the <a target="_blank" href="https://github.com/BirkAndMe/poc-primary_entity">primary entity module</a> in our base tools, and use it as described here instead.</p>
]]></content:encoded></item><item><title><![CDATA[Drupal: Paging an SQL query]]></title><description><![CDATA[Every time I need to make a pager ind Drupal I need to ask AI or / and do some searching to figure out.I don’t know why I can’t remember how, I just can’t, so this is a boilerplate for me (and anyone else who find it useful) for making paged SQL quer...]]></description><link>https://blog.birk-jensen.dk/drupal-paging-an-sql-query</link><guid isPermaLink="true">https://blog.birk-jensen.dk/drupal-paging-an-sql-query</guid><category><![CDATA[Drupal]]></category><category><![CDATA[PHP]]></category><category><![CDATA[snippets]]></category><category><![CDATA[Pagination]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Wed, 02 Jul 2025 11:35:59 GMT</pubDate><content:encoded><![CDATA[<p>Every time I need to make a pager ind Drupal I need to ask AI or / and do some searching to figure out.<br />I don’t know why I can’t remember how, I just can’t, so this is a boilerplate for me (and anyone else who find it useful) for making paged SQL queries.</p>
<h2 id="heading-snippet">Snippet</h2>
<p>The snippet below builds a simple render array that shows a paged list of all the node IDs:</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">Core</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Query</span>\<span class="hljs-title">PagerSelectExtender</span>;

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

<span class="hljs-comment">/** <span class="hljs-doctag">@var</span> PagerSelectExtender */</span>
$pager_query = \Drupal::database()-&gt;select(<span class="hljs-string">'node'</span>, <span class="hljs-string">'node'</span>)
  <span class="hljs-comment">// The PagerSelectExtender decorates the Select object, and will</span>
  <span class="hljs-comment">// handle the connection between the PagerManager and the Select.</span>
  -&gt;extend(PagerSelectExtender::class);

<span class="hljs-comment">// Manipulate the query so it matches the use case.</span>
$pager_query
  -&gt;fields(<span class="hljs-string">'node'</span>, [<span class="hljs-string">'nid'</span>])
  -&gt;orderBy(<span class="hljs-string">'node.nid'</span>)
  <span class="hljs-comment">// Limit is a PagerSelectExtender function that sets items per page.</span>
  -&gt;limit(<span class="hljs-number">5</span>);

$ids = $pager_query-&gt;execute()-&gt;fetchCol();

<span class="hljs-comment">// Get the pager object holding information about the pager element.</span>
$pager = \Drupal::database()-&gt;getPagerManager()-&gt;getPager($pager_query-&gt;getElement());

$build = [
  <span class="hljs-comment">// Use the pager to get a summary with total items count.</span>
  <span class="hljs-string">'summary'</span> =&gt; [<span class="hljs-string">'#plain_text'</span> =&gt; t(<span class="hljs-string">'Count: @total'</span>, [<span class="hljs-string">'@total'</span> =&gt; $pager-&gt;getTotalItems()]), <span class="hljs-string">'#suffix'</span> =&gt; <span class="hljs-string">'&lt;hr&gt;'</span>],
  <span class="hljs-comment">// Print the content.</span>
  <span class="hljs-string">'content'</span> =&gt; [<span class="hljs-string">'#plain_text'</span> =&gt; implode(<span class="hljs-string">', '</span>, $ids), <span class="hljs-string">'#suffix'</span> =&gt; <span class="hljs-string">'&lt;hr&gt;'</span>],
  <span class="hljs-comment">// Add the pager matching the pager element from the query.</span>
  <span class="hljs-string">'pager'</span> =&gt; [<span class="hljs-string">'#type'</span> =&gt; <span class="hljs-string">'pager'</span>, <span class="hljs-string">'#element'</span> =&gt; $pager_query-&gt;getElement()],
];
</code></pre>
<p>The code should be self explanatory. I’ll quickly recap the inner workings of the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21PagerSelectExtender.php/class/PagerSelectExtender/11.x"><code>PagerSelectExtender</code></a> which extends the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21SelectInterface.php/interface/SelectInterface/11.x"><code>SelectInterface</code></a> (meaning it decorates / proxies the object, see the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21ExtendableInterface.php/interface/ExtendableInterface/11.x"><code>ExtendableInterface</code></a>). It then utilizes the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Pager%21PagerManager.php/class/PagerManager/11.x"><code>PagerManager</code></a> to manage the paging, adding the current page and limit as a range to the <code>Select</code> before it’s executed, see the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21PagerSelectExtender.php/function/PagerSelectExtender%3A%3Aexecute/11.x"><code>PagerSelectExtender::execute()</code></a> function.</p>
<p>The snippet uses the same <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Pager%21Pager.php/class/Pager/11.x"><code>Pager</code></a> object as the query to get the total items, and supplies the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Pager.php/class/Pager/11.x"><code>Pager</code></a> element with the same pager ID (<code>#element</code>) as the query, to allow for multiple pagers on the same page.</p>
<h3 id="heading-when-to-extend-the-select">When to extend the <code>Select</code>?</h3>
<p>Since the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21PagerSelectExtender.php/class/PagerSelectExtender/11.x"><code>PagerSelectExtender</code></a> extends <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21SelectExtender.php/class/SelectExtender/11.x"><code>SelectExtender</code></a>, which implements <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21SelectInterface.php/interface/SelectInterface/11.x"><code>SelectInterface</code></a> it can extend the <code>Select</code> object anytime, and you can easily work with the new <code>PagerSelectExtender</code> object as if it was a regular <code>Select</code>.</p>
<p>I prefer to extend the <code>Select</code> object as part of the first chain, this avoids an intermediate <code>$query</code> variable and gives a clear intention of the query from the start.</p>
<pre><code class="lang-php"><span class="hljs-comment">/** <span class="hljs-doctag">@var</span> PagerSelectExtender */</span>
$pager_query = \Drupal::database()-&gt;select(<span class="hljs-string">'node'</span>, <span class="hljs-string">'node'</span>)
  -&gt;extend(PagerSelectExtender::class);
$pager_query-&gt;fields(<span class="hljs-string">'node'</span>, [<span class="hljs-string">'nid'</span>])-&gt;condition(<span class="hljs-string">'node.type'</span>, <span class="hljs-string">'article'</span>);

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

$query = \Drupal::database()-&gt;select(<span class="hljs-string">'node'</span>, <span class="hljs-string">'node'</span>)
  -&gt;fields(<span class="hljs-string">'node'</span>, [<span class="hljs-string">'nid'</span>])-&gt;condition(<span class="hljs-string">'node.type'</span>, <span class="hljs-string">'article'</span>);
<span class="hljs-comment">/** <span class="hljs-doctag">@var</span> PagerSelectExtender */</span>
$pager_query = $query-&gt;extend(PagerSelectExtender::class);
</code></pre>
<p>You could also reassign the query to it self: <code>$query = $query-&gt;extend(PagerSelectExtender::class);</code>, I’m not sure where I land on variable reassignment in general.<br />But anything is possible, you should follow your code conventions or what you feel is the most maintainable code.</p>
<h3 id="heading-its-a-simple-query-why-break-the-chain">It’s a simple query, why break the chain?</h3>
<p>I made a thing about <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21PagerSelectExtender.php/class/PagerSelectExtender/11.x"><code>PagerSelectExtender</code></a> implementing the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21SelectInterface.php/interface/SelectInterface/11.x"><code>SelectInterface</code></a>, so why not simply chain the whole query like so:</p>
<pre><code class="lang-php"><span class="hljs-comment">/** <span class="hljs-doctag">@var</span> PagerSelectExtender */</span>
$query = \Drupal::database()-&gt;select(<span class="hljs-string">'node'</span>, <span class="hljs-string">'node'</span>)
  -&gt;extend(PagerSelectExtender::class)
  -&gt;fields(<span class="hljs-string">'node'</span>, [<span class="hljs-string">'nid'</span>])
  -&gt;orderBy(<span class="hljs-string">'node.nid'</span>)
  -&gt;limit(<span class="hljs-number">5</span>);
</code></pre>
<p>This being a boilerplate I want it to be easy to build on when I copy paste it into something. Some of the functions in the <code>SelectInterface</code> are not being chainable (join functions come to mind), I like to have the space to quickly add these functions without having to split up the chain - It’s a small thing I know, but the easier something is to use the more I use it.</p>
<p>But more importantly my VSCode wont let me autocomplete anything beyond <code>extend()</code> and static code analysis wont know about the functions either.<br />This is because you can’t type hint inside a chain. So after the <code>extend(PagerSelectExtender::class)</code> function, the hinted type will be <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21ExtendableInterface.php/interface/ExtendableInterface/11.x"><code>ExtendableInterface</code></a> which is what the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21ExtendableInterface.php/function/ExtendableInterface%3A%3Aextend/11.x"><code>ExtendableInterface::extend()</code></a> function returns. Meanwhile the actual type would be the <code>PagerSelectExtender</code>(99.9% of the time anyway, it can change depending on the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21Select.php/function/Select%3A%3Aextend/11.x"><code>Select</code></a> class implementation and <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Connection.php/function/Connection%3A%3AgetDriverClass/11.x">driver class overrides</a>).</p>
<h2 id="heading-what-about-drupalentityqueryhttpsapidrupalorgapidrupalcore21lib21drupalphpfunctiondrupal3a3aentityquery11x">What about <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal.php/function/Drupal%3A%3AentityQuery/11.x"><code>Drupal::entityQuery()</code></a>?</h2>
<p>The <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21Query%21QueryInterface.php/interface/QueryInterface/11.x"><code>QueryInterface</code></a> (which is what <code>\Drupal::entityQuery()</code> returns) has a <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21Query%21QueryInterface.php/function/QueryInterface%3A%3Apager/11.x"><code>pager($limit = 10, $element = NULL)</code></a>, function so you’d think it would be easy, and it is, as long as you don’t need to support multiple pagers on the same page. Multiple pagers on the same page is a rare occurrence, but you’ll never know if there’s something else on the page that uses pagers.<br />Now in the case you’re sure nothing else will ever happen on a page (this is usually one-off admin pages for me) let me start with the simplest implementation:</p>
<h3 id="heading-without-multiple-pagers">Without multiple pagers</h3>
<pre><code class="lang-php">$ids = \Drupal::entityQuery(<span class="hljs-string">'node'</span>)
  -&gt;accessCheck(<span class="hljs-literal">FALSE</span>)
  -&gt;pager(<span class="hljs-number">5</span>)-&gt;execute();

$build = [
  <span class="hljs-comment">// Print the content.</span>
  <span class="hljs-string">'content'</span> =&gt; [<span class="hljs-string">'#plain_text'</span> =&gt; implode(<span class="hljs-string">', '</span>, $ids), <span class="hljs-string">'#suffix'</span> =&gt; <span class="hljs-string">'&lt;hr&gt;'</span>],
  <span class="hljs-comment">// Add the pager matching the pager element from the query.</span>
  <span class="hljs-string">'pager'</span> =&gt; [<span class="hljs-string">'#type'</span> =&gt; <span class="hljs-string">'pager'</span>],
];
</code></pre>
<h3 id="heading-with-multiple-pagers">With multiple pagers</h3>
<pre><code class="lang-php"><span class="hljs-comment">/** <span class="hljs-doctag">@var</span> Drupal\Core\Pager\PagerManagerInterface */</span>
$pager_manager = \Drupal::service(<span class="hljs-string">'pager.manager'</span>);

<span class="hljs-comment">// Get the next available pager ID</span>
$pager_id = $pager_manager-&gt;getMaxPagerElementId() + <span class="hljs-number">1</span>;

$ids = \Drupal::entityQuery(<span class="hljs-string">'node'</span>)
  -&gt;accessCheck(<span class="hljs-literal">FALSE</span>)
  <span class="hljs-comment">// Supply the pager function with the next pager_id</span>
  -&gt;pager(<span class="hljs-number">2</span>, $pager_id)
  -&gt;execute();

<span class="hljs-comment">// </span>
$pager = $pager_manager-&gt;getPager($pager_id);

$variables[<span class="hljs-string">'page'</span>][<span class="hljs-string">'content'</span>][] = [
  <span class="hljs-string">'summary'</span> =&gt; [<span class="hljs-string">'#plain_text'</span> =&gt; t(<span class="hljs-string">'Count: @total'</span>, [<span class="hljs-string">'@total'</span> =&gt; $pager-&gt;getTotalItems()]), <span class="hljs-string">'#suffix'</span> =&gt; <span class="hljs-string">'&lt;hr&gt;'</span>],
  <span class="hljs-string">'content'</span> =&gt; [<span class="hljs-string">'#plain_text'</span> =&gt; implode(<span class="hljs-string">', '</span>, $ids), <span class="hljs-string">'#suffix'</span> =&gt; <span class="hljs-string">'&lt;hr&gt;'</span>],
  <span class="hljs-string">'pager'</span> =&gt; [<span class="hljs-string">'#type'</span> =&gt; <span class="hljs-string">'pager'</span>],
];
</code></pre>
<p>Much of the code is the same as the very first snippet, so there’s not much to go over.</p>
<p>One <s>tiny</s> pedantic thing to notice is that in this example I use <a target="_blank" href="https://api.drupal.org/api/drupal/core%21core.services.yml/service/pager.manager/10"><code>\Drupal::service('pager.manager')</code></a> instead of <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Connection.php/function/Connection%3A%3AgetPagerManager/10"><code>\Drupal::database()-&gt;getPagerManager()</code></a> to get the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Pager%21PagerManagerInterface.php/interface/PagerManagerInterface/10"><code>PagerManager</code></a>. Both these functions end up getting the exact same <code>PageManager</code> instance, the <code>getPagerManager()</code> function calls <code>\Drupal::service('pager.manager')</code>.<br />The reason for using <code>::service</code> with when working with <code>entityQuery</code> is the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21Query%21QueryBase.php/function/QueryBase%3A%3Apager/10"><code>QueryBase::pager</code></a> function calls the <code>::service</code>, while the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21PagerSelectExtender.php/class/PagerSelectExtender/10"><code>PagerSelectExtender</code></a> uses the <code>getPagerManager()</code> in its <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Query%21PagerSelectExtender.php/function/PagerSelectExtender%3A%3Aexecute/10"><code>execute()</code></a> function.<br />This is all a big fuss over nothing, but in theory the <code>getPagerManager()</code> function on the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Connection.php/class/Connection/10"><code>Connection</code></a> class could use something else.<br />With all that being said, use whatever way of getting the <code>PagerManager</code> you’re comfortable with.</p>
<h2 id="heading-the-end">The end</h2>
<p>It seems like a lot of talk about a very simple solution, but sometimes it’s fun to dive into the little things, hope you enjoyed.</p>
]]></content:encoded></item><item><title><![CDATA[Hashnode tag filtered RSS]]></title><description><![CDATA[If you just want the code, here it is: https://github.com/BirkAndMe/hashnoderss.It should be straight forward, check the index.php file to get the gist of things.
Intro
A while back Hashnode released a new version of their public API, and one of the ...]]></description><link>https://blog.birk-jensen.dk/hashnode-tag-filtered-rss</link><guid isPermaLink="true">https://blog.birk-jensen.dk/hashnode-tag-filtered-rss</guid><category><![CDATA[Hashnode]]></category><category><![CDATA[rss]]></category><category><![CDATA[PHP]]></category><category><![CDATA[aggregation]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Mon, 09 Dec 2024 14:49:33 GMT</pubDate><content:encoded><![CDATA[<p>If you just want the code, here it is: <a target="_blank" href="https://github.com/BirkAndMe/hashnoderss">https://github.com/BirkAndMe/hashnoderss</a>.<br />It should be straight forward, check the <a target="_blank" href="https://github.com/BirkAndMe/hashnoderss/blob/main/index.php"><code>index.php</code></a> file to get the gist of things.</p>
<h2 id="heading-intro">Intro</h2>
<p>A while back Hashnode released a new version of their <a target="_blank" href="https://apidocs.hashnode.com/">public API</a>, and one of the great things about this, is the ability to filter posts by tags. This will allow you to easily make an RSS feed that only show posts with a given tag.<br />This is important if you write about multiple topics, but want specific posts to appear in a <a target="_blank" href="https://en.wikipedia.org/wiki/Planet_\(software\)">feed aggregator</a>.</p>
<p>I’ve <a target="_blank" href="https://blog.birk-jensen.dk/hashnoderss">previously written</a> about this, but that code outdated with the new API. I’ve made the code simpler and less abstract, so it’s easier to use.</p>
<h2 id="heading-quick-code-breakdown">Quick code breakdown</h2>
<p>The code is intentionally kept simple, with 2 core steps in a single file:</p>
<ul>
<li><p>Fetch the code from the Hashnode API.</p>
<ul>
<li><p>Using the <a target="_blank" href="https://symfony.com/doc/current/components/cache.html">Symfony Cache</a> (<code>symfony/cache</code>) to cache the request, so the script don’t swarm the API.</p>
</li>
<li><p>And <a target="_blank" href="https://docs.guzzlephp.org/">Guzzle</a> (<code>guzzlehttp/guzzle</code>) to call the API.</p>
</li>
</ul>
</li>
<li><p>Restructure it into RSS, using <a target="_blank" href="https://github.com/mibe/FeedWriter">FeedWriter</a> (<code>mibe/feedwriter</code>) to easily get a valid feed.</p>
</li>
</ul>
<h3 id="heading-code-notes">Code notes</h3>
<ul>
<li><p>I set a <a target="_blank" href="https://github.com/BirkAndMe/hashnoderss/blob/50276c52d5b279307008b6c718b5a64372211931/settings.php#L14"><code>$scheme</code> variable in the <code>settings.php</code></a>, the <code>$_SERVER[‘REQUEST_SCHEME’]</code> variable is server dependent, so set this to <em>https</em> (or <em>http</em>) to match your configuration.<br />  The <code>$scheme</code> is used to set the atom link.</p>
</li>
<li><p>I’m not using any GraphQL library abstraction, so I’ve simply <a target="_blank" href="https://github.com/BirkAndMe/hashnoderss/blob/50276c52d5b279307008b6c718b5a64372211931/index.php#L40">pasted the entire query</a> into the code as clean text.<br />  And to get tags working, I’ve <a target="_blank" href="https://github.com/BirkAndMe/hashnoderss/blob/50276c52d5b279307008b6c718b5a64372211931/index.php#L44">injected them directly into the query</a> (I did not escape anything, because it’s all just calling the public Hashnode API anyway).</p>
</li>
</ul>
<h2 id="heading-outro">Outro</h2>
<p>I’m not a fan of GQL, but I’ve worked with much worse query languages, and it’s a matter of taste. However the Hashnode API is a breeze to use, and it allows for the exact filtering features I’ve missed previously.</p>
<p>As an added bonus the FeedWriter will (unlike the normal Hashnode RSS) produce RSS that validates without reccomendations.</p>
]]></content:encoded></item><item><title><![CDATA[Documenting a callback function in JSDoc]]></title><description><![CDATA[A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.
-- MDN: Callback function

Callbacks are used all over in JavaScript, a good ex...]]></description><link>https://blog.birk-jensen.dk/documenting-a-callback-function-in-jsdoc</link><guid isPermaLink="true">https://blog.birk-jensen.dk/documenting-a-callback-function-in-jsdoc</guid><category><![CDATA[jsdoc]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[VS Code]]></category><category><![CDATA[documentation]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Fri, 12 Jan 2024 13:48:16 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>A <strong>callback function</strong> is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.</p>
<p>-- <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Callback_function">MDN: Callback function</a></p>
</blockquote>
<p>Callbacks are used all over in JavaScript, a good example is the <a target="_blank" href="https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.some"><code>Array.prototype.some(callbackFn[, thisArg])</code></a> function:</p>
<pre><code class="lang-javascript">[<span class="hljs-number">1</span>, <span class="hljs-number">2</span>].some(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">kValue, k, O</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Key <span class="hljs-subst">${k}</span> = <span class="hljs-subst">${kValue}</span> in [<span class="hljs-subst">${O.join(<span class="hljs-string">','</span>)}</span>]`</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
});
<span class="hljs-comment">// Outputs:</span>
<span class="hljs-comment">// Key 0 = 1 in [1,2]</span>
</code></pre>
<p>This callback function has multiple arguments and a return value, <a target="_blank" href="https://code.visualstudio.com/">VSCode</a> does a great job of documenting the function via the IntelliSense tooltip:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702648931488/b0296534-508f-47af-990b-41bb3648af1d.webp" alt class="image--center mx-auto" /></p>
<p>MDN also has this information covered in the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some#parameters">Parameters section</a> of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some">Array.prototype.some()</a> information page.</p>
<p>The endgame is to achieve the same documentation on custom functions as native functions. Meaning I would like to get tooltip information inside my IDE (VSCode) and <a target="_blank" href="https://jsdoc.app/">JSDoc</a> output to document callback functions.</p>
<p>JSDoc output isn't always important, but if you're using it to create documentation, for a library or something else, it needs to be able to sufficiently document a callback.</p>
<h2 id="heading-the-setup">The setup</h2>
<p>So the very simple setup looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mathFn</span>(<span class="hljs-params">in1, in2, callbackFn</span>) </span>{
  <span class="hljs-keyword">return</span> callbackFn(in1, in2);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sumCallback</span>(<span class="hljs-params">in1, in2</span>) </span>{
  <span class="hljs-keyword">return</span> in1 + in2;
}

mathFn(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, sumCallback); <span class="hljs-comment">// Returns 3.</span>
</code></pre>
<p>The <code>mathFn</code> (<em>outer function</em>) takes 2 numbers and a callback. In this example the <code>sumCallback</code> (<em>callback function</em>) is a simple function that sums the 2 numbers.</p>
<h2 id="heading-take-1-letting-vscode-document-the-functions">Take 1: Letting VSCode document the functions</h2>
<p>Using VSCode's <em>[Quick Fix - Infer parameter types from usage]</em> feature to add docblocks to the 2 functions:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in1</span></span>
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in2</span></span>
 * <span class="hljs-doctag">@param <span class="hljs-type">{{ (in1: any, in2: any): any; (arg0: any, arg1: any): any; }</span></span>} callbackFn
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mathFn</span>(<span class="hljs-params">in1, in2, callbackFn</span>) </span>{
  <span class="hljs-keyword">return</span> callbackFn(in1, in2);
}

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{any}</span> <span class="hljs-variable">in1</span></span>
 * <span class="hljs-doctag">@param <span class="hljs-type">{any}</span> <span class="hljs-variable">in2</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sumCallback</span>(<span class="hljs-params">in1, in2</span>) </span>{
  <span class="hljs-keyword">return</span> in1 + in2;
}
</code></pre>
<p>Which provided a nice boilerplate, I've modified the output a bit, added return value and some descriptions, so the final result looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in1</span></span>
 *   First number
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in2</span></span>
 *   Second number
 * <span class="hljs-doctag">@param <span class="hljs-type">{{ (in1: number, in2: number): number }</span></span>} callbackFn
 *   Computation callback
 * <span class="hljs-doctag">@returns <span class="hljs-type">{number}</span></span>
 *   The result
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mathFn</span>(<span class="hljs-params">in1, in2, callbackFn</span>) </span>{
  <span class="hljs-keyword">return</span> callbackFn(in1, in2);
}

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in1</span></span>
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in2</span></span>
 * <span class="hljs-doctag">@returns <span class="hljs-type">{number}</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sumCallback</span>(<span class="hljs-params">in1, in2</span>) </span>{
  <span class="hljs-keyword">return</span> in1 + in2;
}
</code></pre>
<h3 id="heading-result">Result</h3>
<p>This gives the perfect tooltip, with every type hinting inside the callback documented. It even suggests parameter naming of the callback function:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703789305310/696e6294-2932-4b90-acfe-fbfeba1cecd2.png" alt class="image--center mx-auto" /></p>
<p>Exactly what I was looking for, except JSDoc wont parse this, returning with the following error message:<br /><code>ERROR: Unable to parse a tag's type expression for source file</code></p>
<p>So if I didn't need JSDoc to create documentation this would be my go to method. But it would be nice to have JSDoc working, so the quest continues.</p>
<h2 id="heading-take-2-documenting-the-callback-inline">Take 2: Documenting the callback inline</h2>
<p>JSDoc supports a <code>function</code> type, so I've changed the type definition to look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in1</span></span>
 *   First number
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in2</span></span>
 *   Second number
 * <span class="hljs-doctag">@param <span class="hljs-type">{ function(number, number): number }</span> <span class="hljs-variable">callbackFn</span></span>
 *   Computation callback
 * <span class="hljs-doctag">@return <span class="hljs-type">{number}</span></span>
 *   The result
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mathFn</span>(<span class="hljs-params">in1, in2, callbackFn</span>) </span>{
  <span class="hljs-keyword">return</span> callbackFn(in1, in2);
}
</code></pre>
<h3 id="heading-result-1">Result</h3>
<p>VSCode still does a good tooltip job. Note the callback arguments are no longer named, but simply called <code>arg0</code> and <code>arg1</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704199190634/c08d6c6c-031a-4bbd-9dd4-16f5845a6d2c.png" alt class="image--center mx-auto" /></p>
<p>Having the named arguments would be great, it gives a great hint at what to expect from the arguments in a callback. Unfortunately it's not supported, so the experience is a bit worse than take 1, but JSDoc parses everything and produces an output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704200001747/c0e8a3eb-9fbd-47c0-8025-4be1b45686a1.png" alt class="image--center mx-auto" /></p>
<p>So far so good. Problem is the callback is not documented, it simply states it's a function, but we got no information about the arguments. Looking through the JSDoc issues I'm not the only one encountering this problem: <a target="_blank" href="https://github.com/jsdoc/jsdoc/issues/260#issuecomment-13364061">Ability to document callback</a>.</p>
<p>The reason for this, is that JSDoc did not parse the <code>@param</code> type to anything other than a function, using the <code>--explain</code> option we can see JSDoc data structure, in this case the important part of the explain dump is this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"type"</span>: {
        <span class="hljs-attr">"names"</span>: [
            <span class="hljs-string">"function"</span>
        ]
    },
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"&lt;p&gt;Computation callback&lt;/p&gt;"</span>,
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"callbackFn"</span>
}
</code></pre>
<h2 id="heading-take-25-inline-with-added-description">Take 2.5: Inline with added description</h2>
<p>It's possible to add information about the callback arguments as simple text in the parameter description:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in1</span></span>
 *   First number
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in2</span></span>
 *   Second number
 * <span class="hljs-doctag">@param <span class="hljs-type">{ function(number, number): number }</span> <span class="hljs-variable">callbackFn</span></span>
 *   Computation callback
 *
 *   **Callback arguments**
 *   1. in1: *number* \
 *   First number
 *   2. in2: *number* \
 *   Second number
 *
 * <span class="hljs-doctag">@return <span class="hljs-type">{number}</span></span>
 *   The result
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mathFn</span>(<span class="hljs-params">in1, in2, callbackFn</span>) </span>{
  <span class="hljs-keyword">return</span> callbackFn(in1, in2);
}
</code></pre>
<h3 id="heading-result-2">Result</h3>
<p>The tooltip:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704285353657/92ccb95b-96c7-47bb-94c0-41917542df51.png" alt class="image--center mx-auto" /></p>
<p>And the JSDoc output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704285434554/b413c02e-c04a-4b12-9556-e44e5afd36cf.png" alt class="image--center mx-auto" /></p>
<p>Both JSDoc and VSCode support markdown, so this extra description can be formatted pretty much in anyway you like. The problem with this is, you can format it anyway you like, so it's up to the developer to always do this the same way to keep consistency in the output. It also makes it impossible to change layout of the argument documentation later on by changing the JSDoc theme.</p>
<h2 id="heading-take-3-defining-a-callback">Take 3: Defining a callback</h2>
<p>Using the <a target="_blank" href="https://jsdoc.app/tags-callback">@callback</a> tag to define a callback, and using this defined type in the function docblock:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Callback short description
 *
 * Long description
 *
 * <span class="hljs-doctag">@callback <span class="hljs-variable">callbackFn</span></span>
 *
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in1</span></span>
 *   The first number in the computation
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in2</span></span>
 *   Second number in the computation
 * <span class="hljs-doctag">@returns <span class="hljs-type">{number}</span></span>
 *   Result
 */</span>

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in1</span></span>
 *   First number
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in2</span></span>
 *   Second number
 * <span class="hljs-doctag">@param <span class="hljs-type">{callbackFn}</span> <span class="hljs-variable">callbackFn</span></span>
 *   Computation callback
 * <span class="hljs-doctag">@return <span class="hljs-type">{number}</span></span>
 *   The result
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mathFn</span>(<span class="hljs-params">in1, in2, callbackFn</span>) </span>{
  <span class="hljs-keyword">return</span> callbackFn(in1, in2);
}
</code></pre>
<h3 id="heading-result-3">Result</h3>
<p>Unfortunately this broke the initial tooltip:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704828977118/5890f7c7-5778-44c5-aef5-68e498170f94.png" alt class="image--center mx-auto" /></p>
<p>VSCode doesn't expand the definition, instead it just says the <code>callbackFn</code> parameter is of the <code>callbackFn</code> type.<br />However when you start typing out an anonymous function inside the <code>mathFn</code> function call VSCode will jump in and tell you the parameters, including parameter namings of the callback:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704829013411/6cf9d6ae-ea8d-456e-9336-122f36793e1e.png" alt class="image--center mx-auto" /></p>
<p>So it's not all bad, it could be better.<br />But the JSDoc output finally provided information about the parameters (and return value) of the callback function. Although it's "hidden" behind a link to the <code>callbackFn</code> type (much like VSCode) it's still a great improvement. So the parameter list looks like this now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705004241591/10e691fd-5736-4e75-ba40-13fe84225cf4.png" alt class="image--center mx-auto" /></p>
<p>And the <code>callbackFn</code> definition looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705004378470/fbd5d589-3d29-4035-a741-ace22845f4aa.png" alt class="image--center mx-auto" /></p>
<p>Creating a callback definition also allows you to reuse the same callback documentation for multiple places.<br />Agreed that most of the time a callback is tied to the <em>outer function</em>, but it's a tiny plus for when this is not the case.</p>
<h2 id="heading-type-checking">Type checking</h2>
<p>VSCode also allows <a target="_blank" href="https://code.visualstudio.com/docs/nodejs/working-with-javascript#_type-checking-javascript">type checking Javascript</a>, and thankfully all the above methods works with this, so the IDE can give you warnings, I've added an <code>invalidCallback()</code> function that takes a <code>string</code> as the first argument instead of a <code>number</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{string}</span> <span class="hljs-variable">in1</span></span>
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> <span class="hljs-variable">in2</span></span>
 * <span class="hljs-doctag">@returns <span class="hljs-type">{number}</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">invalidCallback</span>(<span class="hljs-params">in1, in2</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>VSCode will highlights this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705066187997/421c4b8a-bcf4-4abc-8824-5f77cc90657d.png" alt class="image--center mx-auto" /></p>
<p>In what feels like serendipity, I just read a recent post by Thomas Portelange, that gives an introduction to this exact feature: <a target="_blank" href="https://blog.lekoala.be/typescript-or-jsdocs">Typescript... or jsdocs ?</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Unfortunately I haven't found the perfect solution for documenting a callback in both VSCode and JSDoc.</p>
<p>The 3 methods I've tested here all have pros and cons, so your use case will determine what kind method you'll choose. I would go with the following:</p>
<ul>
<li><p><strong>Take 1</strong>: If you do not need JSDoc go with this. I think getting a naming suggestion for your parameters is a huge plus, and a great named parameter will go a long way in understanding what parameters are available in a callback.</p>
</li>
<li><p><strong>Take 2</strong>: If you don't mind writing a bit of extra boilerplate in the comment.<br />  When working in VSCode you'll miss out on the parameter naming, but an added description to the parameter can make up for this (given it's subjectively harder to read).<br />  The good thing about adding the description is it'll enrich the JSDoc presentation as well.</p>
</li>
<li><p><strong>Take 3</strong>: Would be my goto for writing libraries that needed a strong documentation outside of the IDE.<br />  Even though it doesn't add parameter naming in the first tooltip, it does when adding an anonymous function, and adding a TypeScript declaration file (<em>.d.ts</em>) could make up for it inside VSCode (although there's extra work involved).</p>
</li>
</ul>
<h3 id="heading-future-improvements">Future improvements</h3>
<p>My favorite approach is the first (<em>Take 1</em>), it's the easiest to write and gives excellent tooltips. But looking at the JSDoc source code I'm not sure it's possible to get it working without hacking (or forking) it.</p>
<p>I'm still deep diving into the JSDoc tool trying to figure out better ways for documenting a callback.</p>
]]></content:encoded></item><item><title><![CDATA[Drupal: HTTP redirection from anywhere]]></title><description><![CDATA[The drupal_goto() function was removed in Drupal 8, and this is all good!In the change record there's an example, of how to do a redirection outside the context of a controller:

Redirecting when not in context of a controller
  $response = new Redir...]]></description><link>https://blog.birk-jensen.dk/drupal-http-redirection-from-anywhere</link><guid isPermaLink="true">https://blog.birk-jensen.dk/drupal-http-redirection-from-anywhere</guid><category><![CDATA[Drupal]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Tue, 05 Sep 2023 15:25:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ocqT7E3QJlk/upload/6ca53d2a93a79aada956f7a15eadb7cd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <a target="_blank" href="https://www.drupal.org/node/2023537">drupal_goto() function was removed</a> in Drupal 8, and this is all good!<br />In the change record there's an example, of how to do a redirection outside the context of a controller:</p>
<blockquote>
<p><strong>Redirecting when not in context of a controller</strong></p>
<pre><code class="lang-php">  $response = <span class="hljs-keyword">new</span> RedirectResponse($url-&gt;toString());
  $request = \Drupal::request();
  <span class="hljs-comment">// Save the session so things like messages get saved.</span>
  $request-&gt;getSession()-&gt;save();
  $response-&gt;prepare($request);
  <span class="hljs-comment">// Make sure to trigger kernel events.</span>
  \Drupal::service(<span class="hljs-string">'kernel'</span>)-&gt;terminate($request, $response);
  $response-&gt;send();
</code></pre>
</blockquote>
<p>This does the job (almost), and one could simply wrap this in a new <code>drupal_goto()</code> function and call it a day.</p>
<p>But once caching is turned on this stops working as expected.</p>
<h2 id="heading-testing-the-caching-issue">Testing the caching issue</h2>
<p>To show the issue, I've added a redirect inside the <code>THEME_preprocess_html()</code>:</p>
<pre><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mytheme_preprocess_html</span>(<span class="hljs-params">&amp;$variables</span>) </span>{
  <span class="hljs-comment">// Redirect when there's a "redirect-test" query parameter</span>
  <span class="hljs-keyword">if</span> (\Drupal::request()-&gt;query-&gt;has(<span class="hljs-string">'redirect-test'</span>)) {
    <span class="hljs-comment">// Prepare the goto Url.</span>
    $url = Url::fromUserInput(<span class="hljs-string">'/'</span>)
      -&gt;setAbsolute()
      -&gt;toString();

    <span class="hljs-comment">// Create the response.</span>
    $response = <span class="hljs-keyword">new</span> TrustedRedirectResponse($url);
    <span class="hljs-comment">// And add the url.query_args as the cache context.</span>
    $response-&gt;getCacheableMetadata()-&gt;addCacheContexts([<span class="hljs-string">'url.query_args'</span>]);

    <span class="hljs-comment">// Redirect code from https://www.drupal.org/node/2023537</span>
    $request = \Drupal::request();
    $request-&gt;getSession()-&gt;save();
    $response-&gt;prepare($request);
    \Drupal::service(<span class="hljs-string">'kernel'</span>)-&gt;terminate($request, $response);
    $response-&gt;send();
  }
}
</code></pre>
<p>Adding a <code>?redirect-test</code> query string to the URL, will redirect the user to the frontpage (<code>/</code>).<br />This works the first time around, but the second time it simply shows the page and doesn't send any redirection.</p>
<p>Let's have a look at the headers (I've removed most of the headers, and only kept the ones relevant for this example):</p>
<pre><code class="lang-plaintext">$ curl -D /dev/stdout -o /dev/null -s https://example.com/node/2?redirect-test
HTTP/1.1 302 Found
Cache-Control: no-cache, private
Location: https://example.com/
</code></pre>
<p>This first request sends the user the <code>/</code> location.</p>
<pre><code class="lang-plaintext">$ curl -D /dev/stdout -o /dev/null -s https://example.com/node/2?redirect-test
HTTP/1.1 200 OK
Cache-Control: max-age=300, public
X-Drupal-Cache: HIT
</code></pre>
<p>The second request however shows the requested node (note the <code>X-Drupal-Cache: HIT</code>) and doesn't set the <code>location</code>.</p>
<p>On the second request the <code>mytheme_preprocess_html</code> is never reached (because the page is cached), and it never sends the redirect response.</p>
<h2 id="heading-fixing-redirection-and-caching">Fixing redirection and caching</h2>
<p>So the idea is to throw an <code>exception</code>, this exception is handled via an <a target="_blank" href="https://www.drupal.org/docs/develop/creating-modules/subscribe-to-and-dispatch-events#s-drupal-8-events"><code>event_subscriber</code></a> and it will change the response.</p>
<h3 id="heading-the-implementation">The implementation</h3>
<p><a target="_blank" href="https://gist.github.com/BirkAndMe/7422a58d33f1e7c199774c5cc54873e3">I've compiled the files in a gist for a quick overview.</a></p>
<p>Create a new exception and an event subscriber, In any module (we've implemented the functionality in our agency general purpose module, but it can be anywhere).<br />In the example implementation below the module is called <code>drupalgoto</code>.</p>
<p>The <strong>exception</strong> is simple, the important thing is it needs a <a target="_blank" href="https://github.com/symfony/symfony/blob/4.0/src/Symfony/Component/HttpFoundation/Response.php"><code>response</code></a>, which is what the event subscriber will send to the client:</p>
<pre><code class="lang-php"><span class="hljs-keyword">namespace</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">drupalgoto</span>\<span class="hljs-title">EventSubscriber</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpFoundation</span>\<span class="hljs-title">Response</span>;

<span class="hljs-comment">/**
 * Override response exception.
 */</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OverrideResponseException</span> <span class="hljs-keyword">extends</span> \<span class="hljs-title">RuntimeException</span> </span>{

  <span class="hljs-comment">/**
   *
   */</span>
  <span class="hljs-keyword">protected</span> $response;

  <span class="hljs-comment">/**
   * Construct instance.
   */</span>
  <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">Response $response = <span class="hljs-literal">null</span>, ?<span class="hljs-keyword">string</span> $message = <span class="hljs-string">''</span>, \<span class="hljs-built_in">Throwable</span> $previous = <span class="hljs-literal">null</span>, <span class="hljs-keyword">int</span> $code = <span class="hljs-number">0</span></span>) </span>{
    <span class="hljs-keyword">$this</span>-&gt;response = $response;
    <span class="hljs-built_in">parent</span>::__construct($message, $code, $previous);
  }

  <span class="hljs-comment">/**
   * Get response.
   */</span>
  <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getResponse</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;response;
  }

}
</code></pre>
<p>Now the <strong>event subscriber</strong> should listen for this type of exception, and set the response to the response set in the exception:</p>
<pre><code class="lang-php"><span class="hljs-keyword">namespace</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">drupalgoto</span>\<span class="hljs-title">EventSubscriber</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">drupalgoto</span>\<span class="hljs-title">Exception</span>\<span class="hljs-title">OverrideResponseException</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">EventDispatcher</span>\<span class="hljs-title">EventSubscriberInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">Event</span>\<span class="hljs-title">ExceptionEvent</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">KernelEvents</span>;

<span class="hljs-comment">/**
 * Event subscriber.
 */</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EventSubscriber</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">EventSubscriberInterface</span> </span>{

  <span class="hljs-comment">/**
   * Exception event.
   */</span>
  <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onKernelException</span>(<span class="hljs-params">ExceptionEvent $event</span>) </span>{
    $throwable = $event-&gt;getThrowable();

    <span class="hljs-comment">// Check if it's the OverrideResponseException.</span>
    <span class="hljs-keyword">if</span> ($throwable <span class="hljs-keyword">instanceof</span> OverrideResponseException) {
      <span class="hljs-comment">// And set the response if it is.</span>
      $event-&gt;setResponse($throwable-&gt;getResponse());
    }
  }

  <span class="hljs-comment">/**
   * {<span class="hljs-doctag">@inheritdoc</span>}
   */</span>
  <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSubscribedEvents</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> [
      KernelEvents::EXCEPTION =&gt; [<span class="hljs-string">'onKernelException'</span>],
    ];
  }

}
</code></pre>
<p>The magic is inside the <code>onKernelException()</code> function, here it tests for the exception and then sets the response accordingly.<br />Remember to register the event subscriber in the <code>drupalgoto.services.yml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">ervices:</span>
  <span class="hljs-attr">drupalgoto.event_subscriber:</span>
    <span class="hljs-attr">class:</span> <span class="hljs-string">Drupal\drupalgoto\EventSubscriber\EventSubscriber</span>
    <span class="hljs-attr">tags:</span>
      <span class="hljs-bullet">-</span> { <span class="hljs-attr">name:</span> <span class="hljs-string">event_subscriber</span> }
</code></pre>
<p>That's it. It's a lot of boilerplate and extra code, but now you're ready to throw the <code>OverrideResponseException</code> and redirect the user.</p>
<h3 id="heading-using-the-implementation">Using the implementation</h3>
<p>Changing the previous test case to use the new exception throwing variant:</p>
<pre><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mytheme_preprocess_html</span>(<span class="hljs-params">&amp;$variables</span>) </span>{
  <span class="hljs-comment">// Redirect when there's a "redirect-test" query parameter</span>
  <span class="hljs-keyword">if</span> (\Drupal::request()-&gt;query-&gt;has(<span class="hljs-string">'redirect-test'</span>)) {
    <span class="hljs-comment">// Prepare the goto Url.</span>
    $url = Url::fromUserInput(<span class="hljs-string">'/'</span>)
      -&gt;setAbsolute()
      -&gt;toString();

    <span class="hljs-comment">// Create the response.</span>
    $response = <span class="hljs-keyword">new</span> TrustedRedirectResponse($url);
    <span class="hljs-comment">// And add the url.query_args as the cache context.</span>
    $response-&gt;getCacheableMetadata()-&gt;addCacheContexts([<span class="hljs-string">'url.query_args'</span>]);

    <span class="hljs-comment">// Throw the override exception:</span>
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> OverrideResponseException($redirect);
  }
}
</code></pre>
<p>It's the same as the last test, except this throws the <code>OverrideResponseException</code>, instead of the approach from <a target="_blank" href="https://www.drupal.org/node/2023537">https://www.drupal.org/node/2023537</a>.</p>
<h3 id="heading-is-it-working">Is it working?</h3>
<p>Let's run the previous test from the terminal (I've stripped a lot of unimportant headers from the output again):</p>
<pre><code class="lang-plaintext">$ curl -D /dev/stdout -o /dev/null -s https://example.com/node/2?redirect-test
HTTP/1.1 302 Found
Cache-Control: no-cache, private
Location: https://example.com/
</code></pre>
<p>The first run works just the same as with the old method.</p>
<pre><code class="lang-plaintext">$ curl -D /dev/stdout -o /dev/null -s https://example.com/node/2?redirect-test
HTTP/1.1 200 OK
Cache-Control: max-age=300, public
X-Drupal-Cache: HIT
Location: https://example.com/
</code></pre>
<p>On the second call, notice the <code>Location</code>, so this time the redirect is cached and executed correctly.</p>
<p>Setting <code>http.response.debug_cacheability_headers</code> to <code>TRUE</code> on the Drupal installation will <a target="_blank" href="https://www.drupal.org/docs/8/api/responses/cacheableresponseinterface#debugging">show caching information in the headers</a>.<br />Doing this adds the <code>X-Drupal-Cache-Contexts: url.query_args user.permissions</code> header to both the first and the second call, telling us the <code>url.query_args</code> cache context is added as expected.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Even though Drupal 7 was a long time ago, I'll make the comparison because I've previously mentioned the <code>drupal_goto()</code> function.<br />So like anything in Drupal 8+ it's a lot more boilerplate and extra code compared to Drupal 7. But the extra code allows for an excellent caching system, so it's worth it!</p>
<p>And once you've implemented the override response system once (in its own module or a base module if you have one) it's always there ready to use.</p>
<h3 id="heading-icing-on-the-cake">Icing on the cake</h3>
<p>We've implemented a wrapper function for even easier HTTP redirects, this makes it a simple one-liner with no need to reference the <code>TrustedRedirectResponse</code> class:</p>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">goto</span>(<span class="hljs-params">Url $url, $cache = []</span>) </span>{
  $response = TrustedRedirectResponse::create($url-&gt;setAbsolute()-&gt;toString())
    -&gt;addCacheableDependency(CacheableMetadata::createFromRenderArray([<span class="hljs-string">'#cache'</span> =&gt; $cache]));

  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">static</span>($response);
}
</code></pre>
<p>Notice this makes sure the URL is absolute, and calls <code>throw</code>. It also wraps the cache allowing for a simple array setting the cache.<br />A simple one-liner of the previous example:</p>
<pre><code class="lang-php">OverrideResponseException::goto(Url::fromUserInput(<span class="hljs-string">'/'</span>), [<span class="hljs-string">'contexts'</span> =&gt; [<span class="hljs-string">'user'</span>]]);
</code></pre>
]]></content:encoded></item><item><title><![CDATA[The Neverending Object]]></title><description><![CDATA[I've been messing with the Proxy object lately, and I've created a very simple proxy, that allows for a neverending object chain. It's basically a chain of No operations, and yes I know it's a hard sell, but I used it to rekindle one of my old projec...]]></description><link>https://blog.birk-jensen.dk/the-neverending-object</link><guid isPermaLink="true">https://blog.birk-jensen.dk/the-neverending-object</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[proxy]]></category><category><![CDATA[debugging]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Mon, 17 Apr 2023 08:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1675872971153/52319ce5-f32d-4f01-8b34-a55b2c87e643.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been messing with the <a target="_blank" href="https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-proxy-object-internal-methods-and-internal-slots">Proxy</a> object lately, and I've created a very simple proxy, that allows for a neverending object chain. It's basically a chain of <a target="_blank" href="https://en.wikipedia.org/wiki/NOP_(code)">No operations</a>, and yes I know it's a hard sell, but I used it to rekindle one of my old projects and it can work a (tiny) bit like <a target="_blank" href="https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#prod-OptionalExpression">Optional chaining (<code>?.</code>)</a>, so I thought I'd share.</p>
<h2 id="heading-implementation">Implementation</h2>
<p>It's a small proxy, with a <em>[[ProxyHandler]]</em> that traps <a target="_blank" href="https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver"><em>[[Get]]</em></a> and <a target="_blank" href="https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist"><em>[[Call]]</em></a>, it simply returns the proxy object itself in each trap:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> neverendingObject = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ }, {
  <span class="hljs-attr">apply</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">return</span> neverendingObject; },
  <span class="hljs-attr">get</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">return</span> neverendingObject; }
});
</code></pre>
<p>Note the <em>[[ProxyTarget]]</em> is a function, this will allow a <code>neverendingObject()</code> function call without any extra chaining.</p>
<p>For educational purposes, I'll add logging in the traps, this way it's easier to get what's happening:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> neverendingObject = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ }, {
  <span class="hljs-attr">apply</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">target</span>) </span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'[[Call]] ()'</span>); <span class="hljs-keyword">return</span> neverendingObject; },
  <span class="hljs-attr">get</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">target, prop</span>) </span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'[[Get]]'</span>, prop); <span class="hljs-keyword">return</span> neverendingObject; }
});

neverendingObject.aProperty.aFunction().aProperty

<span class="hljs-comment">// [[Get]] aProperty</span>
<span class="hljs-comment">// [[Get]] aFunction</span>
<span class="hljs-comment">// [[Call]] ()</span>
<span class="hljs-comment">// [[Get]] aProperty</span>
</code></pre>
<h3 id="heading-how-it-works">How it works</h3>
<p>I had some trouble wrapping my head around the <code>apply</code> part, the extra <code>get</code> call getting the reference to <code>aFunction</code> first and then calling it after.<br />I've included the call chain below highlighting each step of the way, thinking of it that way helped me out (remember that each <em>[[Get]]</em> and <em>[[Call]]</em> will return the <code>neverendingObject</code> object).</p>
<ol>
<li><p><code>neverendingObject</code> <strong><em>[[Get]]</em></strong> <em>aProperty</em></p>
</li>
<li><p><code>neverendingObject.aProperty</code> <strong><em>[[Get]]</em></strong> <em>aFunction</em></p>
</li>
<li><p><code>neverendingObject.aProperty.aFunction</code> <strong><em>[[Call]]</em></strong> <em>()</em></p>
</li>
<li><p><code>neverendingObject.aProperty.aFunction()</code> <strong><em>[[Get]]</em></strong> <em>aProperty</em></p>
</li>
<li><p><code>neverendingObject.aProperty.aFunction().aProperty</code></p>
</li>
</ol>
<p>Hopefully, this didn't add to any confusion.</p>
<h2 id="heading-the-optional-chaining-case">The optional chaining (<code>?.</code>) case</h2>
<p>The <code>neverendingObject</code> can not replace the <code>?.</code> operator, but it's possible to mimic the behavior of chaining without testing every step. but only if the last action is a function, and yes that's a show-stopper for most use cases.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> person = {
  arms(hasNoArms) {
    <span class="hljs-keyword">if</span> (hasNoArms === <span class="hljs-literal">true</span>) {
      <span class="hljs-keyword">return</span> neverendingObject
    } 

    <span class="hljs-keyword">return</span> {
      wave() { <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Waving arms'</span>); }
    };
  }
}

person.arms().wave(); <span class="hljs-comment">// "Waving arms"</span>
person.arms(<span class="hljs-literal">true</span>).wave(); <span class="hljs-comment">// Nothing happens</span>
</code></pre>
<p>Using the <code>?.</code> operator, it would look like the following (assuming <code>null</code> is returned instead of the <code>neverendingObject</code>, when the <code>hasNoArms</code> parameter is set):</p>
<pre><code class="lang-javascript">person.arms(<span class="hljs-literal">true</span>)?.wave(); <span class="hljs-comment">// Nothing happens</span>
</code></pre>
<p>The <code>?.</code> operator is more explicit and would be the preferred way to go most of the time. As a bonus the <code>?.</code> operator stops execution of the rest of the chain, where the <code>neverendingObject</code> will need to run the chain to its end, including any expensive calculation as an argument.</p>
<h2 id="heading-an-actual-use-case">An actual use case</h2>
<p>With everything, there's wrong about the <code>neverendingObject</code>, I found a use case where it shines, which is adding conditional chaining to existing objects.</p>
<p>A very simple case is adding an <code>and</code> function to the <code>console</code> object, giving an easy way to only log output if a <em>condition</em> is true without the added if:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.and = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">condition</span>) </span>{
  <span class="hljs-keyword">if</span> (condition) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">console</span>;
  }
  <span class="hljs-keyword">return</span> neverendingObject;
}

<span class="hljs-built_in">console</span>.and(<span class="hljs-number">1</span> == <span class="hljs-number">1</span>).log(<span class="hljs-string">'is logged'</span>); <span class="hljs-comment">// "is logged"</span>
<span class="hljs-built_in">console</span>.and(<span class="hljs-number">1</span> == <span class="hljs-number">2</span>).log(<span class="hljs-string">'is logged'</span>); <span class="hljs-comment">// Nothing happens</span>
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Let's compare a simple <code>if</code> with the <code>and()</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (isDebugging) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Some message'</span>);
}

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

<span class="hljs-built_in">console</span>.and(isDebugging).log(<span class="hljs-string">'some message'</span>);
</code></pre>
<p>It saves a couple of lines, and it's (subjectively) easier to read. When using the <code>time()</code> and <code>timeEnd()</code> to do some light profiling those couple of lines add up:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (isDebugging) {
  <span class="hljs-built_in">console</span>.time(<span class="hljs-string">'abcd'</span>);
}
<span class="hljs-comment">// Expensive stuff</span>
<span class="hljs-keyword">if</span> (isDebugging) {
  <span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">'abcd'</span>);
}

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

<span class="hljs-built_in">console</span>.and(isDebugging).time(<span class="hljs-string">'abcd'</span>);
<span class="hljs-comment">// Expensive stuff</span>
<span class="hljs-built_in">console</span>.and(isDebugging).timeEnd(<span class="hljs-string">'abcd'</span>);
</code></pre>
<p>I've started a new branch of an old project: <a target="_blank" href="https://github.com/BirkAndMe/ConditionalConsole">ConditionalConsole</a>.<br />The previous version needed to create another object than <code>console</code>. Using this technique I'm able to decorate it and make the functionality have a more native feel.</p>
]]></content:encoded></item><item><title><![CDATA[Peek inside the Drupal Search API blackbox]]></title><description><![CDATA[Disclaimer: I'm the developer of the Search API Generic Devel module
The Search API module is the de facto standard of adding search to a Drupal site. It supports many search backends and has a built-in internal Database backend, that is great for te...]]></description><link>https://blog.birk-jensen.dk/peek-inside-the-drupal-search-api-blackbox</link><guid isPermaLink="true">https://blog.birk-jensen.dk/peek-inside-the-drupal-search-api-blackbox</guid><category><![CDATA[Drupal]]></category><category><![CDATA[search]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Thu, 17 Nov 2022 08:32:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/QKSm-kXeWac/upload/v1665493551620/iSFyzVjjS.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Disclaimer: I'm the developer of the Search API Generic Devel module</em></p>
<p>The <a target="_blank" href="https://www.drupal.org/project/search_api">Search API</a> module is the de facto standard of adding search to a Drupal site. It supports <a target="_blank" href="https://www.drupal.org/docs/8/modules/search-api/getting-started/server-backends-and-features#backends">many search backends</a> and has a built-in internal Database backend, that is great for testing and smaller sites. It also has an impressive amount of <a target="_blank" href="https://www.drupal.org/docs/8/modules/search-api/getting-started/adding-an-index">index settings</a>, field data types, <a target="_blank" href="https://www.drupal.org/docs/8/modules/search-api/getting-started/processors">processors</a>, and the ability to integrate with <a target="_blank" href="https://www.drupal.org/project/facets">facets</a>.</p>
<p>These are all necessary to make a complete search experience, even creating a simple searcher requires you to manage (and understand) many of these options.</p>
<p>This makes it a daunting task to create a search index. I know I didn't feel comfortable, working with the Search API module at first, and have since revisited my first implementations to change the most glaring beginner mistakes.</p>
<p>One of the (my) biggest challenges is not knowing what entities had indexed what, and if they had been indexed.</p>
<h2 id="heading-enter-the-search-api-generic-develhttpswwwdrupalorgprojectsearchapigendev-module">Enter the <a target="_blank" href="https://www.drupal.org/project/search_api_gendev">Search API Generic Devel</a> module</h2>
<p>After installing the module, there should appear a new <em>Search API</em> tab under the <em>Devel</em> tab on an indexed node (or any entity that is being indexed by any Search API index).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667473104087/C7UlfKmkf.png" alt="A node that hasn't been indexed yet" class="image--center mx-auto" /></p>
<h3 id="heading-try-it-yourself">Try it yourself</h3>
<p>The screenshot above is from an <a target="_blank" href="https://www.drupal.org/docs/umami-drupal-demonstration-installation-profile">Umami installation</a>, where I've installed <em>search_api_gendev</em> and the <em>search_api_db</em> module.</p>
<p>You can use <a target="_blank" href="https://simplytest.me/">Simplytest</a> to try what you see in this article yourself (or spin up your own installation).</p>
<p>Enable the <em>search_api_db</em> module and import the config from <a target="_blank" href="https://gist.github.com/BirkAndMe/7ceed756bfdfa49a0f1dc16399288b91">this gist</a>.</p>
<p>Using the <em>Index item</em> and <em>Delete item</em> actions will do exactly what you would expect. Try them out and see that there's now content in the <em>Indexed data</em> container:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668085309659/Ba-XFkEDo.png" alt="Node after data has been indexed" /></p>
<h2 id="heading-not-there-yet">Not there yet</h2>
<p>You'll notice that the <em>Local data</em> is updated immediately when altering the entity, which is to be expected, but you'll notice the <em>Indexed data</em> is also updated. This is because it simply loads the <em>Item</em>, and it doesn't fill it with data from the backend (the backends don't have a uniform way of returning this information), however, some backend implementations will include <code>extraData</code>, that could hold the data actually indexed.</p>
<h3 id="heading-the-extradata">The extraData</h3>
<p>Depending on the backend server used there's be access to more information. Some backend implementations will add more data to the <code>extraData</code> property of the indexed item. The <code>extraData</code> is shown in the <em>Indexed data</em> container if any is provided by the backend server implementation (the <a target="_blank" href="https://www.drupal.org/project/search_api_solr">Solr module</a> will provide extra information).</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Even with the shortcomings (of not being able to see exactly what every backend has actually indexed), I find the <a target="_blank" href="https://www.drupal.org/project/search_api_gendev">Search API Generic Devel</a> module extremely useful. Getting a view of what the <em>rendered HTML output</em> is has helped me debug a lot faster than previously.</p>
<p>It also eases the onboarding of new developers that haven't worked with the Search API before, which is a huge win.</p>
]]></content:encoded></item><item><title><![CDATA[Drupal: Setting the 'Date' field value]]></title><description><![CDATA[I tend to forget how to set the Drupal datetime field programmatically, so this post works as a note for my self and anyone else who's in the same boat.
Snippet
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\datetime\Plugin\Field\FieldType\DateT...]]></description><link>https://blog.birk-jensen.dk/drupal-setting-the-date-field-value</link><guid isPermaLink="true">https://blog.birk-jensen.dk/drupal-setting-the-date-field-value</guid><category><![CDATA[Drupal]]></category><category><![CDATA[snippets]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Wed, 12 Oct 2022 09:03:54 GMT</pubDate><content:encoded><![CDATA[<p>I tend to forget how to set the Drupal datetime field programmatically, so this post works as a note for my self and anyone else who's in the same boat.</p>
<h2 id="heading-snippet">Snippet</h2>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">Core</span>\<span class="hljs-title">Datetime</span>\<span class="hljs-title">DrupalDateTime</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">datetime</span>\<span class="hljs-title">Plugin</span>\<span class="hljs-title">Field</span>\<span class="hljs-title">FieldType</span>\<span class="hljs-title">DateTimeItemInterface</span>;

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

<span class="hljs-comment">// Use the DrupalDateTime object to modify (or parse) datetimes.</span>
$datetime = <span class="hljs-keyword">new</span> DrupalDateTime(<span class="hljs-string">'now'</span>);
<span class="hljs-comment">// Set the timezone to UTC (the stored timezone).</span>
$datetime-&gt;setTimezone(<span class="hljs-keyword">new</span> \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE);

<span class="hljs-comment">// Date and time.</span>
$entity-&gt;field_dateandtime = $datetime-&gt;format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
<span class="hljs-comment">// Or date only.</span>
$entity-&gt;field_dateonly = $datetime-&gt;format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
</code></pre>
<p>The <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Datetime%21DrupalDateTime.php/class/DrupalDateTime/9.4.x"><code>DrupalDateTime</code></a> extends the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Datetime%21DateTimePlus.php/class/DateTimePlus/9.4.x"><code>DateTimePlus</code></a> (with drupal specific functionalities), which wraps the regular PHP <a target="_blank" href="https://www.php.net/manual/en/class.datetime.php"><code>DateTime</code></a> object (adding extra functionality and error handling).</p>
<p>The datetime field always saves with the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21modules%21datetime%21src%21Plugin%21Field%21FieldType%21DateTimeItemInterface.php/constant/DateTimeItemInterface%3A%3ASTORAGE_TIMEZONE/9.4.x"><code>STORAGE_TIMEZONE</code></a> (UTC) timezone, so use the <a target="_blank" href="https://www.php.net/manual/en/datetime.settimezone.php"><code>setTimezone</code></a> function with a <a target="_blank" href="https://www.php.net/manual/en/class.datetimezone.php"><code>DateTimeZone</code></a> to make sure of this.</p>
<p>Use the correct storage format depending on the datetime_type field settings. <a target="_blank" href="https://api.drupal.org/api/drupal/core%21modules%21datetime%21src%21Plugin%21Field%21FieldType%21DateTimeItemInterface.php/constant/DateTimeItemInterface%3A%3ADATETIME_STORAGE_FORMAT/9.4.x"><code>DATETIME_STORAGE_FORMAT</code></a> for <em>Date and time</em> or <a target="_blank" href="https://api.drupal.org/api/drupal/core%21modules%21datetime%21src%21Plugin%21Field%21FieldType%21DateTimeItemInterface.php/constant/DateTimeItemInterface%3A%3ADATE_STORAGE_FORMAT/9.4.x"><code>DATE_STORAGE_FORMAT</code></a> for <em>Date only</em>.</p>
<h3 id="heading-automatically-determine-date-and-time-or-date-only">Automatically determine <em>date and time</em> or <em>date only</em>.</h3>
<p>For a more generic version, use the field storage settings to determine what format to save as.</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Drupal</span>\<span class="hljs-title">datetime</span>\<span class="hljs-title">Plugin</span>\<span class="hljs-title">Field</span>\<span class="hljs-title">FieldType</span>\<span class="hljs-title">DateTimeItem</span>;

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

<span class="hljs-comment">// Get the setting from the field settings.</span>
$datetime_type = $entity-&gt;field_datetime-&gt;getFieldDefinition()-&gt;getFieldStorageDefinition()-&gt;getSetting(<span class="hljs-string">'datetime_type'</span>);

<span class="hljs-comment">// Determine the format.</span>
$format = match ($datetime_type) {
    DateTimeItem::DATETIME_TYPE_DATE =&gt; DateTimeItemInterface::DATE_STORAGE_FORMAT,
    DateTimeItem::DATETIME_TYPE_DATETIME =&gt; DateTimeItemInterface::DATETIME_STORAGE_FORMAT,
};

<span class="hljs-comment">// Set using the correct format.</span>
$entity-&gt;field_datetime = $datetime-&gt;format($format);
</code></pre>
<h2 id="heading-what-is-up-with-timezones">What is up with timezones</h2>
<p><strong>tl;dr</strong><br />Set the timezone of the given time when creating the <code>DrupalDateTime</code>, and set the timezone to the <code>STORAGE_TIMEZONE</code> format before setting the datetime field value.</p>
<pre><code class="lang-php"><span class="hljs-comment">// Set the timezone of the given time.</span>
$datetime = <span class="hljs-keyword">new</span> DrupalDateTime($time, $time_timezone);

<span class="hljs-comment">// Set the timezone to the storage format, and set the value.</span>
$datetime-&gt;setTimezone(<span class="hljs-keyword">new</span> \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE);
$entity-&gt;field_datetime = $datetime-&gt;format($format);
</code></pre>
<p><em>Setting field value is always the same, but it's important to know what timezone your data comes from to instantiate it with the correct zone.</em></p>
<h3 id="heading-creating-the-drupaldatetime-object">Creating the <code>DrupalDateTime</code> object.</h3>
<p>The <code>DrupalDateTime</code> defaults the timezone to the <a target="_blank" href="https://www.php.net/manual/en/function.date-default-timezone-get.php"><code>date_default_timezone_get()</code></a>, but it takes a <code>$timezone</code> parameter (second parameter) which is the timezone of the given time (first argument).</p>
<p>The timezone is passed directly the <a target="_blank" href="https://www.php.net/manual/en/datetimezone.construct.php">DateTimeZone constructor</a>, so it supports the following:</p>
<blockquote>
<p>One of the supported timezone names, an offset value (+0200), or a timezone abbreviation (BST).</p>
</blockquote>
<p>You can get a list of timezone names or abbreviation by using the static <a target="_blank" href="https://www.php.net/manual/en/datetimezone.listidentifiers.php"><code>DateTimeZone::listIdentifiers()</code></a> or <a target="_blank" href="https://www.php.net/manual/en/datetimezone.listabbreviations.php"><code>DateTimeZone::listAbbreviations()</code></a> functions.</p>
<p>If the given time is a timestamp or has specified a timezone, it will ignore the <code>$timezone</code> parameter.</p>
<pre><code class="lang-php"><span class="hljs-comment">// This will ignore the 'UTC' parameter.</span>
$datetime = <span class="hljs-keyword">new</span> DrupalDateTime(<span class="hljs-string">'2022-05-28T12:00:00+02:00'</span>, <span class="hljs-string">'UTC'</span>);
<span class="hljs-keyword">echo</span> $datetime-&gt;format(\DateTime::ISO8601, [<span class="hljs-string">'timezone'</span> =&gt; <span class="hljs-string">'UTC'</span>]);
<span class="hljs-comment">// 2022-05-28T10:00:00+0000 (notice it says 10 and not 12).</span>

<span class="hljs-comment">// Without the timezone it uses UTC.</span>
$datetime = <span class="hljs-keyword">new</span> DrupalDateTime(<span class="hljs-string">'2022-05-28T12:00:00'</span>, <span class="hljs-string">'UTC'</span>);
$datetime-&gt;format(\DateTime::ISO8601, [<span class="hljs-string">'timezone'</span> =&gt; <span class="hljs-string">'UTC'</span>])
<span class="hljs-comment">// 2022-05-28T12:00:00+0000 (stays 12, because it's in the same zone).</span>
</code></pre>
<p>If lucky the <code>DrupalDateTime</code> will simply read the time given, and return an object with the correct date and time. Check out the <a target="_blank" href="https://www.php.net/manual/en/datetime.formats.php">Supported Date and Time Formats</a> in the PHP manual.</p>
<p>But if the given date and time format isn't parsable, the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Datetime%21DateTimePlus.php/class/DateTimePlus/9.4.x"><code>DateTimePlus</code></a> (which <code>DrupalDateTime</code> inherits from) has some static helpers. Look for functions prefixed with <code>createFrom</code> and find the one suited for the current date time input.</p>
]]></content:encoded></item><item><title><![CDATA[Using JavaScript Proxy to automatically time your functions]]></title><description><![CDATA[The Proxy class is a way of intercepting calls to an object. This post assumes basic knowledge of the Proxy object, there's plenty of good information, so read up if this is your first Proxy encounter (I like the MDN and javascript.info writeups).
Th...]]></description><link>https://blog.birk-jensen.dk/using-javascript-proxy-to-automatically-time-your-functions</link><guid isPermaLink="true">https://blog.birk-jensen.dk/using-javascript-proxy-to-automatically-time-your-functions</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Benchmark]]></category><category><![CDATA[debugging]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Mon, 12 Sep 2022 14:22:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/mRGtYItJRnA/upload/v1662967610726/mA-FnZPQ-3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>The <a target="_blank" href="https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-proxy-object-internal-methods-and-internal-slots">Proxy</a> class is a way of intercepting calls to an object. This post assumes basic knowledge of the Proxy object, <a target="_blank" href="https://duckduckgo.com/?q=javascript+proxy">there's plenty</a> of good information, so read up if this is your first Proxy encounter (I like the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">MDN</a> and <a target="_blank" href="https://javascript.info/proxy">javascript.info</a> writeups).</em></p>
<h2 id="heading-the-idea">The idea</h2>
<p>Is to create a <code>ProxyStats</code> class that can time all calls to an object. I'll walk through the class creation in iterations, revisiting methods as functionality is added.</p>
<h2 id="heading-initial-implementation">Initial implementation</h2>
<p><a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/tree/05a5e6698527f4ddfa1294465df5767565121fd8">Browse the code at github (commit #05a5e66)</a></p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProxyStats</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>._timers = {};
    <span class="hljs-built_in">this</span>.stats = {};
  }

  <span class="hljs-keyword">static</span> watch(target) {
    <span class="hljs-keyword">const</span> proxyStats = <span class="hljs-keyword">new</span> ProxyStats();
    <span class="hljs-keyword">return</span> proxyStats.watch(target);
  }

  watch(target) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(target, <span class="hljs-built_in">this</span>)
  }

  getTimeKey(target, property) {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${target.constructor.name}</span>.<span class="hljs-subst">${property}</span>`</span>;
  }

  time(key) {
    <span class="hljs-built_in">this</span>._timers[key] = <span class="hljs-built_in">Date</span>.now();
  }

  timeEnd(key) {
    <span class="hljs-keyword">const</span> time = <span class="hljs-built_in">Date</span>.now() - <span class="hljs-built_in">this</span>._timers[key];
    <span class="hljs-built_in">this</span>.updateStats(key, time);

    <span class="hljs-built_in">console</span>.log(key, <span class="hljs-string">`<span class="hljs-subst">${time}</span> ms (<span class="hljs-subst">${<span class="hljs-built_in">this</span>.stats[key].time}</span> ms -- <span class="hljs-subst">${<span class="hljs-built_in">this</span>.stats[key].count}</span>)`</span>);
  }

  updateStats(key, time) {
    <span class="hljs-built_in">this</span>.stats[key] = <span class="hljs-built_in">this</span>.stats[key] || {<span class="hljs-attr">time</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">count</span>: <span class="hljs-number">0</span>};

    <span class="hljs-built_in">this</span>.stats[key].time +=  time;
    <span class="hljs-built_in">this</span>.stats[key].count += <span class="hljs-number">1</span>;
  }

  get(target, property, receiver) {
    <span class="hljs-keyword">const</span> timeKey = <span class="hljs-string">'get '</span> + <span class="hljs-built_in">this</span>.getTimeKey(target, property);

    <span class="hljs-built_in">this</span>.time(timeKey);
    <span class="hljs-keyword">const</span> result = <span class="hljs-built_in">Reflect</span>.get(target, property, receiver);
    <span class="hljs-built_in">this</span>.timeEnd(timeKey);

    <span class="hljs-keyword">return</span> result;
  }

  set(target, property, value, receiver) {
    <span class="hljs-keyword">const</span> timeKey = <span class="hljs-string">'set '</span> + <span class="hljs-built_in">this</span>.getTimeKey(target, property);

    <span class="hljs-built_in">this</span>.time(timeKey);
    <span class="hljs-built_in">Reflect</span>.set(target, property, value, receiver);
    <span class="hljs-built_in">this</span>.timeEnd(timeKey);

    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  }
}
</code></pre>
<p>The <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/ProxyStats.js#L16"><code>getTimeKey()</code></a>, <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/ProxyStats.js#L20"><code>time()</code></a>, <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/ProxyStats.js#L24"><code>timeEnd()</code></a> and <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/ProxyStats.js#L31"><code>updateState()</code></a> is used for timing and counting the calls to the object.</p>
<p>Both the <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/ProxyStats.js#L7">static</a> and <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/ProxyStats.js#L12">prototype</a> <code>watch()</code> function is used for watching an object.<br />
The static function will automatically create a new ProxyStats, while the prototype function can be used to watch several objects with the same handler instance.<br />
Using the same ProxyStats instance to watch several objects is helpful if you want to get statistics across all objects watched.</p>
<p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get"><code>get()</code></a><sup><a target="_blank" href="https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver">spec</a></sup> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set"><code>set()</code></a><sup><a target="_blank" href="https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver">spec</a></sup> traps are both simple implementations. Using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect"><code>Reflect</code></a><sup><a target="_blank" href="https://tc39.es/ecma262/multipage/reflection.html#sec-reflect-object">spec</a></sup> object to set or get the original value. This call is timed, and information is added to the <code>ProxyStats.stats</code> object.</p>
<h3 id="heading-test-setup">Test setup</h3>
<p>Prepare a <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/example.html#L16">simple class</a> to test the proxy handler. Notice the 50 ms delays added in the <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/example.html#L19"><code>getter</code></a>, <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/example.html#L24"><code>setter</code></a> and <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/example.html#L28"><code>function</code></a>.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">slowFunction</span>(<span class="hljs-params">min, max</span>) </span>{
  <span class="hljs-keyword">const</span> end = <span class="hljs-built_in">Date</span>.now() + <span class="hljs-built_in">Math</span>.round(min + <span class="hljs-built_in">Math</span>.random() * (max - min));
  <span class="hljs-keyword">while</span> (<span class="hljs-built_in">Date</span>.now() &lt; end) { }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleClass</span> </span>{
  aProperty = <span class="hljs-string">'some property'</span>

  <span class="hljs-keyword">get</span> <span class="hljs-title">aGetter</span>() {
    slowFunction(<span class="hljs-number">50</span>, <span class="hljs-number">50</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-string">'some getter'</span>;
  }

  <span class="hljs-keyword">set</span> <span class="hljs-title">aGetter</span>(<span class="hljs-params">$value</span>) {
    slowFunction(<span class="hljs-number">50</span>, <span class="hljs-number">50</span>);
  }

  aFunction() {
    slowFunction(<span class="hljs-number">50</span>, <span class="hljs-number">50</span>);
  }
}
</code></pre>
<p>Now <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/example.html#L33">wrap the <code>ExampleClass</code></a> using the <code>watch()</code> function, and calling the different properties to see the output.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> exampleObject = ProxyStats.watch(<span class="hljs-keyword">new</span> ExampleClass());
exampleObject.aProperty;
exampleObject.aGetter;
exampleObject.aGetter = <span class="hljs-string">''</span>;
exampleObject.aFunction();
</code></pre>
<h4 id="heading-output">Output</h4>
<p>The output here is the <em>console</em> output written in the <code>timeEnd()</code> function, it's the following format: <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/05a5e6698527f4ddfa1294465df5767565121fd8/ProxyStats.js#L28"><code>KEY TIME ms (TOTAL_TIME ms -- COUNT)</code></a>.</p>
<ul>
<li><code>KEY</code>: The key tells what kind of trap is used, and the object property.</li>
<li><code>TIME</code>: Is the time of the current call.</li>
<li><code>TOTAL_TIME</code>: Time of all calls for that specific property.</li>
<li><code>COUNT</code>: The amount of times the property has been called.</li>
</ul>
<pre><code class="lang-no-color">get ExampleClass.aProperty 0 ms (0 ms -- 1)
get ExampleClass.aGetter 50 ms (50 ms -- 1)
get ExampleClass.aGetter 50 ms (100 ms -- 2)
set ExampleClass.aGetter 50 ms (50 ms -- 1)
get ExampleClass.aFunction 0 ms (0 ms -- 1)
get ExampleClass.aFunction 0 ms (0 ms -- 2)
</code></pre>
<p>Getting a property (<code>get ExampleClass.aProperty</code>) is instant, so this seems fine at <em>0 ms</em>.</p>
<p>Both the getter (<code>get ExampleClass.aGetter</code>) and setter (<code>set ExampleClass.aGetter</code>) takes <em>50 ms</em> as expected.</p>
<p>But the function call (<code>get ExampleClass.aFunction</code>) is also instant, which is not correct. The reason it's instant is it only gets the property value, which is a function, it's not executing it.</p>
<h2 id="heading-getting-function-timing-to-work">Getting function timing to work</h2>
<p><a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/tree/bb97f3f43b360b15dfdd396045651c3d08c297d9">Browse the code at github (commit #bb97f3f)</a></p>
<p>To time function calls, the ProxyStats needs to wrap each function in another <code>Proxy</code> that utilizes the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply"><code>apply()</code></a><sup><a target="_blank" href="https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist">spec</a></sup> trap.</p>
<p>This is done by <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/bb97f3f43b360b15dfdd396045651c3d08c297d9/ProxyStats.js#L54">detecting functions</a> in the <code>get()</code> trap and returning a new <code>Proxy</code>, created by the helper function <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/bb97f3f43b360b15dfdd396045651c3d08c297d9/ProxyStats.js#L38"><code>handleFunction()</code></a>:</p>
<pre><code class="lang-javascript">handleFunction(target, property, receiver) {
  <span class="hljs-keyword">const</span> me = <span class="hljs-built_in">this</span>;

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(target[property], {
    apply(fnTarget, thisArg, args) {
      <span class="hljs-keyword">const</span> timeKey = me.getTimeKey(target, property) + <span class="hljs-string">'()'</span>;
      me.time(timeKey);
      <span class="hljs-keyword">const</span> result = <span class="hljs-built_in">Reflect</span>.apply(fnTarget, thisArg, args);
      me.timeEnd(timeKey);

      <span class="hljs-keyword">return</span> result;
    }
  });
}

get(target, property, receiver) {
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> target[property] === <span class="hljs-string">'function'</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.handleFunction(target, property, receiver);
  }

  <span class="hljs-keyword">const</span> timeKey = <span class="hljs-string">'get '</span> + <span class="hljs-built_in">this</span>.getTimeKey(target, property);

  <span class="hljs-built_in">this</span>.time(timeKey);
  <span class="hljs-keyword">const</span> result = <span class="hljs-built_in">Reflect</span>.get(target, property, receiver);
  <span class="hljs-built_in">this</span>.timeEnd(timeKey);

  <span class="hljs-keyword">return</span> result;
}
</code></pre>
<p>It's a tiny rewrite of <code>get()</code> that returns early if the property is a function. All the functionality is in the <code>handleFunction()</code> function</p>
<p>Note that the new <code>Proxy</code> wraps the actual function <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/bb97f3f43b360b15dfdd396045651c3d08c297d9/ProxyStats.js#L41"><code>target[property]</code></a>, and timing is simple and done the same way as with properties and getters.</p>
<h2 id="heading-recursive-functions">Recursive functions</h2>
<p><a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/tree/c652f33193e688cc93df11ef71a719d17c4ad915">Browse the code at github (commit #c652f33)</a></p>
<p>The last issue in the POC is recursive functions. When the recursive function calls itself, it resets the timer because the key is identical. Updating the example object will give a clear view of the problem. So <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/c652f33193e688cc93df11ef71a719d17c4ad915/example.html#L32">a recursive function</a> is added to the <code>ExampleClass</code>.</p>
<pre><code class="lang-javascript">aRecursiveFunction(depth) {
  slowFunction(<span class="hljs-number">50</span>, <span class="hljs-number">50</span>);

  <span class="hljs-keyword">if</span> (--depth &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-built_in">this</span>.aRecursiveFunction(depth);
  }
}
</code></pre>
<p>Calling this function with depth 2 <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/c652f33193e688cc93df11ef71a719d17c4ad915/example.html#L48"><code>exampleObject.aRecursiveFunction(2);</code></a> will give the following output:</p>
<pre><code class="lang-no-color">ExampleClass.aRecursiveFunction() 0 ms (0 ms -- 1)
ExampleClass.aRecursiveFunction() 0 ms (0 ms -- 2)
ExampleClass.aRecursiveFunction() 0 ms (0 ms -- 3)
</code></pre>
<p>To fix this the timer key is made unique, so it no longer resets every time a function with the same name is called. This is done in the <code>handleFunction()</code> function by adding a random string to the key:</p>
<pre><code class="lang-javascript">handleFunction(target, property, receiver) {
  <span class="hljs-keyword">const</span> me = <span class="hljs-built_in">this</span>;

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(target[property], {
    apply(fnTarget, thisArg, args) {
      <span class="hljs-keyword">const</span> timeKey = me.getTimeKey(target, property) + <span class="hljs-string">'() -- '</span> + <span class="hljs-built_in">Math</span>.random().toString(<span class="hljs-number">36</span>).substring(<span class="hljs-number">2</span>);
      me.time(timeKey);
      <span class="hljs-keyword">const</span> result = <span class="hljs-built_in">Reflect</span>.apply(fnTarget, thisArg, args);
      me.timeEnd(timeKey);

      <span class="hljs-keyword">return</span> result;
    }
  });
}
</code></pre>
<p>The important part here is the random string suffix <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/c652f33193e688cc93df11ef71a719d17c4ad915/ProxyStats.js#L45">added to the <code>timeKey</code></a>.<br />
Don't get confused about the <code>Math.random().toString(36).substring(2)</code> it's just a clever way of making a random string, try running it a couple of times in the console. This trick is purely cosmetics a <code>Math.random()</code> would've done the job.</p>
<p>This creates a problem with aggregating function calls, and makes it impossible, to sum up totals, check out the output:</p>
<pre><code class="lang-no-color">ExampleClass.aFunction() -- mq55zfo3g99 50 ms (50 ms -- 1)
ExampleClass.aFunction() -- hw42ikytz5m 50 ms (50 ms -- 1)
ExampleClass.aRecursiveFunction() -- sirt14hqvn 50 ms (50 ms -- 1)
ExampleClass.aRecursiveFunction() -- hfcibln8qjf 100 ms (100 ms -- 1)
</code></pre>
<p>To solve this the <a target="_blank" href="https://github.com/BirkAndMe/proxy-stats/blob/c652f33193e688cc93df11ef71a719d17c4ad915/ProxyStats.js#L27">suffix is removed</a> from the key before calling <code>updateStats()</code> in the <code>timeEnd()</code>. This way the key used in the <code>_timers</code> is unique, but key used in the stats is only the function name without the unique part:</p>
<pre><code class="lang-javascript">timeEnd(key) {
  <span class="hljs-keyword">const</span> time = <span class="hljs-built_in">Date</span>.now() - <span class="hljs-built_in">this</span>._timers[key];

  key = key.split(<span class="hljs-string">' -- '</span>).shift();
  <span class="hljs-built_in">this</span>.updateStats(key, time);

  <span class="hljs-built_in">console</span>.log(key, <span class="hljs-string">`<span class="hljs-subst">${time}</span> ms (<span class="hljs-subst">${<span class="hljs-built_in">this</span>.stats[key].time}</span> ms -- <span class="hljs-subst">${<span class="hljs-built_in">this</span>.stats[key].count}</span>)`</span>);
}
</code></pre>
<p>This gives a result without the unique part:</p>
<pre><code class="lang-no-color">ExampleClass.aFunction() 50 ms (50 ms -- 1)
ExampleClass.aFunction() 50 ms (100 ms -- 2)
ExampleClass.aRecursiveFunction() 50 ms (50 ms -- 1)
ExampleClass.aRecursiveFunction() 100 ms (150 ms -- 2)
</code></pre>
<p>There's still an issue with the total sum of the recursive function.</p>
<ul>
<li>Initial call waits <em>50 ms</em>, then calls itself.</li>
<li>The second call waits <em>50 ms</em> before logging the time and ending the second call.</li>
<li>Now the initial call has been <em>100 ms</em> underway, this is logged and initial call ended.</li>
<li>The total sum is calculated as <code>second call + initial call</code>, which is <em>150 ms</em> even though only <em>100 ms</em> is actually used in total.</li>
</ul>
<p>At this time it's good enough for jazz, but this issue will be fixed later on.</p>
<h2 id="heading-future">Future</h2>
<p>So far so good, the proof of concept seems to be working as intended (except for recursive functions).</p>
<p>As I'll be expanding on the ProxyStats class I'll try to create relevant blog posts. The following is a list of possible topics (also functioning as notes for myself, so every bullet might not justify a blog post), in no particular order:</p>
<ul>
<li>Fix the recursive function issue.</li>
<li>Documenting the class, using <a target="_blank" href="https://jsdoc.app/">JSDoc</a>.</li>
<li>How to use the ProxyStats class.</li>
<li>Testing, with simple unit tests.</li>
<li>Adding more features, filtering calls, smarter watcher functions.</li>
<li>Iterating existing functions and reviewing/rewriting code.</li>
<li>Performance, any obvious performance improvements.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Native PHP ping]]></title><description><![CDATA[To make a ping you'll need to know about the PHP Socket functions, ICMP protocol and Computing the Internet Checksum (don't let the RFCs scare you, the basics will be covered here).
Echo message
The ICMP header starts after the regular IP header (whi...]]></description><link>https://blog.birk-jensen.dk/native-php-ping</link><guid isPermaLink="true">https://blog.birk-jensen.dk/native-php-ping</guid><category><![CDATA[PHP]]></category><category><![CDATA[internet]]></category><category><![CDATA[network]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Thu, 14 Jul 2022 11:35:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1654697764102/YX0HwWQh4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To make a ping you'll need to know about the <a target="_blank" href="https://www.php.net/manual/en/ref.sockets.php">PHP Socket functions</a>, <a target="_blank" href="https://www.ietf.org/rfc/rfc792.txt">ICMP protocol</a> and <a target="_blank" href="https://www.ietf.org/rfc/rfc1071.txt">Computing the Internet Checksum</a> (don't let the RFCs scare you, the basics will be covered here).</p>
<h2 id="heading-echo-message">Echo message</h2>
<p>The ICMP header starts after the regular IP header (which is handled by socket_connect). It's a pretty simple header of 8 bytes, followed by the message being sent.</p>
<pre><code class="lang-nocolor">╔═══════════════╦═══════════════╦══════════════════════════════╗
║    Type 8b    ║    Code 8b    ║         Checksum 16b         ║
╠═══════════════╩═══════════════╬══════════════════════════════╣
║         Identifier 16b        ║      Sequence number 16b     ║
╠═══════════════════════════════╩══════════════════════════════╣
║                     data (variable length)                   ║
╚══════════════════════════════════════════════════════════════╝
</code></pre>
<ul>
<li><strong>Type:</strong> The type of control message to send. The echo message is <code>8</code>, but there are <a target="_blank" href="https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-codes">a lot of other types</a>, for other commands.</li>
<li><strong>Code:</strong> An extra setting for the type, depending on the type. The echo message doesn't have any extra settings, so it's left at <code>0</code>.</li>
<li><strong>Checksum:</strong> This is calculated after the package is assembled, to start with it's simply set to <code>0</code>. The package is paired into 16-bits integers, and <a target="_blank" href="https://en.wikipedia.org/wiki/Ones'_complement">one's complement</a> of the sum(explained later on).</li>
<li><strong>Identifier:</strong> The request identifier can be anything, it's used to match the reply. In the original ping, this was the <a target="_blank" href="https://gist.github.com/BirkAndMe/d5d1e069d94dd060ebccc9a866aa6bb8#file-ping-c-L270">UNIX process ID</a>, but we'll leave it <code>0</code>.</li>
<li><strong>Sequence number:</strong> Like the identifier, this is used to match the reply, left at <code>0</code>. The original ping <a target="_blank" href="https://gist.github.com/BirkAndMe/d5d1e069d94dd060ebccc9a866aa6bb8#file-ping-c-L271">increments this on every ping</a>.</li>
<li><strong>Data:</strong> The message. Like with the Identifier and Sequence number, the destination needs to reply with the same values. <a target="_blank" href="https://gist.github.com/BirkAndMe/d5d1e069d94dd060ebccc9a866aa6bb8#file-ping-c-L249">Original ping sends a <code>timeval</code> struct</a> to <a target="_blank" href="https://gist.github.com/BirkAndMe/d5d1e069d94dd060ebccc9a866aa6bb8#file-ping-c-L381">compute the round-trip</a>.</li>
</ul>
<h2 id="heading-calculating-the-checksum">Calculating the checksum</h2>
<p>In this example, the package used is <code>phping</code> (when sending a ping the package will be the entire header and the data to send)</p>
<p><strong>1) Divide the package into 16-bit pairs.</strong></p>
<p>The package <code>phping</code> is the following in binary:</p>
<pre><code>p: <span class="hljs-number">0111</span> <span class="hljs-number">0000</span>
<span class="hljs-attr">h</span>: <span class="hljs-number">0110</span> <span class="hljs-number">1000</span>
<span class="hljs-attr">p</span>: <span class="hljs-number">0111</span> <span class="hljs-number">0000</span>
<span class="hljs-attr">i</span>: <span class="hljs-number">0110</span> <span class="hljs-number">1001</span>
<span class="hljs-attr">n</span>: <span class="hljs-number">0110</span> <span class="hljs-number">1110</span>
<span class="hljs-attr">g</span>: <span class="hljs-number">0110</span> <span class="hljs-number">0111</span>
</code></pre><p>This is paired into 16-bit integers:</p>
<pre><code>a: <span class="hljs-number">0111</span> <span class="hljs-number">0000</span> <span class="hljs-number">0110</span> <span class="hljs-number">1000</span>
<span class="hljs-attr">b</span>: <span class="hljs-number">0111</span> <span class="hljs-number">0000</span> <span class="hljs-number">0110</span> <span class="hljs-number">1001</span>
<span class="hljs-attr">c</span>: <span class="hljs-number">0110</span> <span class="hljs-number">1110</span> <span class="hljs-number">0110</span> <span class="hljs-number">0111</span>
</code></pre><p><strong>2) Add all the pairs to get the sum.</strong></p>
<p>Adding a, b and c give an integer larger than 16 bit:</p>
<pre><code>a + b + c: <span class="hljs-number">1</span> <span class="hljs-number">0100</span> <span class="hljs-number">1111</span> <span class="hljs-number">0011</span> <span class="hljs-number">1000</span>
</code></pre><p><strong>3) Sum 16-bit pairs of the sum again.</strong></p>
<p>Reiterate the first 2 steps, until the sum is a single 16-bit integer.</p>
<p>Divide it:</p>
<pre><code>a: <span class="hljs-number">0000</span> <span class="hljs-number">0000</span> <span class="hljs-number">0000</span> <span class="hljs-number">0001</span>
<span class="hljs-attr">b</span>: <span class="hljs-number">0100</span> <span class="hljs-number">1111</span> <span class="hljs-number">0011</span> <span class="hljs-number">1000</span>
</code></pre><p>Sum it:</p>
<pre><code>a + b: <span class="hljs-number">0100</span> <span class="hljs-number">1111</span> <span class="hljs-number">0011</span> <span class="hljs-number">1001</span>
</code></pre><p><strong>4) End with one's complement, to invert the integer.</strong></p>
<p>One's complement is the same as a bitwise NOT operation.</p>
<pre><code>~ <span class="hljs-number">0100</span> <span class="hljs-number">1111</span> <span class="hljs-number">0011</span> <span class="hljs-number">1001</span>: <span class="hljs-number">1011</span> <span class="hljs-number">0000</span> <span class="hljs-number">1100</span> <span class="hljs-number">0110</span>
</code></pre><h3 id="heading-php-checksum-implementation">PHP checksum implementation</h3>
<p>There are a lot of tips and tricks, in the previously mentioned <a target="_blank" href="https://www.ietf.org/rfc/rfc1071.txt">RFC 1071</a> for implementing this (and some examples).
This is just one way of doing it.</p>
<pre><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">computeInternetChecksum</span>(<span class="hljs-params">$in</span>) </span>{
  <span class="hljs-comment">// Add an empty char (8-bit).</span>
  <span class="hljs-comment">// This trick leverages the way unpack() works.</span>
  $in .= <span class="hljs-string">"\x0"</span>;

  <span class="hljs-comment">// The n* format splits up the data string into 16-bit pairs.</span>
  <span class="hljs-comment">// It will unpack the string from the beginning, and only split</span>
  <span class="hljs-comment">// whole pairs. So it will automatically leave out (or include) the</span>
  <span class="hljs-comment">// odd byte added above.</span>
  $pairs = unpack(<span class="hljs-string">'n*'</span>, $in);

  <span class="hljs-comment">// Sum the pairs.</span>
  $sum = array_sum($pairs);

  <span class="hljs-comment">// Add the hi 16 to the low 16 bits, ending in a single 16-bit int.</span>
  <span class="hljs-keyword">while</span> ($sum &gt;&gt; <span class="hljs-number">16</span>)
    $sum = ($sum &gt;&gt; <span class="hljs-number">16</span>) + ($sum &amp; <span class="hljs-number">0xffff</span>);

  <span class="hljs-comment">// End with one's complement, to invert the integer.</span>
  <span class="hljs-comment">// Note the ~ operator before packing the sum into a string again.</span>
  <span class="hljs-keyword">return</span> pack(<span class="hljs-string">'n'</span>, ~$sum);
}
</code></pre>
<p>Check <code>phping</code> gives the expected checksum:</p>
<pre><code class="lang-php">$checksum = computeInternetChecksum(<span class="hljs-string">'phping'</span>);

<span class="hljs-comment">// Note that unpack() returns an array starting 1 (not 0).</span>
<span class="hljs-keyword">echo</span> decbin(unpack(<span class="hljs-string">'n*'</span>, $checksum)[<span class="hljs-number">1</span>]);

<span class="hljs-comment">// Output (the same as manually calculated):</span>
<span class="hljs-comment">// 1011000011000110</span>
</code></pre>
<p>If you're interested check out <a target="_blank" href="https://gist.github.com/BirkAndMe/d5d1e069d94dd060ebccc9a866aa6bb8#file-ping-c-L416">the original ping checksum implementation</a>.</p>
<p>Another PHP implementation, that resembles the original ping implementation (and C implementation in the RFC) can be found in the <a target="_blank" href="https://www.php.net/manual/en/function.socket-create.php#80775">socket_create() comments</a>.</p>
<h2 id="heading-preparing-the-package">Preparing the package</h2>
<p>The package is set up following the ICMP header schema:</p>
<pre><code class="lang-php"><span class="hljs-comment">// Prepare the package.</span>
$package = [
  <span class="hljs-string">'type'</span> =&gt; <span class="hljs-string">"\x08"</span>,
  <span class="hljs-string">'code'</span> =&gt; <span class="hljs-string">"\x00"</span>,
  <span class="hljs-string">'checksum'</span> =&gt; <span class="hljs-string">"\x00\x00"</span>,
  <span class="hljs-string">'identifier'</span> =&gt; <span class="hljs-string">"\x00\x00"</span>,
  <span class="hljs-string">'seqNumber'</span> =&gt; <span class="hljs-string">"\x00\x00"</span>,
  <span class="hljs-string">'data'</span> =&gt; <span class="hljs-string">'phping'</span>,
];

<span class="hljs-comment">// Compute the checksum, so it's ready to send.</span>
$package[<span class="hljs-string">'checksum'</span>] = computeInternetChecksum(implode(<span class="hljs-string">''</span>, $package));
$rawPackage = implode(<span class="hljs-string">''</span>, $package);
</code></pre>
<p>Check the package is as expected:</p>
<pre><code class="lang-php"><span class="hljs-comment">// Unpack the package into an associated array.</span>
$icmpHeaderFormat = <span class="hljs-string">'Ctype/Ccode/nchecksum/nidentifier/nsequence'</span>;
<span class="hljs-comment">// And show the binary values of each header part.</span>
print_r(array_map(<span class="hljs-string">'decbin'</span>, unpack($icmpHeaderFormat, $rawPackage)));

<span class="hljs-comment">// Output:</span>
<span class="hljs-comment">// Array</span>
<span class="hljs-comment">// (</span>
<span class="hljs-comment">//     [type] =&gt; 1000</span>
<span class="hljs-comment">//     [code] =&gt; 0</span>
<span class="hljs-comment">//     [checksum] =&gt; 1010100011000110</span>
<span class="hljs-comment">//     [identifier] =&gt; 0</span>
<span class="hljs-comment">//     [sequence] =&gt; 0</span>
<span class="hljs-comment">// )</span>
</code></pre>
<h2 id="heading-moving-on-to-sockets">Moving on to sockets</h2>
<p>Sending the package is done by using the <a target="_blank" href="https://www.php.net/manual/en/book.sockets.php">PHP socket functions</a>.</p>
<p><em>ICMP requests need raw network access, this causes permission issues, more on this later.</em></p>
<pre><code class="lang-php"><span class="hljs-comment">// Create the socket.</span>
<span class="hljs-comment">// AF_INIT is the IPv4 protocol.</span>
<span class="hljs-comment">// SOCK_RAW is needed to perform ICMP requests.</span>
$socket = socket_create(AF_INET, SOCK_RAW, getprotobyname(<span class="hljs-string">'icmp'</span>));

<span class="hljs-comment">// Open up the connection to a host.</span>
socket_connect($socket, <span class="hljs-string">'google.com'</span>, <span class="hljs-literal">null</span>);

<span class="hljs-comment">// Used to calculate the response time.</span>
$time = microtime(<span class="hljs-literal">true</span>);

<span class="hljs-comment">// Send the package to the target host.</span>
socket_send($socket, $rawPackage, strlen($rawPackage), <span class="hljs-number">0</span>);

<span class="hljs-comment">// Read the response.</span>
<span class="hljs-keyword">if</span> ($in = socket_read($socket, <span class="hljs-number">1</span>)) {
  <span class="hljs-comment">// Print the response time.</span>
  <span class="hljs-keyword">echo</span> microtime(<span class="hljs-literal">true</span>) - $time . <span class="hljs-string">" seconds\n"</span>;
}

<span class="hljs-comment">// Close the socket.</span>
socket_close($socket);
</code></pre>
<p>This doesn't check the reply, and simply assumes any reply is valid. This is also why only 1 byte is read in the socket_read.</p>
<p>To check the reply, you would need to parse the input (including the IPv4 header) and verify the checksum.</p>
<h3 id="heading-the-sockraw-issue">The SOCK_RAW issue.</h3>
<p>Because of security risks, <em>root</em> / <em>administrator</em> access is needed to use <code>SOCK_RAW</code> (or the PHP executable needs <code>CAP_NET_RAW</code> capability).</p>
<p>So when testing the script you'll need to <code>sudo</code> the command (I believe the Windows equivalent would be <code>run as administrator</code>).</p>
<p>PHP will trigger the following warning (this may vary depending on OS), if it's not run with sufficient permissions:</p>
<pre><code class="lang-nocolor">PHP Warning:  socket_create(): Unable to create socket [1]: Operation not permitted
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>First the script, ready for copy paste:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">computeInternetChecksum</span>(<span class="hljs-params">$in</span>) </span>{
  <span class="hljs-comment">// Add an empty char (8-bit).</span>
  <span class="hljs-comment">// This trick leverages the way unpack() works.</span>
  $in .= <span class="hljs-string">"\x0"</span>;

  <span class="hljs-comment">// The n* format splits up the data string into 16-bit pairs.</span>
  <span class="hljs-comment">// It will unpack the string from the beginning, and only split</span>
  <span class="hljs-comment">// whole pairs. So it will automatically leave out (or include) the</span>
  <span class="hljs-comment">// odd byte added above.</span>
  $pairs = unpack(<span class="hljs-string">'n*'</span>, $in);

  <span class="hljs-comment">// Sum the pairs.</span>
  $sum = array_sum($pairs);

  <span class="hljs-comment">// Add the hi 16 to the low 16 bits, ending in a single 16-bit int.</span>
  <span class="hljs-keyword">while</span> ($sum &gt;&gt; <span class="hljs-number">16</span>)
    $sum = ($sum &gt;&gt; <span class="hljs-number">16</span>) + ($sum &amp; <span class="hljs-number">0xffff</span>);

  <span class="hljs-comment">// End with one's complement, to invert the integer.</span>
  <span class="hljs-comment">// Note the ~ operator before packing the sum into a string again.</span>
  <span class="hljs-keyword">return</span> pack(<span class="hljs-string">'n'</span>, ~$sum);
}

<span class="hljs-comment">// Prepare the package.</span>
$package = [
  <span class="hljs-string">'type'</span> =&gt; <span class="hljs-string">"\x08"</span>,
  <span class="hljs-string">'code'</span> =&gt; <span class="hljs-string">"\x00"</span>,
  <span class="hljs-string">'checksum'</span> =&gt; <span class="hljs-string">"\x00\x00"</span>,
  <span class="hljs-string">'identifier'</span> =&gt; <span class="hljs-string">"\x00\x00"</span>,
  <span class="hljs-string">'seqNumber'</span> =&gt; <span class="hljs-string">"\x00\x00"</span>,
  <span class="hljs-string">'data'</span> =&gt; <span class="hljs-string">'phping'</span>,
];

<span class="hljs-comment">// Compute the checksum, so it's ready to send.</span>
$package[<span class="hljs-string">'checksum'</span>] = computeInternetChecksum(implode(<span class="hljs-string">''</span>, $package));
$rawPackage = implode(<span class="hljs-string">''</span>, $package);

<span class="hljs-comment">// Create the socket.</span>
<span class="hljs-comment">// AF_INIT is the IPv4 protocol.</span>
<span class="hljs-comment">// SOCK_RAW is needed to perform ICMP requests.</span>
$socket = socket_create(AF_INET, SOCK_RAW, getprotobyname(<span class="hljs-string">'icmp'</span>));

<span class="hljs-comment">// Open up the connection to a host.</span>
socket_connect($socket, <span class="hljs-string">'google.com'</span>, <span class="hljs-literal">null</span>);

<span class="hljs-comment">// Used to calculate the response time.</span>
$time = microtime(<span class="hljs-literal">true</span>);

<span class="hljs-comment">// Send the package to the target host.</span>
socket_send($socket, $rawPackage, strlen($rawPackage), <span class="hljs-number">0</span>);

<span class="hljs-comment">// Read the response.</span>
<span class="hljs-keyword">if</span> ($in = socket_read($socket, <span class="hljs-number">1</span>)) {
  <span class="hljs-comment">// Print the response time.</span>
  <span class="hljs-keyword">echo</span> microtime(<span class="hljs-literal">true</span>) - $time . <span class="hljs-string">" seconds\n"</span>;
}

<span class="hljs-comment">// Close the socket.</span>
socket_close($socket);
</code></pre>
<p>And the result:</p>
<pre><code class="lang-nocolor">$ sudo php ping.php
0.015023946762085 seconds
</code></pre>
<p>Because of the <code>SOCK_RAW</code> limitation, it's mostly an exercise in working with sockets, RFC documentations and binary string handling in PHP.</p>
<p>It might have its merits in a PHP CLI script, or if the PHP executable called by the webserver  can get the <code>CAP_NET_RAW</code> using <code>setcap</code>.</p>
]]></content:encoded></item><item><title><![CDATA[HashnodeRSS]]></title><description><![CDATA[The problem
If you have a blog hosted on hashnode you'll automatically get a lot of great features. One of these essential blog features is the RSS feed, allowing readers to subscribe with whatever RSS reader they are most comfortable with.
The probl...]]></description><link>https://blog.birk-jensen.dk/hashnoderss</link><guid isPermaLink="true">https://blog.birk-jensen.dk/hashnoderss</guid><category><![CDATA[Linode]]></category><category><![CDATA[Linode Hackathon]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[rss]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Thu, 30 Jun 2022 15:03:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/KaeaUITiWnc/upload/v1656059422946/TzxUT2InK.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-problem">The problem</h2>
<p>If you have a blog hosted on <a target="_blank" href="https://hashnode.com/">hashnode</a> you'll automatically get a lot of great features. One of these essential blog features is the RSS feed, allowing readers to subscribe with whatever RSS reader they are most comfortable with.</p>
<p>The problem is this feed is for every post, so if you have a blog with many different topics (different programming languages, frameworks, personal posts, movie reviews) your readers can't subscribe to a single topic but will receive all your posts. Or maybe you want to add your feed to a <a target="_blank" href="https://en.wikipedia.org/wiki/Planet_(software)">planet</a> or another aggregator with a specific topic (my own usecase).</p>
<h2 id="heading-the-solution">The solution</h2>
<p>An externally hosted script, that parses the RSS and returns only the relevant posts. A reader (or feed aggregator) could then subscribe to this feed instead, and only get the posts they're interested in.</p>
<h2 id="heading-the-stack">The stack</h2>
<p>Code is available at the following GitHub repo (the readme holds a lot of the information in this blog post): https://github.com/BirkAndMe/hashnoderss</p>
<p>The stack for this project is kept as simple as possible, it only consists of <a target="_blank" href="https://www.php.net">PHP</a>. One just needs a host with PHP support, this can be any <a target="_blank" href="https://en.wikipedia.org/wiki/Shared_web_hosting_service">shared host</a>, <a target="_blank" href="https://en.wikipedia.org/wiki/Dedicated_hosting_service">dedicated server</a> or <a target="_blank" href="https://en.wikipedia.org/wiki/Cloud_computing">cloud solution</a>.</p>
<p>It's a standalone script, with no ties to any external libraries. And since the script is as small as it is, everyone with PHP knowledge should be able to grasp what's going on and understand the ins and outs, no matter what framework they usually work with. Making it more available to everyone.</p>
<h2 id="heading-the-installation">The installation</h2>
<h3 id="heading-testing-the-code-locally">Testing the code locally.</h3>
<p>_There's also a quick intro to testing locally in the repository.</p>
<p>This is a quick and dirty way of testing out the functionality, and should not be used for any actual production.</p>
<pre><code class="lang-no-color">git clone git@github.com:BirkAndMe/hashnoderss.git
cd hashnoderss
php -S localhost:8000 router-hack.php
</code></pre>
<h3 id="heading-quick-intro-to-a-linodehttpswwwlinodecom-lemphttpswwwlinodecommarketplaceappslinodelemp-installation">Quick intro to a <a target="_blank" href="https://www.linode.com/">Linode</a> <a target="_blank" href="https://www.linode.com/marketplace/apps/linode/lemp/">LEMP</a> installation.</h3>
<p>Use whatever hosting option you're comfortable with, this is just a quick getting started.</p>
<p><em>Since the script doesn't use MySQL you could opt for manually setting up an instance with just a web server and PHP, but that is out of scope for this post</em></p>
<h4 id="heading-1-find-and-install-lemp-in-the-linode-marketplace">1) Find and install LEMP in the Linode marketplace.</h4>
<p>Fill in all the options, and start by choosing the smallest (and cheapest) Shared CPU (<em>Nanode 1 GB</em> at the time of writing). The instance should start automatically and setup everything for a standard LEMP setup.</p>
<h5 id="heading-11-open-an-ssh-connection-to-the-linode-instance">1.1) Open an SSH connection to the Linode instance.</h5>
<p>Or use the Linode LISH console if you prefer.</p>
<h4 id="heading-2-getting-the-repository-into-the-web-root">2) Getting the repository into the web root.</h4>
<p>Find the webroot (on the Linode instance) in <code>/var/www/html/[SITE_NAME]/public_html</code> and install the script here (notice git is installed, and the directory is cleaned).</p>
<pre><code class="lang-no-color">sudo apt install git
rm *
git clone https://github.com/BirkAndMe/hashnoderss.git .
</code></pre>
<p>The <code>[SITE_NAME]</code> is the Reverse DNS name of the Linode instance (should be something like <code>[IP-ADDRESS].ip.linodeusercontent.com</code>). You can find this name in the <code>Network</code> tab when inspecting your Linode instance in the Linode backend <code>https://cloud.linode.com/linodes/[INSTANCE_ID]/networking</code>.</p>
<h4 id="heading-3-setting-up-php-and-nginx">3) Setting up PHP and NGINX.</h4>
<p>Just a few changes are needed to host the script.</p>
<h5 id="heading-31-install-php-xml">3.1) Install PHP XML.</h5>
<p>The XML module is not default for the PHP Debian installation, so install it.</p>
<pre><code class="lang-no-color">sudo apt install php-xml
</code></pre>
<h5 id="heading-32-change-the-nginx-site-config">3.2) Change the NGINX site config.</h5>
<p>Edit the site configuration found in <code>/etc/nginx/sites-enabled/[SITE_NAME]</code>, on a clean installation there should only be 1 site so that's it (the server has <a target="_blank" href="https://www.nano-editor.org/">nano</a> ready for editing, but use whatever you like).</p>
<p>Setup a redirect of all the requests to the <code>index.php</code> script. Do so by replacing this:</p>
<pre><code class="lang-nginx"><span class="hljs-attribute">location</span> / {
  <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> <span class="hljs-variable">$uri</span>/ =<span class="hljs-number">404</span>;
}
</code></pre>
<p>With this:</p>
<pre><code class="lang-nginx"><span class="hljs-attribute">location</span> / {
  <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> <span class="hljs-variable">$uri</span>/ /index.php;
}
</code></pre>
<p>Reload NGINX by running this command</p>
<pre><code><span class="hljs-attribute">systemctl</span> reload nginx`
</code></pre><p>Now visit the site to make sure everything works (this should get the complete RSS feed for this blog):
<code>https://[SERVER-IP].ip.linodeusercontent.com/blog.birk-jensen.dk</code></p>
<h2 id="heading-the-api">The API</h2>
<p>The primary (and only) endpoint is:</p>
<pre><code class="lang-no-color">https://host/[BLOG_DOMAIN]
</code></pre>
<p>It's possible to set a blog domain in the <code>settings.php</code> (as <code>$hostname</code>). Doing this will ignore the BLOG_DOMAIN, and using <code>https://host</code> will work without the extra path.</p>
<h3 id="heading-filtering">Filtering</h3>
<p>This is filtered using Query parameters.</p>
<p>These parameters are mapped to <code>PostDetailed</code> query on <a target="_blank" href="https://api.hashnode.com">api.hashnode.com</a>, so use the documentation available there to figure out what properties to filter by.</p>
<h4 id="heading-simple-filter">Simple filter.</h4>
<pre><code class="lang-no-color">host/blog.birk-jensen.dk?_id=6298c0135787a911a45d5fcf
</code></pre>
<p>This will get a single blog post with a specific ID.</p>
<h4 id="heading-nested-filtering">Nested filtering.</h4>
<p>It's possible to check nested properties, using double dashes <code>--</code> as a seperator, so the following is probably the most useful filter:</p>
<pre><code class="lang-no-color">host/blog.birk-jensen.dk?tags--name=Drupal
</code></pre>
<p>Only return blog posts tagged with <code>Drupal</code>.</p>
<h4 id="heading-combining-filters">Combining filters.</h4>
<pre><code class="lang-no-color">host/blog.birk-jensen.dk?author--username=BirkAndMe&amp;tags--name[]=Drupal
</code></pre>
<p>Get blog posts written by <code>BirkAndMe</code> and tagged with <code>Drupal</code>.</p>
<p><em>The filter only support AND conditions.</em></p>
<h4 id="heading-operators">Operators.</h4>
<p>The value checks support 4 operators, which is written as a prefix (remember to URL escape) to the value</p>
<ul>
<li><strong>=</strong> Equal, is the default operator and makes sure the post value is the same as the given value.</li>
<li><strong>!</strong> Not, make sure the value is not the same.</li>
<li><strong>&lt;</strong> Lesser than, only if the value is lesser than.</li>
<li><strong>&gt;</strong> Greater than, only if the value is greater than.</li>
</ul>
<p>Get all posts except a specific one.</p>
<pre><code class="lang-no-color">host/blog.birk-jensen.dk?_id=!6298c0135787a911a45d5fcf
</code></pre>
<p>Only posts with more than 5 reactions <em>(the <code>%3E</code> is <code>&gt;</code> escaped).</em></p>
<pre><code class="lang-no-color">host/blog.birk-jensen.dk?totalReactions=%3E5
</code></pre>
<h3 id="heading-debugging">Debugging.</h3>
<p>Add a <code>debug</code> parameter to get a better view of what's going on. It will show the normalized values and the Graph QL used to query hashnode.</p>
<pre><code class="lang-no-color">host/blog.birk-jensen.dk?author--username=BirkAndMe&amp;tags--name[]=Drupal&amp;debug
</code></pre>
<h1 id="heading-the-code">The code</h1>
<p>Repo link again: https://github.com/BirkAndMe/hashnoderss</p>
<p>There's not much to be said about the code, it should be pretty self-explanatory.</p>
<p>It's kept simple rather than smart. Whenever I could do something smart, I chose to do something explicit instead, to make the code more approachable.</p>
<p>Because of the simplicity, reading the <code>index.php</code> from the beginning should give a pretty good picture of what's going on.</p>
<p>Here's the gist of it (gives you an overview, before diving into the code):</p>
<ol>
<li>Read the original RSS feed from the blog.</li>
<li>Get all URLs in the original RSS (these are used as slugs when querying the <a target="_blank" href="https://api.hashnode.com/">Hashnode api</a>.</li>
<li>Normalize the query parameters into a filter array, and use this array to build the Graph QL property list.</li>
<li>Query the Hashnode API, and run through all the posts checking the filter matches (and removing the posts that don't match from the RSS).</li>
<li>Return the filtered RSS.</li>
</ol>
<h1 id="heading-remarks">Remarks</h1>
<p>I hope anyone other than me can use this for their Hashnode blog (I might set up an actual service in the future, so you don't have to host the script).</p>
<p>I also someone will find it refreshing to read a simple not framework dependant open source project. I know I found it refreshing to write something using only the simplest of tools.</p>
<h2 id="heading-real-life-use-case">Real-life use case.</h2>
<p>I haven't tested this in production yet, but I would likely set up an HTTP cache (varnish) in front of this to minimize all the external requests (RSS and API calls), this should be almost plug and play, since it only uses GET requests.</p>
<p>Also using the <code>$hostname</code> setting and then having the RSS feed on a <code>rss.domain.com</code> will give prettier URLs.</p>
<h2 id="heading-future-features">Future features.</h2>
<p>My guess is that tag filtering is the only needed filter 99.9% of the time, so any future feature is primary just for the fun of it, but here goes:</p>
<ul>
<li>Get the greater and lesser than working on dates.</li>
<li>Implement a regular expression operator.</li>
<li>Improve code documentation on helper functions.</li>
<li>Error handling (there's none at the moment).</li>
<li>Get posts from a specific user (using the <code>user</code> query in the Hashnode API) across multiple blogs.</li>
</ul>
<h2 id="heading-limitations">Limitations.</h2>
<p>The Hashnode API is slow, especially when querying many posts (to the point where it sometimes fails). This is why it limits the posts to a max of 20, in most cases this is more than enough because RSS clients will only look for new responses anyway.</p>
<p>It also caches the requests for 10 minutes, so you won't get anything instantly.</p>
]]></content:encoded></item><item><title><![CDATA[Drupal: Check if the current page is using the admin theme]]></title><description><![CDATA[Snippet
if (\Drupal::service('router.admin_context')->isAdminRoute()) {
  // ...
}

The router.admin_context service maps to the AdminContext helper class.
isAdminRoute() simply checks the Route for the _admin_route option.
Pitfalls
When working with...]]></description><link>https://blog.birk-jensen.dk/drupal-snippet-check-if-the-current-page-is-using-the-admin-theme</link><guid isPermaLink="true">https://blog.birk-jensen.dk/drupal-snippet-check-if-the-current-page-is-using-the-admin-theme</guid><category><![CDATA[Drupal]]></category><category><![CDATA[snippets]]></category><dc:creator><![CDATA[Philip Birk-Jensen]]></dc:creator><pubDate>Thu, 02 Jun 2022 13:50:11 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-snippet">Snippet</h2>
<pre><code class="lang-php"><span class="hljs-keyword">if</span> (\Drupal::service(<span class="hljs-string">'router.admin_context'</span>)-&gt;isAdminRoute()) {
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>The <a target="_blank" href="https://api.drupal.org/api/drupal/core%21core.services.yml/service/router.admin_context/9.4.x"><code>router.admin_context</code></a> service maps to the <a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Routing%21AdminContext.php/class/AdminContext/9.4.x"><code>AdminContext</code></a> helper class.</p>
<p><a target="_blank" href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Routing%21AdminContext.php/function/AdminContext%3A%3AisAdminRoute/9.4.x"><code>isAdminRoute()</code></a> simply checks the <a target="_blank" href="https://api.drupal.org/api/drupal/vendor%21symfony%21routing%21Route.php/class/Route/9.4.x"><code>Route</code></a> for the <em>_admin_route</em> option.</p>
<h3 id="heading-pitfalls">Pitfalls</h3>
<p>When working with a Route object, it's tempting to check for the <code>_admin_route</code> option directly, instead of using the <code>router_admin_context</code> service. The problem here is any <a target="_blank" href="https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/altering-existing-services-providing-dynamic">customization of the service</a> will be ignored.</p>
<h2 id="heading-relevant-links">Relevant links</h2>
<ul>
<li><a target="_blank" href="https://www.drupal.org/node/2224207">Admin paths are now defined as part of route definitions</a>: Drupal 7 to 8 change record, with a <em>Matching admin routes</em> section referring to this.</li>
<li><a target="_blank" href="https://www.drupal.org/docs/drupal-apis/routing-system/structure-of-routes">Structure of routes</a>: Information about the <em>_admin_route</em> option (and Drupal routes in general).</li>
<li><a target="_blank" href="http://lobsterr.me/post/how-check-if-current-page-using-admin-theme-drupal">How to check if the current page using admin theme in Drupal?</a>: Similar blog post.</li>
</ul>
]]></content:encoded></item></channel></rss>