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:
- Extend the
Str
object with your own object that includes your new helper. - Create a new Str helper that just has your new helper within it (something like
ProjectSpecificStrHelper
), then using both of them when you need. - 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!