[!IMPORTANT] dot-mapper is a wrapper on top of laminas/laminas-db
[!CAUTION]
Security-Only Maintenance Mode
This package is considered feature-complete, and is now in security-only maintenance mode.
DotKernel backend abstraction that implements the Data Mapper pattern. It does not offer a full ORM solution but rather a middle-ground solution for entity to database transfer with the possibility to be used with relationships too.
Run the following command in your project root directory
$ composer required dotkernel/dot-mapper
Entities are part of the domain model. They model real life objects or concepts. Usually in a web application they model the database objects and their relationships.
In DotKernel we like to keep entities simple and as close to the database structure as possible. Usually they will model a table's row and can be composed into more complex objects via object composition
To use our mapper package to save entities to the database, you have to implement the EntityInterface
interface or better, extend the Entity
class.
Among some utility methods defined for object property handling, the Entity class defines internally the laminas hydrator that is to be used when fetching or saving data to the backend.
The mappers do this automatically when using the CRUD operations.
To set the prefered hydrator, you can extend the Entity class and override the protected $hydrator
property with the class name of the desired hydrator. The hydrator has to be registered in the hydrator manager beforehand.
We give below an entity example
namespace SomeNamespace;
use Dot\Mapper\Entity\Entity;
//...
class MyEntity extends Entity
{
protected $id;
protected $field1;
protected $field2;
/**
* This field is inherited from the base Entity class and it indicates to the mappers what hydrator to be used
* The default value is ClassMethodsCamelCase so you don't have to write the following line if you are using that
*/
protected $hydrator = ClassMethodsCamelCase::class;
//...
public function getField1()
{
return $this->field1;
}
public function setField1($value)
{
$this->field1 = $value;
}
// etc...
}
DotKernel mappers must implement the MapperInterface
which defines the basic CRUD operations along with some specific database functions like transaction management, generated value and so on.
We already provide an abstract mapper implementation for SQL databases. The abstract mapper is based on functions offered by laminas-db. We'll implement other types of backend support in the future.
If you use an SQL database, in order to create a mapper, you should extend the AbstractDbMapper
class. You don't have to write any database related code if all you need is CRUD operations. We'll detail later in this lesson how to create more complex mappers.
The following mapper example is all you need if you want to select, insert, update or delete an entity.
//...
class MyEntityDbMapper extends AbstractDbMapper
{
// an empty db mapper does support CRUD operations and has already implemented the MapperInterface's methods
}
public function find(string $type = 'all', array $options = []): array
findFinderName
where FinderName is the name of the finder. There is a findAll finder defined by default that will leave the select query intact. You can defined custom finder methods too in order to modify the select for your needs. To specify which finder to use, the find
method's first parameter is the $type
. Parameters:
type
- the finder method to useoptions
- an array containing find options.fields
- the column/field names to select from the databaseconditions
- where conditions using boolean AND. For more complex conditions, you should use the custom finder method or define your own mapper method.group
- group by select clausehaving
- having select clauseorder
- order by clauselimit
- limit number of resultsoffset
- offset where the select should startpage
- alternative to limit/offset pair(it will be converted to them internally)joins
- array of join conditions. This needs to be detailed belowjoins
key of the find options array. The joins options must be an array of join configurations. The join format is as following$options['joins'] = [
'join_table_alias[optional]' => [
'table' => 'joined table name',
'on' => 'ON condition as string',
'fields' => 'joined table fields to select',
'type' => Select::INNER_JOIN,
],
//...
];
public function count($type = 'all', array $options = []): int
-1
public function get($primaryKey, array $options = [])
find
method and limits the result to one element which is returned. If no element were found, the return will be null
.find
method with one additional supported parameter, finder
which you can use to specify which finder method the find
will use.public function save(EntityInterface $entity, array $options = [])
atomic
options($options['atomic'] = true|false) which you can use to toggle atomic save operation. It enabled the query will be wrapped in a transaction(on by default)public function delete(EntityInterface $entity, array $options = [])
public function deleteAll(array $conditions)
public function updateAll(array $fields, array $conditions)
public function newEntity(): EntityInterface;
public function lastGeneratedValue(string $name = null);
public function getPrototype(): EntityInterface;
public function getHydrator(): HydratorInterface;
Other methods will be described in the advanced mapper usage section. Their availability might be conditioned by the underlying backend engine used.
All defined mappers have to be registered and fetched from the mapper manager. The mapper manager is a special case of service container. It is an instance of the AbstractPluginManager
type provided by Laminas Service Manager.
The mapper manager is responsible for proper mapper initialization. Another feature is mapper caching - multiple calls to a mapper will result in just one initialization. The mapper manager initializes the mapper following the configuration set including the backend adapters, associated entity, event listeners and so on.
You'll use the mapper manager's single public method: get($name, array $options = null)
;
To access the mapper manager you can inject it manually in your classes by fetching it from the container.
$container->get(MapperManager::class);
//OR
$container->get('MapperManager');
OR you can implement the MapperManagerAwareInterface
along with the MapperManagerAwareTrait
. This way you won't need to inject it yourself, and if this is the only dependency needed, you won't have to define a factory class because the mapper manager will be automatically injected by an initializer.
The mapper configuration structure is as follows
return [
'dot_mapper' => [
'mapper_manager' => [
'factories' => [
//...
],
'aliases' => [
//...
]
]
]
];
Even though it is a regular laminas service plugin manager, in the case of mappers the mapper registration needs to be defined more strictly. Let's see next how to set up a mapper.
DbMapperFactory
in case of SQL mappers or an extended class version if you have to customize the way the mapper is initialized.return [
'dot_mapper' => [
'mapper_manager' => [
'factories' => [
MyEntityDbMapper::class => DbMapperFactory::class,
],
'aliases' => [
MyEntity::class => MyEntityDbMapper::class
]
]
]
];
The abstract db mapper support multiple options, the majority can be overriden through configuration. We'll list them below through a configuration example and the explanations
return [
'dot_mapper' => [
'mapper_manager' => [
//...
],
'options' => [
'mapper' => [
//under this key you can setup or override mapper options
//the mapper options must be specified using the associated alias(its entity class name)
MyEntityDbMapper::class => [
'adapter' => 'name of the db adapter service(database by default)',
'table' => 'database table name(by default it will be generated from the mapper class name converting the camel case to underscore notation)',
'alias' => 'alias of the table to use(autogenerated by default)',
'event_listeners' => [
//array of mapper event listeners, to listen for mapper CRUD operation events(detailed later in the documentation)
]
]
]
]
]
];
At this point the mapper is ready to be used if configured correctly and the backend was set up as well. In your class that implements the bussiness logic and has the mapper manager defined you can fetch the mapper from the mapper manager as below
//...
class MyService implements MapperManagerAwareInterface
{
use MapperManagerAwareTrait;
//...
public function saveAnEntity(MyEntity $entity)
{
/** @var MapperInterface $mapper **/
$mapper = $this->getMapperManager()->get(MyEntity::class);
return $mapper->save($entity);
}
//...
}
A mapper event listener can be created by implementing the MapperEventListenerInterface
. In order to make it easier, we provide an abstract listener and a trait to help you setup the listener.
The mapper event object is defined in the MapperEvent
class.
Please note that the provided abstract mappers act also as event listeners to themselfs. This can be usefull for adding additional functionality directly to the mapper.
We list below the mapper event along with some tips on how you could use them and for what purpose. The grouped the events based on the mapper operation that triggers them and for each group we kept the order in which they are triggered.
These are triggered when calling the find
method of the mapper or the get
method
find
method. It is triggered before the query is run. It allows you to change the query at runtime or add find options. The parameters carried by this event are
select
- the query object specific to the underlying database adaptertype
- the finder type(defaults to 'all')options
- the array of options that was set for find operationdata
- the raw data of the entity as an associative arrayoptions
- the options array that was used to select the resultsentity
- the single entity result object that was hydrateddata
- the raw entity data that was used to hydrate the prototypeoptions
- the same options array used to query the databaseentities
- the query result as an array of entities or an empty array if no results were foundtype
- the finder typeoptions
- the find options array usedThe following events are triggered in the order listed below when calling the save
method of a mapper. The save method can act as a create or update function depending on the entity saved(it has an id or not). You can check if it's a create or update operation by reading an event parameter that we'll see below.
entity
- the entity object that is to be savedoptions
- the options array sent to the save functionisNew
- a boolean flag indicating if it is a new entity(create) or existing one(update)entity
- the saved entity. If it was a create operation, it will have the autogenerated id filled inoptions
- the options array as sent to the save methodisNew
- flag indicating if it was an insert or an update operationentity
- entity that was created or updatedoptions
- options array as set on the save methodTriggered when an entity is to be deleted by calling the mapper's delete
method. It works only with the single entity deletion not with the deleteAll
.
entity
- entity object to be deletedoptions
- the delete options array that was passed to the delete methodentity
- entity object that was deletedoptions
- the delete options array as passed to the delete method@ TODO: write more documentation