Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Creating your own Entity/Object Manager

Creating your own Entity/Object Manager

Most of us are used to working with an Entity or Object manager. In ORMs like Doctrine, they are the central point to talk to to keep track of our objects (and safely load/store them into a DB or document store). We can use this approach to not only manage objects in our database, but to manage third party sources, like webservices, etc... This talk will explain the place of the Entity Manager in our projects and how to write your own version of it.

Jachim Coudenys

January 24, 2015
Tweet

More Decks by Jachim Coudenys

Other Decks in Technology

Transcript

  1. CREATING YOUR OWN
    ENTITY/OBJECT
    MANAGER
    Jachim Coudenys
    /
    jachim.be @coudenysj

    View Slide

  2. ABOUT ME
    Jachim Coudenys ( )
    (Ab)Using PHP since 2002 (4.1.0)
    @coudenysj

    View Slide

  3. KING FOO
    Developer at King Foo ( )
    http://king-foo.be
    SYMFONY2 TRAINING
    SensioLabs partner
    SF4PHPBNL
    http://training.king-foo.be

    View Slide

  4. PHP-WVL
    Co-Organizer of PHP-WVL ( )
    http://php-wvl.be

    View Slide

  5. $user = new User();
    $user->setName('Jachim Coudenys');
    $user->setEmail('[email protected]');
    $em->persist($user);
    $em->flush();
    $user = $em->find('User', 123);
    $user->setEmail('[email protected]');
    $em->flush();
    Who recognizes this?
    Who is using this?
    Who loves using this?

    View Slide

  6. WHAT?

    View Slide

  7. define EntityManager
    The EntityManager API is used to access a database in a
    particular unit of work. It is used to create and remove
    persistent entity instances, to find entities by their primary key
    identity, and to query over all entities.
    ( )
    http://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/architecture.html

    View Slide

  8. WHY?

    View Slide

  9. TIME FOR SOME HISTORY
    Small extraction of my presentation.
    The Database Access rEvolution (with @miljar)

    View Slide

  10. FILES
    Textbook example: guestbook

    View Slide

  11. -FUNCTIONS
    cubrid_*
    dbplus_*
    dbase_*
    filepro_*
    ibase_*
    fbsql_*
    db2_*
    ifx_*
    ingres_*
    maxdb_*
    msql_*
    mssql_*
    mysql_
    oci_*
    ovrimos_*
    px_*
    pg_*
    sqlite_*
    sqlsrv_*
    sybase_*

    View Slide

  12. PDO
    The PHP Data Objects (PDO) extension defines a lightweight,
    consistent interface for accessing databases in PHP
    ( )
    PHP.net manual

    View Slide

  13. ORM FRAMEWORK
    Object-relational mapping in computer software is a
    programming technique for converting data between
    incompatible type systems in relational databases and object-
    oriented programming languages
    ( )
    Wikipedia

    View Slide

  14. ORM FRAMEWORK: ACTIVE RECORD
    An object that wraps a row in a database table or view,
    encapsulates the database access, and adds domain logic on
    that data.
    ( )
    PoEAA: Active Record
    Doctrine 1.x

    View Slide

  15. ORM FRAMEWORK: GATEWAYS
    An object that encapsulates access to an external system or
    resource
    ( )
    PoEAA: Gateway
    Zend_Db_Table
    Zend_Db_Table_Row

    View Slide

  16. ORM FRAMEWORK: DATA MAPPER
    A layer of Mappers that moves data between objects and a
    database while keeping them independent of each other and
    the mapper itself.
    ( )
    PoEAA: Data Mapper
    Doctrine 2

    View Slide

  17. ORM FRAMEWORK: DATA MAPPER
    This is where an entity/object manager enters the stage.

    View Slide

  18. NO REALLY, WHY?
    Use code that most people recognize
    Separation of responsibilities
    Better overview
    Because you can

    View Slide

  19. VARIANTS
    We will write our own variant, but many exist already
    ORM
    ODM
    Object Document Mapper
    OGM
    Object Graph Mapper
    Etc...
    Object Relational Mapper
    Mongo
    CouchDB
    PHPCR
    Neo4j-PHP-OGM

    View Slide

  20. HOW?

    View Slide

  21. LET'S GET STARTED
    We want to talk to a SOAP server
    We want to use entities to send data back and forth
    We will only implement what we need

    View Slide

  22. EXTENDING THE ENTITY MANAGER
    Let's open the
    Doctrine\ORM\EntityManager
    class
    The class DocBlock says:
    You should never attempt to inherit from
    the EntityManager: Inheritance is not a
    valid extension point for the
    EntityManager. Instead you should take a
    look at the {@see \Doctrine\ ORM\
    Decorator\ EntityManagerDecorator} and
    wrap your entity manager in a decorator.

    View Slide

  23. ENTITYMANAGER DECORATOR
    namespace Doctrine\ORM\Decorator;
    use Doctrine\DBAL\LockMode;
    use Doctrine\ORM\Query\ResultSetMapping;
    use Doctrine\ORM\EntityManagerInterface;
    use Doctrine\Common\Persistence\ObjectManagerDecorator;
    abstract class EntityManagerDecorator
    extends ObjectManagerDecorator
    implements EntityManagerInterface
    {
    /**
    * @var EntityManagerInterface
    */
    protected $wrapped;
    /**
    * @param EntityManagerInterface $wrapped
    */
    public function __construct(EntityManagerInterface $wrapped)
    {
    $this->wrapped = $wrapped;
    }
    /**
    Allows you to override specific methods
    Still ORM oriented
    A lot of stuff we won't actually use

    View Slide

  24. OBJECTMANAGER
    namespace Doctrine\Common\Persistence;
    interface ObjectManager
    {
    /**
    * Finds an object by its identifier.
    *
    * This is just a convenient shortcut for getRepository($className)->find($
    *
    * @param string $className The class name of the object to find.
    * @param mixed $id The identity of the object to find.
    *
    * @return object The found object.
    */
    public function find($className, $id);
    /**
    * Tells the ObjectManager to make an instance managed and persistent.
    *
    * The object will be entered into the database as a result of the flush op
    *
    * NOTE: The persist operation always considers objects that are not yet kn
    * this ObjectManager as NEW. Do not pass detached objects to the persist o
    *
    * @param object $object The instance to make managed and persistent.
    *
    Base for all (or most) variants
    More than enough for now
    Start with throw exception at first and implement what
    you need

    View Slide

  25. SETUP
    "Inject" a connection and config
    namespace My\Project;
    use Doctrine\Common\Persistence\ObjectManager;
    use My\Project\Service\SoapClient;
    class EntityManager implements ObjectManager
    {
    /**
    * @param SoapClient $conn
    * @param mixed $config
    */
    protected function __construct(SoapClient $conn, $config)
    {
    $this->conn = $conn;
    $this->config = $config;
    }
    }

    View Slide

  26. find
    /**
    * Finds an object by its identifier.
    *
    * This is just a convenient shortcut for
    * getRepository($className)->find($id).
    *
    * @param string $className The class name of the object to find.
    * @param mixed $id The identity of the object to find.
    *
    * @return object The found object.
    */
    public function find($className, $id);

    View Slide

  27. getRepository
    /**
    * Gets the repository for a class.
    *
    * @param string $className
    *
    * @return \Doctrine\Common\Persistence\ObjectRepository
    */
    public function getRepository($className);

    View Slide

  28. REPOSITORY
    Mediates between the domain and data mapping layers using
    a collection-like interface for accessing domain objects.
    http://www.martinfowler.com/eaaCatalog/repository.html

    View Slide

  29. getRepository
    public function getRepository($className)
    {
    if (!array_key_exists($className, $this->repositories)) {
    $repositoryName = str_replace(
    'Entity', 'Repository', $className
    );
    $repositoryName .= 'Repository';
    $repository = new $repositoryName($this);
    $this->repositories[$className] = $repository;
    }
    return $this->repositories[$className];
    }
    My\Project\Entity\User
    My\Project\Repository\UserRepository

    View Slide

  30. CUSTOM REPOSITORY
    namespace Doctrine\Common\Persistence;
    interface ObjectRepository
    {
    /**
    * Finds an object by its primary key / identifier.
    *
    * @param mixed $id The identifier.
    *
    * @return object The object.
    */
    public function find($id);
    /**
    * Finds all objects in the repository.
    *
    * @return array The objects.
    */
    public function findAll();
    /**
    * Finds objects by a set of criteria.

    View Slide

  31. MAPPING
    Holds details of object-relational mapping in metadata.
    http://www.martinfowler.com/eaaCatalog/metadataMapping.html

    View Slide

  32. getMetadataFactory
    /**
    * Gets the metadata factory used to gather the metadata of classes.
    *
    * @return \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory
    */
    public function getMetadataFactory();

    View Slide

  33. getClassMetadata
    /**
    * Returns the ClassMetadata descriptor for a class.
    *
    * The class name must be the fully-qualified class name without a
    * leading backslash (as it is returned by get_class($obj)).
    *
    * @param string $className
    *
    * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata
    */
    public function getClassMetadata($className);

    View Slide

  34. Doctrine\Common\Persistenc
    e\Mapping\ClassMetadata
    namespace Doctrine\Common\Persistence\Mapping;
    interface ClassMetadata
    {
    /**
    * Gets the fully-qualified class name of this persistent class.
    *
    * @return string
    */
    public function getName();
    /**
    * Gets the mapped identifier field name.
    *
    * The returned structure is an array of the identifier field names.
    *
    * @return array
    */
    public function getIdentifier();

    View Slide

  35. getRepository
    public function getRepository($className)
    {
    if (!array_key_exists($className, $this->repositories)) {
    $classMetadata = $this->getClassMetadata($className);
    // Doctrine ORM approach
    $repositoryName = $classMetadata->customRepositoryClassName;
    $repository = new $repositoryName($this, $classMetadata);
    $this->repositories[$className] = $repository;
    }
    return $this->repositories[$className];
    }

    View Slide

  36. FIND VIA REPOSITORY
    namespace My\Project\Repository;
    use Doctrine\Common\Persistence\ObjectRepository;
    class UserRepository implements ObjectRepository
    {
    public function find($id)
    {
    $response = $this->getEntityManager()
    ->getConnection()->call('UserGet', ['userId' => $id]);
    // ...
    // return User entity?
    }
    }

    View Slide

  37. HYDRATION
    Hydration is the act of populating an object from a set of data.
    http://framework.zend.com/manual/current/en/modules/zend.stdlib.hydrator.html

    View Slide

  38. DATA HYDRATORS
    http://doctrine.readthedocs.org/en/latest/en/manual/data-hydrators.html
    Query::HYDRATE_OBJECT
    Query::HYDRATE_ARRAY
    Query::HYDRATE_SCALAR
    Query::HYDRATE_SINGLE_SCALAR

    View Slide

  39. RETURN AN ENTITY IN FIND
    // UserRepository
    public function find($id)
    {
    // ...
    $user = new User();
    $hydrator = new \Zend\Stdlib\Hydrator\ClassMethods();
    $hydrator->hydrate($response, $user);
    return $user;
    }
    Improvements:
    $this->em->getHydrator($classname);
    Work with the mapping

    View Slide

  40. ENTITIES
    Now we have a list of objects with an identifier
    We can (or need to) keep track of these in the OM

    View Slide

  41. IDENTITY MAP
    Ensures that each object gets loaded only once by keeping
    every loaded object in a map. Looks up objects using the map
    when referring to them.
    http://www.martinfowler.com/eaaCatalog/identityMap.html

    View Slide

  42. IDENTITY MAP
    namespace My\Project;
    use Doctrine\Common\Persistence\ObjectManager;
    class EntityManager implements ObjectManager
    {
    /**
    * Collection of identities:
    * array(
    * 'User' => [1 => object, 5 => object],
    * // ...
    * )
    */
    private $identityMap = array();
    public function find($className, $id)
    {
    // check map first
    // store in map
    }
    // ...

    View Slide

  43. OBJECT STATES
    EXISTING OBJECTS
    $user = $em->find('User', 123);
    $user->setEmail('[email protected]');
    $em->flush():
    NEW OBJECTS
    $user = new User();
    $user->setName('Jachim Coudenys');
    $user->setEmail('[email protected]');
    $em->persist($user);
    $em->flush();

    View Slide

  44. OBJECT STATES
    STATE_MANAGED
    STATE_NEW
    STATE_DETACHED
    STATE_REMOVED

    View Slide

  45. UNIT OF WORK
    Maintains a list of objects affected by a business transaction
    and coordinates the writing out of changes and the resolution
    of concurrency problems.
    http://www.martinfowler.com/eaaCatalog/unitOfWork.html

    View Slide

  46. UNIT OF WORK
    Can be an object in the OM (Doctrine)
    Can be a construction in the OM

    View Slide

  47. persist
    /**
    * Tells the ObjectManager to make an instance managed and
    * persistent. The object will be entered into the database as
    * a result of the flush operation.
    *
    * NOTE: The persist operation always considers objects that
    * are not yet known to this ObjectManager as NEW. Do not pass
    * detached objects to the persist operation.
    *
    * @param object $object The instance to make managed and persistent
    *
    * @return void
    */
    public function persist($object);

    View Slide

  48. persist IMPLEMENTED
    class EntityManager implements ObjectManager
    {
    private $entities = array();
    public function persist($object)
    {
    $this->entities[spl_object_hash($object)] = $object;
    }
    }

    View Slide

  49. remove
    /**
    * Removes an object instance.
    *
    * A removed object will be removed from the database as a
    * result of the flush operation.
    *
    * @param object $object The object instance to remove.
    *
    * @return void
    */
    public function remove($object);

    View Slide

  50. remove IMPLEMENTED
    class EntityManager implements ObjectManager
    {
    private $entitiesToRemove = array();
    public function remove($object)
    {
    $this->entitiesToRemove[spl_object_hash($object)] = $object;
    }
    }

    View Slide

  51. contains
    /**
    * Checks if the object is part of the current UnitOfWork
    * and therefore managed.
    *
    * @param object $object
    *
    * @return bool
    */
    public function contains($object);

    View Slide

  52. merge
    /**
    * Merges the state of a detached object into the persistence
    * context of this ObjectManager and returns the managed copy
    * of the object. The object passed to merge will not become
    * associated/managed with this ObjectManager.
    *
    * @param object $object
    *
    * @return object
    */
    public function merge($object);

    View Slide

  53. refresh
    /**
    * Refreshes the persistent state of an object from the database,
    * overriding any local changes that have not yet been persisted.
    *
    * @param object $object The object to refresh.
    *
    * @return void
    */
    public function refresh($object);

    View Slide

  54. detach
    /**
    * Detaches an object from the ObjectManager, causing a managed
    * object to become detached. Unflushed changes made to the
    * object if any (including removal of the object), will not be
    * synchronized to the database. Objects which previously referenced
    * the detached object will continue to reference it.
    *
    * @param object $object The object to detach.
    *
    * @return void
    */
    public function detach($object);

    View Slide

  55. clear
    /**
    * Clears the ObjectManager. All objects that are currently managed
    * by this ObjectManager become detached.
    *
    * @param string|null $objectName if given, only objects of
    * this type will get detached.
    *
    * @return void
    */
    public function clear($objectName = null);

    View Slide

  56. flush
    /**
    * Flushes all changes to objects that have been queued up to now
    * to the database. This effectively synchronizes the in-memory
    * state of managed objects with the database.
    *
    * @return void
    */
    public function flush();

    View Slide

  57. flush IMPLEMENTED
    class EntityManager implements ObjectManager
    {
    public function flush()
    {
    // - loop over $this->entities and save them
    // - optionally set the identifier of the entity
    // - update the states of the entities
    // - loop over $this->entitiesToRemove and delete them
    // - reset $this->entitiesToRemove
    }
    }

    View Slide

  58. flush IMPROVED
    class EntityManager implements ObjectManager
    {
    private $entities;
    private $originalEntities;
    public function flush()
    {
    // actually use a "Unit of Work" object to calculate
    // changed values between
    // $this->originalEntities and $this->entities
    // ...
    }
    }

    View Slide

  59. flush HACKED EXAMPLE
    class EntityManager implements ObjectManager
    {
    private $entities;
    private $originalEntities;
    public function flush()
    {
    foreach ($this->entities as $entity) {
    // keep some SOAP logic together
    $this->getRepository(get_class($entity))
    ->flush($entity);
    }
    }
    }

    View Slide

  60. flush HACKED EXAMPLE
    class UserRepository extends ObjectRepository {
    public function flush(User $user) {
    $changedValues = // calculate diffs
    // different SOAP calls
    if (array_key_exists('password', $changedValues)) {
    $this->getEntityManager()
    ->getConnection()->call(
    'UpdatePassword',
    ['userId' => $user->getId(),
    'password' => $changedValues['password']]
    );
    }
    // etc ...
    }
    }

    View Slide

  61. initializeObject
    /**
    * Helper method to initialize a lazy loading proxy
    * or persistent collection.
    *
    * This method is a no-op for other objects.
    *
    * @param object $obj
    *
    * @return void
    */
    public function initializeObject($obj);

    View Slide

  62. LAZY LOADING
    An object that doesn't contain all of the data you need but
    knows how to get it.
    http://www.martinfowler.com/eaaCatalog/lazyLoad.html

    View Slide

  63. THE PROXY PATTERN IN PHP
    http://ocramius.github.io/presentations/proxy-pattern-in-
    php/

    View Slide

  64. OBJECTMANAGER
    interface ObjectManager
    {
    public function find($className, $id);
    public function persist($object);
    public function remove($object);
    public function merge($object);
    public function clear($objectName = null);
    public function detach($object);
    public function refresh($object);
    public function flush();
    public function getRepository($className);
    public function getClassMetadata($className);
    public function getMetadataFactory();
    public function initializeObject($obj);
    public function contains($object);
    }

    View Slide

  65. WELCOME, OBJECT MANAGER

    View Slide

  66. EXTRA BITS

    View Slide

  67. CACHING
    3 types of
    Metadata Cache
    Query Cache
    Result Cache
    Your turn
    on repositories
    Other
    Doctrine caching
    ObjectCache

    View Slide

  68. QUERY OBJECT
    An object that represents a database query.
    http://www.martinfowler.com/eaaCatalog/queryObject.html

    View Slide

  69. OTHER VARIANTS
    Elasticsearch
    Suggestions?

    View Slide

  70. CONCLUSION
    A lot of variants exist already
    Not that hard (only take what you need)
    Check if you can start by extending the ORM (if you are
    working relational)
    Use the ObjectManager interface for the rest

    View Slide

  71. RESOURCES
    http://doctrine-
    orm.readthedocs.org/en/latest/reference/unitofwork.html
    http://www.martinfowler.com/books/eaa.html

    View Slide

  72. THANK YOU
    https://joind.in/13180
    @coudenysj
    king-foo.be
    php-wvl.be
    QUESTIONS?

    View Slide