maer / entity
Create predefined entity objects
Installs: 1 686
Dependents: 2
Suggesters: 0
Security: 0
Stars: 5
Watchers: 2
Forks: 0
Open Issues: 0
Requires
- php: >=7.1
Requires (Dev)
- phpunit/phpunit: ^7.5
- squizlabs/php_codesniffer: ^3.4
README
This version contains breaking changes and aren't compatible with version 1.x
Instead of passing around entities as arrays or instances of StdClass, where you never can trust if a specific key or property exists, or that they contain the correct datatype, it's better to use predefined entity objects.
I created this class when I was building a REST-api where I had data from different sources. MySQL, for example, defaults to returning all values as strings.
There are other situations where one parameter from the data source might have changed name, or been removed. Then it's nice if you don't need to go through all your code and change it where ever it's being used.
Install
Clone this repository or use composer to download the library with the following command:
$ composer require maer/entity
Usage
Define an entity
When defining an entity, you start create a new class which extends Maer\Entity\Entity
:
class Hero extends Maer\Entity\Entity {}
Default property values and types
Creating an empty entity isn't that exiting. We should define some properties and default values:
class Hero extends Maer\Entity\Entity { protected $id = 0; protected $name = ''; protected $awesome = false; protected $someFloat = 0.0; protected $anything = null; }
When you define a default value, it's important that you set the default values to the correct data type. Later on when you're setting/updating a property value, the new value will be cast to the same data type as the default. The data types that are supported for automatic casting are: integers
, strings
, booleans
and floats/doubles
. If the default value is null
, it can be set to anything.
Protect properties
If you want to be able to use a property in your code, but don't want to expose it in, say, an API response, you can "protect" it. An example would be a user entity having a password hash. To protect (remove) a property on JSON serialization or when fetching it as an array, you can protect using the protect()
-method like this:
class User extends Maer\Entity\Entity { protected $id = 0; protected $name = ''; protected $passwordHash = ''; protected function protect() : array { return [ 'passwordHash', // Keep adding property names to protect, if needed ]; } }
Remap properties
Sometimes the source array might have different key names than the defined entity params. To make it as easy as possible for you, you can map param names and your entity will automatically remap them upon instantiation.
class User extends Maer\Entity\Enity { protected $username = ''; protected function map() : array { // Assign the map as ['entityPropName' => 'sourcePropName'] return [ 'username' => 'email', ]; } }
If you now send in an array with a email
key, the value will be mapped as username
.
Remap nested properties
If you want to map a value in a multidimensional array, you can do just as above, but using dot notation as map key.
class User extends Maer\Entity\Enity { protected $username = ''; protected function map() : array { return [ 'username' => 'user.username', ]; } }
This will map ['user' => ['username' => 'Chuck Norris']]
as just username
. There is no limit on how many nested levels you can go.
Instantiate an entity
You can instantiate an entity in several ways, depending on your needs.
Create a default entity
Since it's a class, you can create a new entity with the default values like this:
$hero = new Hero(); echo $hero->id; // Returns: 0, just as we defined earlier.
You can also set new values for it by passing an array to the constructor:
$hero = new Hero([ 'id' => 1337, ]); echo $hero->id; // Returns: 1337, just as we defined earlier.
Just remember that the values will be cast to the same datatype as the default value.
Convert an array to entity
When creating just one entity, you can use the above constructor method, or you can use the static Entity::make()
method:
$hero = Hero::make([ 'id' => 1337, ]); echo $hero->id; // Returns: 1337
Convert a multidimensional array to a list of entities
The static Entity::make()
method is a bit more clever and can do more than just give you one entity. For example, if you pass a multidimensional array, it will give you a Collection-instance with entities back:
$dataset = [ [ 'id' => 1337, 'name' => 'Chuck Norris', ], [ 'id' => 12345, 'name' => 'Some guy', ], ]; $heroes = Hero::make($dataset); echo $heroes[0]->id; // Returns: 1337
You can also define what property should be used as the array key, making it an associative array.
$heroes = Hero::make($dataset, 'id'); echo $heroes[1337]->name; // Returns: "Chuck Norris"
Get an array instead of collection
If you rather want the make()
-method to return an array instead of a Collection-instance, you can pass true
as the fourth argument:
$heroes = Hero::make($dataset, null, null, true);
Modify values on instantiation
Sometimes you get a list of values which needs to be modified before you create the entity. In this example, we will show an example of how we can prepend http://
in front of an URL, in case it is missing:
Propose that we have an entity and dataset looking like this:
class Website extends Maer\Entity\Entity { protected $title = ''; protected $url = ''; } $dataset = [ 'title' => 'Google', 'url' => 'www.google.com', ]; $website = new Website($dataset); echo $website->url; // Returns: "www.google.com"
Sure, we could add http://
before we instantiate the entity, but that would require us to repeat it where ever we instantiate the entity. It would also mean that we would need to manually iterate through the dataset, if it is a list of websites.
Modifier on instantiation
Luckily, we can send in a modifier in form of a closure upon entity creation:
$website = new Website($dataset, function (array &$params) { if (isset($params['url']) && strpos($params['url'], 'http://') !== 0) { // We got a parameter called url that doesn't start with http:// $params['url'] = 'http://' . $params['url']; } });
As you can see, the closure will get the $params
-array as reference, meaning that it doesn't need to return anything.
You can do the same using the static Entity::make()
method:
$website = Website::make($dataset, null, function (array &$params) { // ... Modifie the values like the above example });
Global modifier
Using a closure works well if you want to add a modifier for some specific instances. However, if you want to use your modifier for every instance, you can use the modified()
-method instead:
class Website extends Maer\Entity\Entity { protected $title = ''; protected $url = ''; protected function modifier(array $params) { if (isset($params['url']) && strpos($params['url'], 'http://') !== 0) { // We got a parameter called url that doesn't start with http:// $params['url'] = 'http://' . $params['url']; } } } $dataset = [ 'title' => 'Google', 'url' => 'www.google.com', ]; $website = new Website($dataset); // Or $website = Website::make($dataset); echo $website->url; // Returns: "http://www.google.com"
Note: If you have a global modifier()
-method and still send in a modifier upon instantiation, the global modifier will be called first and your instance-specific modifier last.
Helper methods
Check if a property exists
If you try to get or set a property that doesn't exist, an \InvalidArgumentException
will be thrown, except from when the entity is created through the new Entity
or Entity::make()
-method or when you use the replace()
-method.
To check if a property exists, use the has()
-method:
if ($hero->has('name')) { // Yes, it exists and can be used } else { // No, doesn't exist. Try to get/set it, an exception will be thrown }
The first argument is the name of the property holding the value, the second argument is the format (defaults to: "F j, Y").
This method also works for properties holding unix timestamps.
Convert an entity to arrays
If you need to convert an entity to array, use asArray()
:
$array = $hero->asArray(); // Return: ['id' => 1337, 'name' => 'Chuck Norris', ...]
All properties returned by the protect()
-method will be removed.
Convert an entity to JSON
The Entity
-class implements the JsonSerializable
-interface, so just use json_encode()
:
$json = json_encode($hero); // Return: "{"id": 1337, "name": "Chuck Norris", ...}"
All properties returned by the protect()
-method will be removed.
Replace entity data
Sometimes you want to update the data in an entity. If it's just a few values, then doing $entity->foo = 'bar';
will work fine, but if you have larger entities with a lot of properties you want to replace, you can use the replace()
-method:
// New data $data = [ 'name' => 'Batman', ... ]; $entity->replace($data); // $entity->name now has the value "batman"
This will replace the existing entity-properties with the data from the array.
You can also pass in a modifier:
// New data $data = [ 'name' => 'Batman', ... ]; $entity->replace($data, function (array $params) { // Modify the data }); // $entity->name now has the value "batman"
Reset an entity
If you, for what ever reason, need to reset an entity to it's default values, use the reset()
-method:
$entity->reset();
Reset a property
If you just want to reset a specific property to it's default value, use the resetProperty()
-method:
$entity->resetProperty('nameOfTheProperty');
Create your own helpers
You can of course create your own helpers:
class Hero extends Maer\Entity\Entity { protected $name = ''; public function myNewHelper() { // Do stuff... } }
And just access it like any other method: $hero->myNewHelper()
Collections
When you use the make()
-method to create multiple entities at the same time, you will get an instance of Maer\Entity\Collection
back.
This class can be used as an array (implementing the ArrayAccess and Countable interfaces).
This class has some helper methods on it's own.
Count
Get the collections entity count:
echo $collection->count(); // same as count($collection)
Get the first element
To get the first element in the collection, call the first()
-method:
$firstElement = $collection->first(); // $firstElement now contains the first entity in the collection
Get the last element
To get the last element in the collection, call the last()
-method:
$firstElement = $collection->last(); // $firstElement now contains the last entity in the collection
Get a list of property values
If you want to get all values (from all entities in a collection) from a single property:
$names = $collection->list('username'); // Will give you something like: // [ // 'firstUsername', // 'secondUsername', // 'thirdUsername', // ... // ]
You can also define what property to use as index by passing the property name as a second argument:
$names = $collection->list('username', 'id'); // Will give you something like: // [ // 1337 => 'firstUsername', // 1234 => 'secondUsername', // 555 => thirdUsername', // ... // ]
Sort the entities
You can sort the entities in a collection by using the usort()
-method:
$collection->usort(function ($a, $b) { return $a->name <=> $b->name; });
This method works exactly like the core function usort() (since it's using it in the background).
Remove an entity
To remove an entity from the collection, use the unset()
-method:
$collection->unset('theEntityIndex');
Add-ons
In version 1, you had the helper method date()
in the base entity class. Since version 2, all helper methods are traits which you need to add to your entities yourself. This is to keep the entity classes as lean as possible.
DateTimeTrait
The trait Maer\Entity\Traits\DateTimeTrait
has some date-methods to make things a bit easier.
date
Get a property as a formatted date string:
class Foo extends Maer\Entity\Entity { protected $created = '2019-06-10 13:37:00'; // Include the trait use Maer\Entity\Traits\DateTimeTrait; } $foo = new Foo(); // First argument is the property to use echo $foo->date('created'); // Returns: June 10, 2019 // The second argument is the datetime format (standard PHP-date formats) echo $foo->date('created', 'Y-m-d'); // Returns: 2019-06-10 // If you want to use a specific timezone, pass it as a third argument echo $foo->date('created', 'Y-m-d', 'Europe/Stockholm');
dateTime
If you rather get a DateTime-instance back, use:
// First argument is the propety to use $date = $foo->dateTime('created'); // Returns an instance of DateTime // If you want to use a specific timezone, pass it as a second argument $date = $foo->dateTime('created', 'Europe/Stockholm');
timestamp
If you rather get the date as a timestamp, use:
// First argument is the propety to use $timestamp = $foo->timestamp('created'); // Returns: 1560166620 // If you want to use a specific timezone, pass it as a second argument $timestamp = $foo->timestamp('created');
TextTrait
The trait Maer\Entity\Traits\TextTrait
has some text-methods to make things a bit easier.
excerpt
If you just want to display an excerpt of some text, you can use the excerpt()
-method:
class Foo extends Maer\Entity\Entity { protected $content = 'Here be some lorem ipsum text'; // Include the trait use Maer\Entity\Traits\TextTrait; } $foo = new Foo(); $maxLength = 20; $suffix = '...'; echo $foo->excerpt('content', $maxLength, $suffix); // Returns: Here be some...
The length of the returned string can be less than the max length, but never more. The method makes sure no words are cropped and that the suffix fits within the max length.
The defaults are 300 for max length and "..." as suffix.
Changes in version 2
Version 2 was completely rebuilt to use less memory and to be a bit faster. When tested creating 10.000 entities, it was just slightly faster, but the used memory was about 2-3 times less.
Quick overview of the biggest changes:
- Properties are now defined as protected class properties instead of
$_params = []
- Entity::make() now returns an instance of Maer\Entity\Collection as default instead of an array
- Helper methods, like
$entity->date()
, has moved to traits and are no longer included in the base entity - Settings, like map and protect, are now methods returning arrays instead of class properties
Note
If you have any questions, suggestions or issues, let me know!
Happy coding!