<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>Symfony &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/symfony/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Fri, 02 May 2025 06:51:52 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>Symfony &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[A (tiny, incomplete, single user, write-only) ActivityPub server in PHP]]></title>
		<link>https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/</link>
					<comments>https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 03 Feb 2024 12:34:51 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[ActivityPub]]></category>
		<category><![CDATA[fediverse]]></category>
		<category><![CDATA[mastodon]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=49490</guid>

					<description><![CDATA[I&#039;ve written an ActivityPub server which only allows you to post messages to your followers.  That&#039;s all it does.  It won&#039;t record favourites or reposts. There&#039;s no support for following other accounts or receiving replies.  It cannot delete or update posts nor can it verify signatures. It doesn&#039;t have a database or any storage beyond flat files.  But it will happily send messages and allow…]]></description>
										<content:encoded><![CDATA[<p>I've written <a href="https://github.com/edent/location-activitypub-symfony/">an ActivityPub server which <em>only</em> allows you to post messages to your followers</a>.  That's all it does.  It won't record favourites or reposts. There's no support for following other accounts or receiving replies.  It cannot delete or update posts nor can it verify signatures. It doesn't have a database or any storage beyond flat files.</p>

<p>But it will happily send messages and allow itself to be followed.</p>

<p>This shows that it is <em>totally</em> possible to broadcast fully-featured ActivityPub messages to the Fediverse with minimal coding skills and modest resources.</p>

<h2 id="why"><a href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#why">Why</a></h2>

<p>I wanted to create <a href="https://shkspr.mobi/blog/2024/01/rebuilding-foursquare-for-activitypub-using-openstreetmap/">a service a bit like FourSquare</a>.  For this, I needed an ActivityPub server which allows posting geotagged locations to the Fediverse.</p>

<p>I didn't want to install a fully-featured server with lots of complex parts. So I (foolishly) decided to write my own. I had a lot of trouble with HTTP Signatures.  Because they are cursed and I cannot read documentation. But mostly the cursed thing.</p>

<h2 id="how"><a href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#how">How</a></h2>

<p>Creating a minimum viable Mastodon instance can be done with <a href="https://justingarrison.com/blog/2022-12-06-mastodon-files-instance/">half a dozen static files</a>. That gets you an account that people can see. They can't follow it or receive any posts though.</p>

<p>I wanted to use PHP to build an interactive server. PHP is supported everywhere and is simple to deploy.  Luckily, <a href="https://rknight.me/blog/building-an-activitypub-server/">Robb Knight has written an excellent tutorial</a>, so I ripped off his code and rewrote it for Symfony.</p>

<p>The structure is relatively straightforward.</p>

<ul>
<li><code>/.well-known/webfinger</code> is a static file which gives information about where to find details of the account.</li>
<li><code>/[username]</code> is a static file which has the user's metadata, public key, and links to avatar images.</li>
<li><code>/following</code> and <code>/followers</code> are also static files which say how many users are being followed / are following.</li>
<li><code>/posts/[GUID]</code> a directory with JSON files saved to disk - each ones contains the published ActivityPub note.</li>
<li><code>/photos/</code> is a directory with any uploaded media in it.</li>
<li><code>/outbox</code> is a list of all the posts which have been published.</li>
<li><code>/inbox</code> is an <em>external</em> API endpoint. An ActivityPub server sends it a follow request, the endpoint then POSTs a cryptographically signed Accept message to the follower's inbox. The follower's inbox address is saved to disk.</li>
<li><code>/logs</code> is a listing of all the messages received by the inbox.</li>
<li><code>/new</code> is a password protected page which lets you write a message. This is then sent to...</li>
<li><code>/send</code> is an <em>internal</em> API endpoint. It constructs an ActivityPub note, with attached location metadata, and POSTs it to each follower's inbox with a cryptographic signature.</li>
</ul>

<p>That's it.</p>

<p>The front-end grabs my phone's geolocation and shows the 25 nearest places within 100 metres. One click and the page posts to the <code>/send</code> endpoint which then publishes a message saying I'm checked in.  It is also possible to attach to the post a short message and a single photo with alt text.</p>

<p>There's no database. Posts are saved as JSON documents. Images are uploaded to a directory. It is single-user, so there is no account management.</p>

<h2 id="what-works"><a href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#what-works">What Works</a></h2>

<ul>
<li style="list-style-type: '✅ ';">Users can find the account.</li>
<li style="list-style-type: '✅ ';">Users can follow the account and receive updates.</li>
<li style="list-style-type: '✅ ';">Posts contain geotag metadata.</li>
<li style="list-style-type: '✅ ';">Posts contain a description of the place.</li>
<li style="list-style-type: '✅ ';">Posts contain an OSM link to the place.</li>
<li style="list-style-type: '✅ ';">Posts contain a custom message.</li>
<li style="list-style-type: '✅ ';">Posts autolink #Hashtags (sort of).</li>
<li style="list-style-type: '✅ ';">Posts can have an image attached to them.</li>
<li style="list-style-type: '✅ ';">Messages to the inbox are recorded (but not yet integrated).</li>
</ul>

<h2 id="todo"><a href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#todo">ToDo</a></h2>

<ul>
<li>My account only has a few dozen followers, some of whom share the same sever. Even with cURL multi handle, it takes time to post to several servers.</li>
<li>It posts plain text. It doesn't autolink websites</li>
<li>Hashtags are linked when viewed remotely, but they don't go anywhere locally.</li>
<li>There's no language selection - it is hard-coded to English.</li>
<li>The outbox isn't paginated.</li>
<li>The UI looks crap - but it is only me using it.</li>
<li>There's only a basic front-page showing a map of all my check-ins.</li>
<li>Replies are logged, but there's no easy way to see them.</li>
<li>Doesn't show any metadata about the place being checked-in to. It could use the item's website (if any) or hashtags for the type of amenity it is.</li>
<li>No way to handle being unfollowed.</li>
<li>No way to remove servers which have died.</li>
<li>Probably lots more.</li>
</ul>

<h2 id="other-resources"><a href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#other-resources">Other Resources</a></h2>

<p>I found these resources helpful while creating this project:</p>

<ul>
<li><a href="https://tinysubversions.com/notes/activitypub-tool/">MVP ActivityPub in Python on Glitch</a></li>
<li><a href="https://seb.jambor.dev/posts/understanding-activitypub/">Understanding ActivityPub Part 1: Protocol Fundamentals</a></li>
<li><a href="https://flak.tedunangst.com/post/activity-notes">Activity Notes</a></li>
<li><a href="https://docs.gotosocial.org/en/latest/federation/">Federating with GoToSocial</a></li>
</ul>

<h2 id="whats-next"><a href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#whats-next">What's Next?</a></h2>

<p>I've <a href="https://github.com/mastodon/mastodon/issues/29002">raised an issue on Mastodon</a> to see if they can support showing locations in posts. Hopefully, one day, they'll allow adding locations and then I can shut this down.</p>

<p>The code needs tidying up - it is very much a scratch-my-own-itch development. Probably riddled with bugs and security holes.</p>

<p>World domination?</p>

<h2 id="where"><a href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#where">Where</a></h2>

<p>You can <a href="https://github.com/edent/location-activitypub-symfony/">laugh at my code on GitHub</a>.</p>

<p>You can <a href="https://location.edent.tel/">look at my check-ins on a map</a>.</p>

<p>You can follow my location on the Fediverse at <code>@edent_location@location.edent.tel</code></p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=49490&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Rewriting OpenBenches in Symfony]]></title>
		<link>https://shkspr.mobi/blog/2023/05/rewriting-openbenches-in-symfony/</link>
					<comments>https://shkspr.mobi/blog/2023/05/rewriting-openbenches-in-symfony/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 27 May 2023 11:34:32 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[OpenBenches]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=45783</guid>

					<description><![CDATA[I once described my ideal coding environment to a colleague as &#34;telneting directly into prod and damn the consequences!&#34;  I jest. But only a little. When I build for myself I treat best practices and coding styles as harmful. Chaotic evil but, hey, it&#039;s only myself I&#039;m hurting.  Anyway, my wife and I run a hobby site - OpenBenches.org - which was coded in a long alcopop fueled weekend. It&#039;s fair…]]></description>
										<content:encoded><![CDATA[<p>I once described my ideal coding environment to a <a href="https://mastodon.social/@jonodrew">colleague</a> as "telneting directly into prod and damn the consequences!"</p>

<p>I jest. But only a little. When I build for myself I treat best practices and coding styles as harmful. Chaotic evil but, hey, it's only myself I'm hurting.</p>

<p>Anyway, my wife and I run a hobby site - <a href="https://openbenches.org/">OpenBenches.org</a> - which was coded in a long alcopop fueled weekend. It's fair to say that it has exceeded our expectations in terms of people getting involved. But is underwhelming in terms of stability, speed, memory usage, efficiency, idempotency, and accessibility.</p>

<p>So I decided to rewrite it using a somewhat modern PHP framework. Partly to improve things and partly as a learning exercise.</p>

<p>I picked Symfony as it seemed to be under active development and had a reasonably simple "getting started" guide. The documentation looked good but, sadly, assumed a tonne of pre-existing knowledge. I ended up sending a few Pull Requests to improve it where I could.</p>

<p>Symfony suffers, like a lot of frameworks, from messy and inconsistent conventions. Some things are zero indexed, some from one. Why? Who know? Similarly, there are a bunch of random YAML (yeuch) files which need to be manually edited and a tangled mess of various config files scattered around.</p>

<p>But... in the end, the Symfony server is reasonably easy to use - with relatively sensible conventions and decent performance. It forced me to actually think about what I was doing and <em>why</em> I'd chosen certain conventions.</p>

<p>I won't claim it to be the best written code on the planet - and there's still some way to go in order to clean it up completely - but I'm pleased with the way things have turned out.</p>

<p>If you'd like to get involved - <a href="https://github.com/openbenches/openbenches.org">check out the code</a></p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=45783&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/05/rewriting-openbenches-in-symfony/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Getting Auth0 user information on non-firewall Symfony pages]]></title>
		<link>https://shkspr.mobi/blog/2023/05/getting-auth0-user-information-on-non-firewall-symfony-pages/</link>
					<comments>https://shkspr.mobi/blog/2023/05/getting-auth0-user-information-on-non-firewall-symfony-pages/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 22 May 2023 11:34:10 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=45789</guid>

					<description><![CDATA[I am using Auth0&#039;s Symfony library to allow users to log in with their social network providers.  It works really well.  Using this firewall configuration, a user who visits /private is successfully taken through the login flow and I can then use $this-&#62;getUser() to see their details.  security:     password_hashers:         Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: …]]></description>
										<content:encoded><![CDATA[<p>I am using <a href="https://github.com/auth0/symfony">Auth0's Symfony library</a> to allow users to log in with their social network providers.  It works really well.</p>

<p>Using this firewall configuration, a user who visits <code>/private</code> is successfully taken through the login flow and I can then use <code>$this-&gt;getUser()</code> to see their details.</p>

<pre><code class="language-yaml">security:
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    providers:
        users_in_memory: { memory: null }
        auth0_provider:
            id: Auth0\Symfony\Security\UserProvider
    firewalls:
        private:
            pattern: ^/private$
            context: user
            stateless: false
            provider: auth0_provider
            custom_authenticators:
                - auth0.authenticator
        main:
            lazy: true
            provider: users_in_memory
</code></pre>

<p>I want some unauthenticated pages to show user information. For example, if the user is logged in then <code>/home</code> should say "Hello $username". If not, it should say "Log in here".</p>

<p>The answer was annoyingly simple - but not documented by Symfony or Auth0.</p>

<p>Change the main firewall to <em>not</em> be lazy:</p>

<pre><code class="language-yaml">        main:
            lazy: false
            provider: users_in_memory
</code></pre>

<p>That then places all the Auth0 information into the <code>$_SESSION</code> global variable.  You can retrieve the user's details with:</p>

<pre><code class="language-php">if ( isset( $_SESSION["_sf2_attributes"]["auth0_session"]["user"] ) ) {
    $user = $_SESSION["_sf2_attributes"]["auth0_session"]["user"];
    $username   = $user["nickname"];
    $avatar     = $user["picture"];
}
</code></pre>

<p>I'm sure there's a more official way to do this, but this quick and dirty hack seems to work pretty well.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=45789&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/05/getting-auth0-user-information-on-non-firewall-symfony-pages/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Doctrine - difference between bindValue() and setParameter() on prepared statements]]></title>
		<link>https://shkspr.mobi/blog/2023/05/doctrine-difference-between-bindvalue-and-setparameter-on-prepared-statements/</link>
					<comments>https://shkspr.mobi/blog/2023/05/doctrine-difference-between-bindvalue-and-setparameter-on-prepared-statements/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 11 May 2023 11:34:34 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=45734</guid>

					<description><![CDATA[This pissed me off and I couldn&#039;t figure out what I was doing wrong. So I&#039;m blogging about my ignorance.  Imagine you&#039;re using Symfony and Doctrine to access a database. You are using prepared statements to prevent any SQL injection problems.  There are two main ways of doing this - and they disagree about how positional variables should be specified.  Data Retrieval And Manipulation  Here&#039;s a…]]></description>
										<content:encoded><![CDATA[<p>This pissed me off and I couldn't figure out what I was doing wrong. So I'm blogging about my ignorance.</p>

<p>Imagine you're using Symfony and Doctrine to access a database. You are using prepared statements to prevent any SQL injection problems.</p>

<p>There are two main ways of doing this - and they disagree about how positional variables should be specified.</p>

<h2 id="data-retrieval-and-manipulation"><a href="https://shkspr.mobi/blog/2023/05/doctrine-difference-between-bindvalue-and-setparameter-on-prepared-statements/#data-retrieval-and-manipulation">Data Retrieval And Manipulation</a></h2>

<p>Here's a fairly trivial SQL statement with a couple of variables:</p>

<pre><code class="language-php">$sql = "SELECT `userID` FROM `users` WHERE `firstname` LIKE ? AND `surname` LIKE ?";
$stmt = $conn-&gt;prepare($sql);
$stmt-&gt;bindValue(1, $user_input_1st_name);
$stmt-&gt;bindValue(2, $user_input_2nd_name);
$results = $stmt-&gt;executeQuery();
</code></pre>

<p>Pretty easy, right? Write your SQL as normal, but place <code>?</code> where you want user supplied variables to be. This uses <code>bindValue()</code> to set the variables in the query.</p>

<blockquote><p>The approach using question marks is called positional, because the values are bound in order from left to right to any question mark found in the previously prepared SQL query. That is why you specify the position of the variable to bind into the bindValue() method
<a href="https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/data-retrieval-and-manipulation.html#dynamic-parameters-and-prepared-statements">Doctrine: Data Retrieval And Manipulation</a></p></blockquote>

<h2 id="query-builder"><a href="https://shkspr.mobi/blog/2023/05/doctrine-difference-between-bindvalue-and-setparameter-on-prepared-statements/#query-builder">Query Builder</a></h2>

<p>Doctrine also offer an <a href="https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/query-builder.html">SQL Query Builder</a> - it looks like this:</p>

<pre><code class="language-php">$queryBuilder = $conn-&gt;createQueryBuilder();
$queryBuilder
    -&gt;select("userID")
    -&gt;from("users")
    -&gt;where("firstname LIKE ? AND surname LIKE ?")
    -&gt;setParameter(0, $user_input_1st_name)
    -&gt;setParameter(1, $user_input_2nd_name);
$results = $queryBuilder-&gt;executeQuery();
</code></pre>

<p>Notice the difference? Yes! <code>setParameter()</code> is <em>zero</em> based!</p>

<blockquote><p>The numerical parameters in the QueryBuilder API start with the needle 0.
<a href="https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/query-builder.html#security-safely-preventing-sql-injection">SQL Query Builder</a></p></blockquote>

<h2 id="why-the-difference"><a href="https://shkspr.mobi/blog/2023/05/doctrine-difference-between-bindvalue-and-setparameter-on-prepared-statements/#why-the-difference">Why the difference?</a></h2>

<p>Because the universe hates you, I guess?</p>

<h2 id="solving-the-issue"><a href="https://shkspr.mobi/blog/2023/05/doctrine-difference-between-bindvalue-and-setparameter-on-prepared-statements/#solving-the-issue">Solving the issue</a></h2>

<p>I've sent a pull request to make the documentation clearer.  In the meantime, both methods accept <em>named</em> parameters.</p>

<pre><code class="language-php">$sql = "SELECT `userID` FROM `users` WHERE `firstname` LIKE :first";
$stmt-&gt;bindValue("first", $user_input_1st_name);

...

$queryBuilder
    -&gt;select("userID")
    -&gt;from("users")
    -&gt;where("firstname LIKE :first")
    -&gt;setParameter("first", $user_input_1st_name)
</code></pre>

<p>I hope that helps remove some confusion for future users. Even if it's only me!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=45734&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/05/doctrine-difference-between-bindvalue-and-setparameter-on-prepared-statements/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Symfony - multiple paths to the same route within a controller]]></title>
		<link>https://shkspr.mobi/blog/2023/05/symfony-multiple-paths-to-the-same-route-within-a-controller/</link>
					<comments>https://shkspr.mobi/blog/2023/05/symfony-multiple-paths-to-the-same-route-within-a-controller/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 10 May 2023 11:34:57 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=45729</guid>

					<description><![CDATA[I couldn&#039;t work out how to use Route Aliasing within my controller. I couldn&#039;t find anything in the documentation about it. But, thanks to a StackOverflow comment it is possible.  Suppose you want users to be able to access a page using /users/123 and /people/123 - with both routes displaying the same data?  Normally you&#039;d write something like #[Route(&#039;/user/{id}&#039;, name: &#039;show_user&#039;)] - as it…]]></description>
										<content:encoded><![CDATA[<p>I couldn't work out how to use <a href="https://symfony.com/doc/current/routing.html#route-aliasing">Route Aliasing</a> within my controller. I couldn't find anything in the documentation about it. But, thanks to <a href="https://stackoverflow.com/a/73011667/1127699">a StackOverflow comment</a> it is possible.</p>

<p>Suppose you want users to be able to access a page using <code>/users/123</code> and <code>/people/123</code> - with both routes displaying the same data?</p>

<p>Normally you'd write something like <code>#[Route('/user/{id}', name: 'show_user')]</code> - as it happens, that first parameter can be an array!  Which means you can write:</p>

<pre><code class="language-php">&lt;?php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class ThingController extends AbstractController
{

    #[Route(["/user/{id}", "/people/{id}", "/guests/{id}"], name: 'show_user')]
    public function show_user(int $id) {
        // Your logic here
    }
}
</code></pre>

<p>If you need to know <em>which</em> route your user took, call <code>$request-&gt;getRequestUri();</code> to get the string representation, i.e. <code>/user</code>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=45729&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/05/symfony-multiple-paths-to-the-same-route-within-a-controller/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Fixing a weird issue with Symfony's Cache]]></title>
		<link>https://shkspr.mobi/blog/2023/05/fixing-a-weird-issue-with-symfonys-cache/</link>
					<comments>https://shkspr.mobi/blog/2023/05/fixing-a-weird-issue-with-symfonys-cache/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 02 May 2023 11:34:23 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=45680</guid>

					<description><![CDATA[I&#039;m just getting started with Symfony, so I&#039;m blogging some of the weird things I&#039;m finding.  Symfony has a concept of Cache Contracts. You can call an expensive / slow / intensive operation and immediately cache the result for a specific time period. Next time you call the operation, the results are served from the cache until the expiry time has been hit.  Nifty! But I couldn&#039;t get it to work.  …]]></description>
										<content:encoded><![CDATA[<p>I'm just getting started with Symfony, so I'm blogging some of the weird things I'm finding.</p>

<p>Symfony has a concept of <a href="https://symfony.com/doc/current/components/cache.html#cache-contracts">Cache Contracts</a>. You can call an expensive / slow / intensive operation and immediately cache the result for a specific time period. Next time you call the operation, the results are served from the cache until the expiry time has been hit.</p>

<p>Nifty! But I couldn't get it to work.</p>

<p>Take this sample code. It should return the current date and time and cache the result for 5 minutes (300 seconds).  If you call it repeatedly, it should keep showing the old date until the 5 minutes are up.</p>

<pre><code class="language-php">&lt;?php
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

class Whatever
{
    public function get_date() {
        $cache = new FilesystemAdapter();
        $value = $cache-&gt;get('cached_date', function (ItemInterface $item) {
            $item-&gt;expiresAfter(300);
            $date = date("Y-m-d H:i:s");
            return $date;
      });
      return $value;
    }
}
</code></pre>

<p>But it wasn't working. Why? I don't know. But I fixed it by using a <a href="https://symfony.com/doc/current/components/cache/adapters/filesystem_adapter.html"><em>namespaced</em> cache</a>:</p>

<pre><code class="language-php">$cache = new FilesystemAdapter("my_awesome_cache");
</code></pre>

<p>I hope that's helpful to someone - even if that someone is me in a few years' time!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=45680&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/05/fixing-a-weird-issue-with-symfonys-cache/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Doctrine - how to use LIKE with dbal prepared statements]]></title>
		<link>https://shkspr.mobi/blog/2023/05/doctrine-how-to-use-like-with-dbal-prepared-statements/</link>
					<comments>https://shkspr.mobi/blog/2023/05/doctrine-how-to-use-like-with-dbal-prepared-statements/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 01 May 2023 11:34:49 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=45684</guid>

					<description><![CDATA[I&#039;m just getting started with Symfony, so I&#039;m blogging some of the weird things I&#039;m finding.  I want to use Doctrine dbal to search a database for a partial match. For example searching for &#34;smith&#34; should find &#34;blacksmith&#34; and &#34;smithy&#34;.  I have a prepared statement like this:  $queryBuilder = $conn-&#62;createQueryBuilder(); $queryBuilder     -&#62;select(&#34;whatever&#34;)     -&#62;from(&#34;table&#34;)     -&#62;where(&#34;name …]]></description>
										<content:encoded><![CDATA[<p>I'm just getting started with Symfony, so I'm blogging some of the weird things I'm finding.</p>

<p>I want to use Doctrine dbal to search a database for a partial match. For example searching for "smith" should find "blacksmith" and "smithy".</p>

<p>I have a prepared statement like this:</p>

<pre><code class="language-php">$queryBuilder = $conn-&gt;createQueryBuilder();
$queryBuilder
    -&gt;select("whatever")
    -&gt;from("table")
    -&gt;where("name LIKE '%?%'")
    -&gt;setParameter(0, $query);
</code></pre>

<p>But I get the error:</p>

<blockquote><p>The number of variables must match the number of parameters in the prepared statement.</p></blockquote>

<p>The solution is annoyingly simple. The escaping has to be <em>in the parameter</em> itself. Like this:</p>

<pre><code class="language-php">...
    -&gt;where("name LIKE ?")
    -&gt;setParameter(0, "%{$query}%");
</code></pre>

<p>I hope that's helpful to someone - even if that someone is me in a few years' time!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=45684&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/05/doctrine-how-to-use-like-with-dbal-prepared-statements/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
