[PHP 8.4] Usage of property hooks for true typesafety and autocomplete #55395
Replies: 6 comments 5 replies
-
Hi. But at least it handles the toArray() function, so it is a step forward. But you still have to define the properties in the $fillable property. Also if you cast datetime to Carbon (just as an example), you would have to handle 2 types in the property definition. This active record was not meant to be used as an Entity or DTO. Of course in a happy flow you can use it as you wrote above, but if you want bulletproof solution.... |
Beta Was this translation helpful? Give feedback.
-
Also this code is duplicate {
get => $this->read(__PROPERTY__);
set => $this->write(__PROPERTY__, $value);
} Would be nice if it could be extracted. |
Beta Was this translation helpful? Give feedback.
-
Interesting to see how this would behave: public string | int $created_at {
get => $this->read('created_at');
set => $this->write('created_at', $value);
}
$model->setAttribute('created_at', \now());
dd($model->created_at); Bottom line is that casts will need special handling |
Beta Was this translation helpful? Give feedback.
-
@TimKunze96 if you couple your solution with ours, you would cover also the clashes at the expence of more code/text being needed: #43716 (reply in thread) <?php
namespace App\Models\Attributes;
use MacropaySolutions\LaravelCrudWizard\Models\Attributes\BaseModelAttributes;
///**
// * @property int $id
// * @property string $name
// * @property string $email
// * @property string $password
// * @property string $created_at
// * @property string|null $updated_at
// */
class ExampleModelAttributes extends BaseModelAttributes
{
public int $id {
get => $this->read('id');
set => $this->write('id', $value);
}
public string $name {
get => $this->read('name');
set => $this->write('name', $value);
}
public string $email {
get => $this->read('email');
set => $this->write('email', $value);
}
public string $password {
get => $this->read('password');
set => $this->write('password', Hash::make($value));
}
public string $created_at {
get => $this->read('created_at');
set => $this->write('created_at', $value);
}
public ?string $updated_at {
get => $this->read('updated_at');
set => $this->write('updated_at', $value);
}
// The bellow 2 functions can be moved into the parent class for DRY.
protected function read(string $key): mixed
{
return $this->ownerBaseModel->getAttribute($key);
}
protected function write(string $key, mixed $value): mixed
{
$this->ownerBaseModel->setAttribute($key, $value);
return $value;
}
} In the above the dock-block can be removed and replaced with your solution from the class' body. <?php
namespace App\Models;
use App\Models\Attributes\ExampleModelAttributes;
use MacropaySolutions\LaravelCrudWizard\Models\BaseModel;
/**
* @property ExampleModelAttributes $a
* @mixin ExampleModelAttributes
*/
class ExampleModel extends BaseModel
{
/**
* @inheritdoc
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* @inheritdoc
*/
protected $hidden = [
'password',
];
} You would use it like: $model->a->id; |
Beta Was this translation helpful? Give feedback.
-
This could introduce multiple places for altering the value set => $this->write(__PROPERTY__, Hash::make($value)); because you can also do it in a function password() or via the old settler setPasswordAttrribute($value) |
Beta Was this translation helpful? Give feedback.
-
@TimKunze96 your solution will return null for unset properties (edit "NOT CODED properties") and will not behave like a normal php class would. If you want it to behave like a php class, we implemented it: #55188 (reply in thread) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
With the introduction of PHP 8.4 property hooks got added to the language. With that comes the opportunity to finally archive native type-safety and auto-complete, without the need to write any docblocks!
How does it work?
We just have to define a virtual property for each attribute that we want to be typed, like the example below.
Noticed the different syntax between the get- and set-methods? This is because the shorthand arrow function returns the value and sets it as the new property value. However
setAttribute
does not return just the value set itself, but a reference to the full object. This is why we use the expanded{}
syntax to get around this issue.We could create a new trait that allows us the clean this up a little bit:
This trait is just a simple wrapper around the get/setAttribute functions, with the key difference being that it returns the value that it sets. This allows us to use the shorthand syntax for both the getter and setter, like this:
The last change we can make here is to make use of the
__PROPERTY__
constant, so we don't have to write the property name over and over again:And just like that we get truly typed attributes for all our models!
I tested this approach on a couple of codebases by now and found no conflicts so far. Please let me know if you spot any issues with this
Beta Was this translation helpful? Give feedback.
All reactions