dotkernel / dot-mapper

DotKernel mapper pattern implementation
MIT License
2 stars 2 forks source link

dot-mapper

[!IMPORTANT] dot-mapper is a wrapper on top of laminas/laminas-db

Dynamic JSON Badge

[!CAUTION]

Security-Only Maintenance Mode

This package is considered feature-complete, and is now in security-only maintenance mode.

OSS Lifecycle PHP from Packagist (specify version)

GitHub issues GitHub forks GitHub stars GitHub license

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.

Installation

Run the following command in your project root directory

$ composer required dotkernel/dot-mapper

Entities

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

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...
}

Mappers

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.

example 1
//...
class MyEntityDbMapper extends AbstractDbMapper
{
    // an empty db mapper does support CRUD operations and has already implemented the MapperInterface's methods
}

Basic mapper functions

Selecting list of items

public function find(string $type = 'all', array $options = []): array

Find options(for SQL databases)

Join options

$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,
    ],
    //...
];

Counting items

public function count($type = 'all', array $options = []): int

Selecting one item/entity

public function get($primaryKey, array $options = [])

Saving an entity

public function save(EntityInterface $entity, array $options = [])

Deleting an entity

public function delete(EntityInterface $entity, array $options = [])

Bulk delete

public function deleteAll(array $conditions)

Bulk update

public function updateAll(array $fields, array $conditions)

Creating new empty entities

public function newEntity(): EntityInterface;

Other useful functions

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.

The Mapper Manager

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.

Mapper setup

return [
    'dot_mapper' => [
        'mapper_manager' => [
            'factories' => [
                MyEntityDbMapper::class => DbMapperFactory::class,
            ],
            'aliases' => [
                MyEntity::class => MyEntityDbMapper::class
            ]
        ]
    ]
];

Mapper configuration options

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)
                    ]
                ]
            ]
        ]
    ]
];

Using the mapper

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);
    }

    //...
}

Listening to mapper events

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.

Mapper events

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.

Select(find) related events

These are triggered when calling the find method of the mapper or the get method

MapperEvent::EVENT_MAPPER_BEFORE_FIND

MapperEvent::EVENT_MAPPER_BEFORE_LOAD

MapperEvent::EVENT_MAPPER_AFTER_LOAD

MapperEvent::EVENT_MAPPER_AFTER_FIND

Save(insert/update) related events

The 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.

MapperEvent::EVENT_MAPPER_BEFORE_SAVE

MapperEvent::EVENT_MAPPER_AFTER_SAVE

MapperEvent::EVENT_MAPPER_AFTER_SAVE_COMMIT

Delete related events

Triggered 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.

MapperEvent::EVENT_MAPPER_BEFORE_DELETE

MapperEvent::EVENT_MAPPER_AFTER_DELETE

MapperEvent::EVENT_MAPPER_AFTER_DELETE_COMMIT

Advanced mapper usage

@ TODO: write more documentation