<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by David Ciulla on Medium]]></title>
        <description><![CDATA[Stories by David Ciulla on Medium]]></description>
        <link>https://medium.com/@offskip-dave?source=rss-9d55e3ca0746------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*xTxcSBSpqgVYSm-kEf4U8Q.jpeg</url>
            <title>Stories by David Ciulla on Medium</title>
            <link>https://medium.com/@offskip-dave?source=rss-9d55e3ca0746------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 05 Jun 2026 11:12:09 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@offskip-dave/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Creating a Macro that lets you convert text to audio on-the-fly in Laravel without needing an extra…]]></title>
            <link>https://offskip-dave.medium.com/creating-a-macro-that-lets-you-convert-text-to-audio-on-the-fly-in-laravel-without-needing-an-extra-702bd86d336f?source=rss-9d55e3ca0746------2</link>
            <guid isPermaLink="false">https://medium.com/p/702bd86d336f</guid>
            <category><![CDATA[laravel]]></category>
            <category><![CDATA[text]]></category>
            <category><![CDATA[speech]]></category>
            <category><![CDATA[macro]]></category>
            <category><![CDATA[audio]]></category>
            <dc:creator><![CDATA[David Ciulla]]></dc:creator>
            <pubDate>Tue, 23 Aug 2022 16:32:16 GMT</pubDate>
            <atom:updated>2022-08-23T16:40:58.622Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9dB9TtMvexvR0JpzcUoXnA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@lunarts?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Volodymyr Hryshchenko</a> on <a href="https://unsplash.com/s/photos/text-to-speech?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h3>Creating a Macro that lets you convert text to audio on-the-fly in Laravel without needing an extra package</h3><p>Ok, so here is the deal: I wanted a quick and dirty way of dynamically and on-the-fly convert text to speech, preferably in PHP. Now, in the past I have fiddled around trying to do the opposite, aka transcribing audio to text.</p><p>I wanted to use my all-time favourite Web Framework Laravel and as an additional requirement, not having to install a third party package or library.</p><p>The solution I came up with is equally genius and hacky at the same time. Simply put, I use Google Translate to do the work for me behind the scenes. No API calls or anything required, just simply send a GET request to the URL that returns the audio file that gets created when you use Google Translate.</p><p>Ok, so let me explain. When you navigate to <a href="https://translate.google.com/">https://translate.google.com/</a> and start typing some text, you will notice that a ‘loudspeaker’ icon appears.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DrzF9nnHTuAJZ6wXF_jSOw.png" /></figure><p>This means, Google has created an audio file on-the-fly. And it just so happens that this audio file can be accessed from a URL, which looks something like this:</p><p><a href="https://translate.google.com/translate_tts?ie=UTF-8&amp;client=gtx&amp;q=Hello+World&amp;tl=en_US">https://translate.google.com/translate_tts?ie=UTF-8&amp;client=gtx&amp;q=Hello+World!&amp;tl=en_US</a></p><p>If you open this URL as is, most browser will probably detect the filetype and render a nice audio player for you like shown below (Example: Google Chrome).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/420/1*6i-TEbG2dpteEWPp4SAoDA.png" /><figcaption>Default HTML5 Audio Player in Google Chrome.</figcaption></figure><p>So, now that we know that this exists and where to get it, it is just a matter of writing some code to open the URL, get the data and store it in our application.</p><p>Let’s get our hands dirty with some code.</p><p>My initial approach was very simple. I didn’t even bother to create a page or a Controller or anything. I just put below code into my `web.php` routes file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BUWmiU2ZM6VQXeKwZwrM9Q.png" /></figure><p>Now, when I hit the `text-to-speech` endpoint on my application, it would send a GET request to the URL where the audio file is located, get the data, create a filename and store it into the `storage/app/public` folder.</p><p>Although this works, I wanted to create a reusable helper for this functionality. In the spirit of Laravel’s nice fluent syntax used everywhere throughout the framework, I thought it would be really cool to create a Macro for the `Str` (String) facade. If you have never used or created Macro’s before, don’t worry, I will briefly explain. A lot of things in Laravel are <em>Macroable</em> (recognizable by the use of the <em>Macroable</em> <em>trait</em>). It is simply a way to extend functionality. Let’s take a simple example straight out of Laravel’s documentation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1002/1*op63WzzK1L1NP6oaW-Zmgw.png" /><figcaption>Example of registering Macro (source: laravel.com)</figcaption></figure><p>Macros are registered in a ServiceProviders `boot` method, like in this instance in the `AppServiceProvider`. Here, a Macro called `github` is added to the Http client, which allows us to use it like this:</p><pre>$response = Http::github()-&gt;get(‘/’);</pre><p>This will send a request to Github with the headers as configured in the ServiceProvider. As you can see, it allows extending functionality and writing really elegant, expressive code.</p><p>But now back to the text-to-speech implementation. What I wanted to achieve was this:</p><pre><br>$audioFile = Str::toAudio(‘Hello World!’); // gets the audio, stores the file and returns the path to the file</pre><p>To achieve this, we basically just have to move my initial code into the AppServiceProvider. There we register our Macro as shown in the example above inside the boot method and that is pretty much all there is to it.</p><p>So the final code looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Bw1GxsXRGyghH-GwERHdZQ.png" /><figcaption>Registration of Macro to retrieve audio from Google Translate</figcaption></figure><p>Note that I improved it slightly by not only passing the text to convert, but also the language as an optional second parameter, which means I can now also convert text in other languages to audio, as long as Google Translate supports it. 🤷‍♂</p><pre>// Hello in Japanese</pre><pre>return Str::toAudio(‘こんにちは！’, ‘ja_JP’);</pre><h3>Summary</h3><p>Obviously, this approach does have its limitations and drawbacks. For starters, everybody that has ever played around with Google Translate will know that certain words and expressions are not well picked up and converted by the underlying AI. Also the voice usually sounds ‘robotic’ and unnatural. So, I would suggest to use it only for simple use cases, such as short texts, preferably not containing any complicated words or phrases. Also, I think it goes without saying that results in English are quality-wise usually the best.</p><p>While I wouldn’t use this in a professional, large-scale business use case, it was a fun little thing to play around with.</p><p>I prepared a gist for this, so feel free to use it:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/be3e66bbe22c4b2525a08db5d30c7713/href">https://medium.com/media/be3e66bbe22c4b2525a08db5d30c7713/href</a></iframe><p>Thanks for reading! 😃</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=702bd86d336f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Laravel Actions: A Developers Best Friend]]></title>
            <link>https://offskip-dave.medium.com/laravel-actions-a-developers-best-friend-51d1c9079f4b?source=rss-9d55e3ca0746------2</link>
            <guid isPermaLink="false">https://medium.com/p/51d1c9079f4b</guid>
            <category><![CDATA[php]]></category>
            <category><![CDATA[laravel]]></category>
            <dc:creator><![CDATA[David Ciulla]]></dc:creator>
            <pubDate>Fri, 11 Feb 2022 16:41:09 GMT</pubDate>
            <atom:updated>2022-03-02T11:59:39.598Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>Disclaimer: This article is based on the presentation of <a href="https://twitter.com/LukeDowning19">Luke Downing</a> at the <a href="https://laracon.net">Laracon Online Winter 2022</a> conference. The whole conference was <a href="https://www.youtube.com/watch?v=0Rq-yHAwYjQ">live-streamed on Youtube</a> for free on February 9, 2022.</blockquote><p>I personally have worked extensively with Action classes in a recent project, so this was a good timing for me to summarize Luke’s talk in the form of this article.</p><h3>What are Actions?</h3><p>To put it very simple: A broken-up service class which has one responsibility only. This means it usually only contains one method to do a specific task, which can be reused. This of course plays well with the principles of <em>Separation of Concerns</em> and also the approach of using <em>Compositions</em> (instead of inheritance). If this all sounds way too complicated for you, don’t worry, we will jump into some examples in a second!</p><h3>Example: User Registration</h3><p>Let’s say you have a simple sign up form on your website. Typically, you would receive the request in your Controller, validate the data, and then invoke, depending on your coding style, either the Model directly, or some Repository class to store the user to the database as shown below.</p><pre>class RegisterController extends Controller<br>{<br>    public function store(RegisterRequest $request)<br>    {<br>        $data = $request-&gt;validated();</pre><pre>        $user = User::create([<br>            ...$data,<br>            &#39;password&#39; =&gt; Hash::make($data[&#39;password&#39;])<br>        ]);</pre><pre>        $auth-&gt;login($user);</pre><pre>        return redirect(route(&#39;home&#39;))-&gt;with(&#39;success&#39;, true);<br>    }<br>}</pre><p>This is a fairly common approach and certainly works well, until the need arises to register the user from, let’s say, the command line. Of course, we would create a custom <em>Artisan</em> command for this task. Let’s see how this command would look like:</p><pre>public function handle(): int<br>{<br>    $data = $this-&gt;validateData($this-&gt;getInput());<br>    <br>    $user = User::create([<br>        ...$data,<br>        &#39;password&#39; =&gt; Hash::make($data[&#39;password&#39;]);<br>    ]);</pre><pre>    $this-&gt;line(&quot;User {$user-&gt;email} has been registered.&quot;);</pre><pre>    return Command::SUCCESS;<br>}</pre><pre>private function validateData(array $data): array<br>{<br>    return Validator::make($data, [<br>        &#39;email&#39; =&gt; [&#39;required&#39;, &#39;email&#39;, &#39;unique:users&#39;],<br>        &#39;name&#39; =&gt; [&#39;required&#39;, &#39;string&#39;, &#39;max:255&#39;],<br>        &#39;password&#39; =&gt; [&#39;required&#39;, &#39;min:6&#39;]<br>    ])-&gt;validate();<br>}</pre><pre>private function getInput(): array<br>{<br>    return [<br>        &#39;email&#39; =&gt; $this-&gt;argument(&#39;email&#39;) ?? $this-&gt;ask(&quot;What is the user&#39;s name?&quot;),<br>        &#39;name&#39; =&gt; $this-&gt;argument(&#39;email&#39;) ?? $this-&gt;ask(&quot;What is the user&#39;s email?&quot;),<br>        &#39;password&#39; =&gt; $this-&gt;argument(&#39;password&#39;) ?? $this-&gt;secret(&quot;What is the user&#39;s password?&quot;)<br>    ];<br>}</pre><p>Again, no problem at all. We have, however, introduced duplicated code. The validation and creation of the user part has now to be maintained in several places, which is a problem in terms of maintainability.</p><h3>Enter Actions</h3><p>One very powerful and elegant solution to this is obviously using an <em>Action</em>. Let’s see how that would like applied to the previous example. For that, we create a file called <em>RegisterUser.php</em> under <em>app/Actions</em> (if the <em>Actions</em> folder doesn’t exist, just create it).</p><pre>class RegisterUser<br>{<br>    public function __invoke(array $data): User<br>    {<br>        $data = $this-&gt;validateData($data);</pre><pre>        return User::create([<br>            ...$data,<br>            &#39;password&#39; =&gt; Hash::make($data[&#39;password&#39;]);<br>        ]);<br>     }</pre><pre>    private function validateData(array $data): array<br>    {<br>        return Validator::make($data, [<br>            &#39;email&#39; =&gt; [&#39;required&#39;, &#39;email&#39;, &#39;unique:users&#39;],<br>            &#39;name&#39; =&gt; [&#39;required&#39;, &#39;string&#39;, &#39;max:255&#39;],<br>            &#39;password&#39; =&gt; [&#39;required&#39;, &#39;min:6&#39;]<br>        ]);<br>    }<br>}</pre><p>Please note the name of the class. It is in the format of <em>verb + noun</em>. This makes it more explicit and easier to understand as to what the class does.</p><p>And now since we abstracted the logic, we can go ahead and replace this logic in both our Controller and the Artisan command, and also tests if you have them (<em>spoiler</em>: you should!) as shown below.</p><p>Our Controller will now look like this:</p><pre>class RegisterController extends Controller<br>{<br>    public function store(RegisterRequest $request, RegisterUser $registerUser)<br>    {<br>        $user = $registerUser($request-&gt;all());</pre><pre>        $auth-&gt;login($user);</pre><pre>        return redirect(route(&#39;home&#39;))-&gt;with(&#39;success&#39;, true);<br>    }<br>}</pre><p>And the Artisan command:</p><pre>public function handle(RegisterUser $registerUser): int<br>{<br>    $user = $registerUser($this-&gt;getInput());</pre><pre>    $this-&gt;line(&quot;User {$user-&gt;email} has been registered.&quot;);</pre><pre>    return Command::SUCCESS;<br>}</pre><pre>private function getInput(): array<br>{<br>    return [<br>        &#39;email&#39; =&gt; $this-&gt;argument(&#39;email&#39;) ?? $this-&gt;ask(&quot;What is the user&#39;s name?&quot;),<br>        &#39;name&#39; =&gt; $this-&gt;argument(&#39;email&#39;) ?? $this-&gt;ask(&quot;What is the user&#39;s email?&quot;),<br>        &#39;password&#39; =&gt; $this-&gt;argument(&#39;password&#39;) ?? $this-&gt;secret(&quot;What is the user&#39;s password?&quot;)<br>    ];<br>}</pre><p>And that’s basically it. Clean abstraction, which means enhanced maintainability.</p><p>Now, Luke takes it even a step further in his Laracon talk by pairing it with Contracts (aka Interfaces), but I’ll leave that out for now. If you are interested in that as well, check out his awesome presentation on <a href="https://www.youtube.com/watch?v=0Rq-yHAwYjQ">Youtube</a> (it was the first talk of the event, starting at around 29 minutes into the video), and follow him on <a href="https://twitter.com/LukeDowning19">Twitter</a>.</p><p>I hope this article was helpful and I hope you all have a nice day/morning/evening.</p><p>Stay safe.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=51d1c9079f4b" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>