grifart / not-serializable
State explicitly that class has not been meant for serialization.
Requires
- php: >=7.4
Requires (Dev)
- nette/tester: ^2.4
- phpstan/phpstan: ^1.4
This package is auto-updated.
Last update: 2024-10-29 06:13:12 UTC
README
...and this package makes it easier for you.
A story
In 3/2022 this has happened.
class UserId { private int $id; public function __construct(int $id) {$this->id = $id} } // later in an app $userId = new UserId(42); // ... $_SESSION['user_id'] = $userId;
This will work. No warning, no errors, no problems. For now...
...
After a year you have decided to do internal change of UserId
object β rename private property $id
to $identifier
.
class UserId { - private int $id; + private int $identifier; public function __construct(int $id) { - $this->id = $id + $this->identifier = $id } }
There is no public change of behaviour. There is no way outer observer should be able to find any changes in object behaviour. Static analysis passes, tests passes. All good! You deploy your application.
Boom! π₯ Application hard crashed for every user that has been logged-in on session deserialization. That is because implicit serialization made public interface from a private property.
Summary
Implicit serialization support is an explosive mine π₯, that you cannot easily spot from the surface or even in code-review. You have to inspect whole object life-cycle including code history. Almost impossible to spot.
This is very similar to disabling inheritance support from object which was not explicitly designed to be extended β final
by default. You can find good reasoning at 1, 2.
TL;DR: You get much better object encapsulation, you know exactly how you object behaves.
The solution
π Replace implicit serialization with the explicit one.
1. Disabling implicit serialization
In PHP every object is serializable by default, until you say otherwise. So let's say otherwise.
This makes most sense for value objects and all its derivatives like entities. There is however no harm by disabling serialization support for all classes as you would never-ever want to serialize a service.
Open you PhpStorm settings and then Editor > File and Code Templates > Files > PHP Class
and update the template.
final class ${NAME} { use \Grifart\NotSerializable\NoSerialization; }
Require this package using composer in your project
composer require grifart/not-serializable
It contains a simple trait, that disables serialization support for PHP >7.4. That is because serialization API has been changed php-watch.
2. Making the serialization explicit
When you need serialization support, implement serialization API explicitly.
Great thing is that when it is not possible to serialize by default, someone has to make a decision that we need it be serializable.
This allows us also choose right type of serialization:
a) Short term & simple
PHP serialization API: Allows you to quick start. Breaks when you start moving your classes around (heavily depends on our classes FQNs).
You can go this way by:
- removing the trait
- implementing
__serialize()
and__unserialize()
methods.
b) Long term serialization
grifart/stateful: Provides strict versioning, field checking and FQN routing. Designed for:
- serialization that can be deserialized after years (e.g. immutable event streams)
- situation when codebase changes significantly
- for maximal strictness, so it fails on you dev machine, not on production.
You can go this way by:
- removing the trait
- implementing
Stateful
interface &composer require
grifart/stateful
Further development
- PhpStan / Rector rule, that finds/fixes classes that does not have serialization disables and does NOT implement serialization explicitly (How to apply this only to value objects & derivatives?)