Laravel has always come bundled with some rather useful helpers, namely the string and array helpers.

These functions are all included in their respective classes Arr and Str within the Illuminate\Support namespace.

But what if we want to add more helpers to these classes? What if we want add a start method to the Str class, to do similar to the finish method and ensure that a string starts with a given value?

You may be thinking one of the following:

  1. Extend the Str object with your own object that includes your new helper.
  2. Create a new Str helper that just has your new helper within it (something like ProjectSpecificStrHelper), then using both of them when you need.
  3. Use the built in Macro functionality.

If you picked answer 3, you would be going along the right lines.

Macros

Both of the helpers within Laravel use a Macroable trait. This trait simply uses php's magic method __callStatic along with a static function to register macros.

You can call Str::macro('start', function($str, $start) { // Implementation Logic ... }); in a bootstrap file somewhere and then simply call Str::start('ello World', 'H'); within your application to use the macro you created.

These 'macros' do have some limitations. As you create them with a closure (or callable) outside of the class you are adding them to, they do not have access to $this->, static::, or self::. As we are dealing with helpers, this is not too much of an issue.

The other common question is where to put these extensions.

Where should I put helper macros?

The way I have solved this issue is as follows.

Macros As Classes

My solution for this issue is to create all of my macros as individual classes. This has a number of benefits.

  • Access to $this within your class.
    • Enables depencency injection to use other objects within your helper!
  • Can be registered easily through a service provider.
  • Easy to find, extend, and/or replace at a later date.
  • Macros can be stores in their own namespace structure anywhere you like (I use App\Extensions\Macros.

The Class

The class template I use for my macro extensions is as follows.

<?php

namespace App\Extensions\Macros;

class StrStarts  
{
    public $name = 'starts';

    /**
     * Ensure a string starts with a given value.
     */
    public function macro($str, $starts, $caseSensitive = true)
    {
        // Copied from danielstjules/Stringy
        $substringLength = mb_strlen($starts);

        $startOfStr = mb_substr($str, 0, $substringLength, $this->encoding);

        if (!$caseSensitive) {
            $starts = mb_strtolower($starts);
            $startOfStr = mb_strtolower($startOfStr);
        }

        if ($starts === $startOfStr) {
            return $str;
        }

        return $starts.$str;
    }
}

It's as simple as that. All of my macro extensions simply must have a public macro method.

The macro method is the implementation of your helper function, its paramaters are the ones you should expect to pass into the helper call (the above could be called through the Str helper like so; Str::starts('ello World', 'H');)

The Service Provider

The service provider is nice and simple.

<?php

namespace App\Providers;

use Illuminate\Support\Srt;  
use Illuminate\Support\Arr;  
use App\Extensions\Macros\StrStarts;

class MacroExtensionServiceProvider extends ServiceProvider  
{
    /**
     * A multi dimensional array of helpers and their extensions and names.
     *
     * @var array
     */
    protected $macros = [
        Str::class => [
            // String Helper Extensions ...
            'starts' => StrStarts::class,
        ],
        Arr::class => [
            // Array Helper Extensions ...
        ]
    ];

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        // Loop over the helpers to extend.
        foreach ($this->macros as $class => $extensions) {

            // Loop over the extensions registered for the helper.
            foreach ($extensions as $name => $extension) {

                // Create an instance of the extension using the app.
                $extensionObject = $this->app->make($extension);

                // Call the macro class on the helper.
                // Passing in a callable to the helper extension.
                call_user_func_array($class.'::macro', [
                    $name,
                    [$extensionObject, 'macro']
                ]);
            }
        }
    }
}

With the above complete and the provider registered in your app config, you will now be able to use Str::starts through out your application.

I hope this article has been useful to anyone reading it. If you have any questions, please feel free to ask them below in the comments!

© 2024. All Rights Reserved.