gugglegum / abstract-entity
Abstract class for entities (models)
Requires
- php: >=7.0.0
Requires (Dev)
README
This class is used as a base class for classes with entities (models). An entities extended from
this base class will get the methods to work with attributes through getters and setters. The
constructor method allow to pass an array which will be used to initialize new object. It also
contains getGetter()
and getSetter()
methods to convert an attribute name to getter and setter
methods. For every entity class property (which should be private) you need to generate a pair of
getter and setter. You may use automatic generation of these methods in your IDE (I use the PhpStorm).
The abstract entity gets a list of attributes of concrete entity from its properties (using
ReflectionClass
). You may redefine this method to add some additional handling. You may add some virtual
attributes which are not present as class properties or remove some special properties which should not be
used as attributes.
Release notes for version 1.1.0
This minor version update contains many awesome improvements. The most important thing is that I found a bug in previous versions related to static variable inside class methods getAttributeNames()
and hasAttribute()
. They were used to cache list of entity attributes which are initially retrieved via \ReflectionClass
from all parent classes. This is potentially slow operation, this is why I cached them on class level. But these static variables may result unexpected behavior if static variable in parent class initialized before child class was loaded, i.e. reproducing of this error is related to the order your classes are loaded and used. And usually you don't control this order due to use of class auto-loaders. This is why it was hard to debug this error.
I had some suspicions about it, I started to write unit tests to cover all code. First time all was looking good, so I calmed down a little. But then it broke in a very unexpected place. It was a result of unit test execution in some special order. PHPUnit executes all tests in one system process, so first tests may affect latest tests. I investigated this and made a special tests for reproducing this bug.
Then I used these tests to ensure that bug completely fixed. I decided to completely refuse static variables inside class methods as they are very unpredictable and can't be controlled in some cases. Now I use class static properties with 2-dimension array where first level is name of concrete model class.
Also there's some other improvements:
- Added possibility to set user-defined exception class for all exceptions generated by AbstractEntity. This may be very useful when you make some component and you don't want to give away details of the internal implementation of third-party components. Your component usually have its own exception class and this is all what user using your component need to know. User shouldn't keep in mind about AbstractEntity you used to catch all exceptions from your component.
- The
getAttributeNames()
method now returns only non-static properties, i.e. if your model class or it's parent class has static property -- it will not be included in list. - The
hasAttribute()
now is a static method. Being static you still able to call it via non-static calls. So this don't break backward compatibility. - Added PHPUnit tests, old test directory removed.
- Added examples of usage.
Requirements
This class written to be executed on PHP version 7.0 or better. It uses define(strict_types=1)
and scalar types in methods definition.
Example
An entity class example:
<?php
declare(strict_types=1);
use gugglegum\AbstractEntity\AbstractEntity;
class User extends AbstractEntity
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $email;
/**
* @var bool
*/
private $isAdmin = false;
/**
* @var bool
*/
private $disabled = false;
/**
* @return string|null
*/
public function getName(): ?string
{
return $this->name;
}
/**
* @param string|null $name
* @return self
*/
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return string|null
*/
public function getEmail(): ?string
{
return $this->email;
}
/**
* @param string|null $email
* @return self
*/
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
/**
* @return bool
*/
public function isAdmin(): bool
{
return $this->isAdmin;
}
/**
* @param bool $isAdmin
* @return User
*/
public function setIsAdmin(bool $isAdmin): self
{
$this->isAdmin = $isAdmin;
return $this;
}
/**
* @return bool
*/
public function isDisabled(): bool
{
return $this->disabled;
}
/**
* @param bool $disabled
* @return self
*/
public function setDisabled(bool $disabled): self
{
$this->disabled = $disabled;
return $this;
}
}
Test usage:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/User.php';
$user = User::fromArray([
'name' => 'John',
'email' => 'john@example.com',
'isAdmin' => false,
'disabled' => true,
]);
var_dump($user->toArray());
If you would like to use your own exception class in your models, the best solution is to define it in the
constructor of your models. But note that you should set exception class prior to call parent::__construct($data)
because parent constructor may throw an exceptions.