PHP
Starting with Event sourcing
Event sourcing is about storing changes, instead of their result. It’s those changes that make up the final state of a project. Read More
Event sourcing is about storing changes, instead of their result. It’s those changes that make up the final state of a project. Read More
YouTube star Povilas Korop shares and discusses five rules from our guidelines.
Adam Wathan wrote a lenghty post on why and how they are bringing React and Vue support to Tailwind UI Read More
Here’s an excerpt from Event Sourcing in Laravel, an premium course by Spatie launching later this year. Read More
[AdSense-A]
We've released a new major version of spatie/data-transfer-object. This package makes it easy to create objects that have a certain shape. Properties can even be validated.
ð Need self-validating PHP objects? Here you go!https://t.co/osPCSr0qu8
— Freek Van der Herten ð (@freekmurze) April 2, 2021
Gotta love PHP 8
Very nice work by @brendt_gd pic.twitter.com/Sn1mS7R8Sv
In this post I'd like to tell you more the package.
When we original released the data-transfer-object package, it was meant to solve four problems:
Typed properties are added in PHP 7.4, and union types and named arguments in PHP 8. So now that PHP 8 has been released, it makes sense that we leverage PHP's native type check abilities.
The renewed goal of our package is to make constructing objects from arrays of (serialized) data as easy as possible. Here's what a DTO looks like:
use SpatieDataTransferObjectDataTransferObject;
class MyDTO extends DataTransferObject
{
public OtherDTO $otherDTO;
<span class="hljs-keyword">public</span> OtherDTOCollection $collection;
<span class="hljs-comment">#[CastWith(ComplexObjectCaster::class)]</span>
<span class="hljs-keyword">public</span> ComplexObject $complexObject;
<span class="hljs-keyword">public</span> ComplexObjectWithCast $complexObjectWithCast;
<span class="hljs-comment">#[NumberBetween(1, 100)]</span>
<span class="hljs-keyword">public</span> int $a;
}
You could construct this DTO like so:
$dto = new MyDTO(
a: 5,
collection: [
['id' => 1],
['id' => 2],
['id' => 3],
],
complexObject: [
'name' => 'test',
],
complexObjectWithCast: [
'name' => 'test',
],
otherDTO: ['id' => 5],
);
Let's discuss all possibilities one by one.
Constructing a DTO can be done with named arguments. It's also possible to still use the old array notation. This example is equivalent to the one above.
$dto = new MyDTO([
'a' => 5,
'collection' => [
['id' => 1],
['id' => 2],
['id' => 3],
],
'complexObject' => [
'name' => 'test',
],
'complexObjectWithCast' => [
'name' => 'test',
],
'otherDTO' => ['id' => 5],
]);
If a DTO has a property that is another DTO or a DTO collection, the package will take care of automatically casting arrays of data to those DTOs:
$dto = new MyDTO(
collection: [ // This will become an object of class OtherDTOCollection
['id' => 1],
['id' => 2], // Each item will be an instance of OtherDTO
['id' => 3],
],
otherDTO: ['id' => 5], // This data will be cast to OtherDTO
);
You can build your own caster classes, which will take whatever input they are given, and will cast that input to the desired result.
Take a look at the ComplexObject
:
class ComplexObject
{
public string $name;
}
And its caster ComplexObjectCaster
:
use Spatie<span class="hljs-title">DataTransferObject<span class="hljs-title">Caster;
class ComplexObjectCaster implements Caster
{
/**
* @param array|mixed $value
*
* @return mixed
*/
public function cast(mixed $value): ComplexObject
{
return new ComplexObject(
name: $value['name']
);
}
}
Instead of specifying which caster should be used for each property, you can also define that caster on the target class itself:
class MyDTO extends DataTransferObject
{
public ComplexObjectWithCast $complexObjectWithCast;
}
#[CastWith(ComplexObjectWithCastCaster::class)]
class ComplexObjectWithCast
{
public string $name;
}
It's possible to define default casters on a DTO class itself. These casters will be used whenever a property with a given type is encountered within the DTO class.
#[
DefaultCast(DateTimeImmutable::class, DateTimeImmutableCaster::class),
DefaultCast(Enum::class, EnumCaster::class),
]
abstract class BaseDataTransferObject extends DataTransferObject
{
public MyEnum $status; // EnumCaster will be used
<span class="hljs-keyword">public</span> DateTimeImmutable $date; <span class="hljs-comment">// DateTimeImmutableCaster will be used</span>
}
This package doesn't offer any specific validation functionality, but it does give you a way to build your own validation attributes. For example, NumberBetween
is a user-implemented validation attribute:
class MyDTO extends DataTransferObject
{
#[NumberBetween(1, 100)]
public int $a;
}
It works like this under the hood:
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
class NumberBetween implements Validator
{
public function __construct(
private int $min,
private int $max
) {
}
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validate</span><span class="hljs-params">(mixed $value)</span>: <span class="hljs-title">ValidationResult</span>
</span>{
<span class="hljs-keyword">if</span> ($value < <span class="hljs-keyword">$this</span>->min) {
<span class="hljs-keyword">return</span> ValidationResult::invalid(<span class="hljs-string">"Value should be greater than or equal to {$this->min}"</span>);
}
<span class="hljs-keyword">if</span> ($value > <span class="hljs-keyword">$this</span>->max) {
<span class="hljs-keyword">return</span> ValidationResult::invalid(<span class="hljs-string">"Value should be less than or equal to {$this->max}"</span>);
}
<span class="hljs-keyword">return</span> ValidationResult::valid();
}
}
We use spatie/data-transfer-object in our bigger projects. We hope that this package can be handy for you as well. The package has a few more features, to learn them, head over to the readme on GitHub.
The principal author of this package is my colleague Brent, who, like always, did an excellent job.
Want to see more packages that our team has created? Head over to the open source section on spatie.be
(more…)What Is Form Validation? Form validation is a process to check where the information provided by the user is met our requirements or not.That is provided data is in right format or not. Before Submitting Read more…
[AdSense-A]
When you cancel a long-running artisan command with Ctrl+C
, a SIGINT
signal is sent by the operating system to the PHP process. You can use this signal to perform some cleanup quickly.
Symphony 5.2 introduced support for handling signals in commands.
We've released a package called spatie/laravel-signal-aware-commands that provides a substantial improvement to how you can use these signals in a Laravel app. In this blog post, I'd like to tell you all about it.
As Laravel's artisan commands are based on Symphony, signals can be handled in Laravel as well. You should simply add the SignableCommandInterface
and implement the getSubscribedSignals
and handleSignal
methods.
Thanks to development by the upstream Symfony team, Artisan inherits a cool signal handling feature. Decided to document it today! â¤ï¸ ð¤ pic.twitter.com/4IG3ZdWGTI
— Taylor Otwell ðª (@taylorotwell) April 1, 2021
That's basically all you need to do to make commands signal aware, but it doesn't feel very "Laravel-y" to me.
Ideally, I just want to add a function like onSigint
to the command and put the signal handling code there. That feels much lighter.
// in an artisan command class
protected $signature = 'your-command';
public function handle()
{
$this->info('Command started...');
sleep(<span class="hljs-number">100</span>);
}
public function onSigint()
{
// will be executed when you stop the command
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'You stopped the command!'</span>);
}
Something that I found lacking in the code that Symfony provides is that only the command itself is aware of signals. Wouldn't it be nice if we could handle signals anywhere in the app, like this?
Signal::handle(SIGINT, function() {
// handle the signal
});
This seemed like a nice thing for me to work on, so I decided to create a package that would add these niceties.
In this stream, you can see me build the foundations of spatie/laravel-signal-aware-command
Making commands signal aware is easy. When the package is installed, there are three ways to handle signals:
Signal
facadeSignalReceived
eventLet's start with making a command signal aware. You need to let your command extend SignalAwareCommand
. Next, define a method that starts with on
followed by the name of the signal. Here's an example where the SIGINT
signal is handled.
use SpatieSignalAwareCommandSignalAwareCommand;
class YourCommand extends SignalAwareCommand
{
protected $signature = 'your-command';
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'Command started...'</span>);
sleep(<span class="hljs-number">100</span>);
}
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onSigint</span><span class="hljs-params">()</span>
</span>{
<span class="hljs-comment">// will be executed when you stop the command</span>
<span class="hljs-keyword">$this</span>->info(<span class="hljs-string">'You stopped the command!'</span>);
}
}
The above code will make the command signal aware. But what if you want to handle signals in other parts of your code? In that case, you can use the Signal
facade. First, you need to define the signals you want to handle in your command in the handlesSignals
property.
use Spatie<span class="hljs-title">SignalAwareCommand<span class="hljs-title">SignalAwareCommand;
class YourCommand extends SignalAwareCommand
{
protected $signature = 'your-command';
<span class="hljs-keyword">protected</span> $handlesSignals = [SIGINT];
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
(<span class="hljs-keyword">new</span> SomeOtherClass())->performSomeWork();
sleep(<span class="hljs-number">100</span>);
}
}
In any class you'd like, you can use the Signal
facade to register code that should be executed when a signal is received.
use Illuminate<span class="hljs-title">Console<span class="hljs-title">Command;
use Spatie<span class="hljs-title">SignalAwareCommand<span class="hljs-title">Facades<span class="hljs-title">Signal;
class SomeOtherClass
{
public function performSomeWork()
{
Signal::handle(SIGINT, function(Command $commandThatReceivedSignal) {
$commandThatReceivedSignal->info('Received the SIGINT signal!');
})
}
}
You can call clearHandlers
if you want to remove a handler that was previously registered.
use Spatie<span class="hljs-title">SignalAwareCommand<span class="hljs-title">Facades<span class="hljs-title">Signal;
public function performSomeWork()
{
Signal::handle(SIGNINT, function() {
// perform cleanup
});
<span class="hljs-keyword">$this</span>->doSomeWork();
<span class="hljs-comment">// at this point doSomeWork was executed without any problems</span>
<span class="hljs-comment">// running a cleanup isn't necessary anymore</span>
Signal::clearHandlers(SIGINT);
}
To clear all handlers for all signals use Signal::clearHandlers()
.
A third way of handling signal is by listening for the SignalReceived
event.
use Spatie<span class="hljs-title">SignalAwareCommand<span class="hljs-title">Events<span class="hljs-title">SignalReceived;
use Spatie<span class="hljs-title">SignalAwareCommand<span class="hljs-title">Signals;
class SomeOtherClass
{
public function performSomeWork()
{
Event::listen(function(SignalReceived $event) {
$signalNumber = $event->signal;
$signalName = Signals::getSignalName($signalNumber);
$event->command->info(<span class="hljs-string">"Received the {$signalName} signal"</span>);
});
}
}
I hope you agree that these three ways of handling signals feels much better than adding the SignableCommandInterface
and implement the getSubscribedSignals
and handleSignal
yourself.
To see a practical example of this package, can take a look at this commit in the laravel-backup package.
The laravel-backup package creates a backup in the form of a zip file that contains DB dumps and a selection of files. This zip is created inside of a temporary directory. Should the user cancel a running backup command, we'll not use the SIGINT
signal to delete that temporary directory.
Pretty nice, right?
After creating the package, some people have asked why I didn't immediately PR this to Laravel. The answer is that creating a package is much faster for me to do. When creating a package, I basically have a blank canvas. I can implement functionality in whichever fashion I want. I can also make use of PHP 8 features. When the package is finished, I can immediately tag a release and start using it.
If I would PR this to Laravel, I would keep Laravel's coding practices in mind, use an older version of PHP. The PR would probably be in review for a couple of days without the guarantee of actually being merged.
The above paragraph isn't a criticism on Laravel. It's just the way it is. It's good that things are getting review and that there is a discussion on features that are added.
I hope that by creating a package, somebody from the community or the Laravel team takes the time to PR some of the functionality to Laravel, making my package obsolete.
Until that happens, you can use my package.
Right now, the package requires you to extend the SignalAwareCommand
command instead of the regularCommand
. Some people frown upon this, and rightly so. Putting functionality in base classes is some cases not flexible, and in the Laravel world, it seems that use traits is preferred. Generally speaking, I prefer this too, as a class can only be extended from one other class, but you can have as many traits as you want.
In this case, I opted for the package offering an abstract SignalAwareCommand
that should be extended instead of a trait because this way, other parts of the code could be hidden.
Let's imagine the package would offer a trait instead of a base class. This is would people would have to use it.
class TestCommand extends Command
{
use HandlesSignals;
<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">()</span>
</span>{
<span class="hljs-keyword">parent</span>::construct();
<span class="hljs-comment">// signals must be known when the command is constructed</span>
<span class="hljs-keyword">$this</span>->registerSignalsToBeHandled();
}
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
<span class="hljs-keyword">$this</span>->registerSignalHandlers();
<span class="hljs-comment">// your code</span>
}
}
In my mind, this would undermine the better DX that the package tries to achieve.
I bet that if the ideas of this package make it into Laravel, that the necessary changes could be added directly into IlluminateConsoleCommand
, making DX even better.
To know more about spatie/laravel-signal-aware-command, head over the the readme on GitHub.
This isn't the first package our team has created. Here's a list of packages we released previously. I'm pretty sure there's something there for your next project.
If you want to support our team creating open-source, consider becoming a sponsor, or picking up one of our paid products.
(more…)[AdSense-A]
In this stream, you'll see me lay the foundations for the spatie/laravel-signal-aware-command package.
(more…)A few days ago, you might have received a Dependabot security warning on Ignition concerning a remote code execution vulnarablity. This post on the Flare blog explains why most people shouldn’t be effected by this. Read more…
In this tutorial, weâll be looking at how to get started using Pest in a Laravel project. Using the test-driven approach, weâll be building a simple to-do application that allows you to create, edit, update Read more…