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. KING FOO Developer at King Foo ( ) http://king-foo.be SYMFONY2

    TRAINING SensioLabs partner SF4PHPBNL http://training.king-foo.be
  2. $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?
  3. 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
  4. TIME FOR SOME HISTORY Small extraction of my presentation. The

    Database Access rEvolution (with @miljar)
  5. <DATABASE>-FUNCTIONS cubrid_* dbplus_* dbase_* filepro_* ibase_* fbsql_* db2_* ifx_* ingres_*

    maxdb_* msql_* mssql_* mysql_ oci_* ovrimos_* px_* pg_* sqlite_* sqlsrv_* sybase_*
  6. PDO The PHP Data Objects (PDO) extension defines a lightweight,

    consistent interface for accessing databases in PHP ( ) PHP.net manual
  7. 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
  8. 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
  9. ORM FRAMEWORK: GATEWAYS An object that encapsulates access to an

    external system or resource ( ) PoEAA: Gateway Zend_Db_Table Zend_Db_Table_Row
  10. 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
  11. NO REALLY, WHY? Use code that most people recognize Separation

    of responsibilities Better overview Because you can
  12. 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
  13. 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
  14. 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.
  15. 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
  16. 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
  17. 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; } }
  18. 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);
  19. getRepository /** * Gets the repository for a class. *

    * @param string $className * * @return \Doctrine\Common\Persistence\ObjectRepository */ public function getRepository($className);
  20. 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
  21. 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
  22. 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.
  23. getMetadataFactory /** * Gets the metadata factory used to gather

    the metadata of classes. * * @return \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory */ public function getMetadataFactory();
  24. 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);
  25. 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();
  26. 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]; }
  27. 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? } }
  28. 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
  29. 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
  30. ENTITIES Now we have a list of objects with an

    identifier We can (or need to) keep track of these in the OM
  31. 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
  32. 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 } // ...
  33. 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();
  34. 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
  35. UNIT OF WORK Can be an object in the OM

    (Doctrine) Can be a construction in the OM
  36. 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);
  37. persist IMPLEMENTED class EntityManager implements ObjectManager { private $entities =

    array(); public function persist($object) { $this->entities[spl_object_hash($object)] = $object; } }
  38. 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);
  39. remove IMPLEMENTED class EntityManager implements ObjectManager { private $entitiesToRemove =

    array(); public function remove($object) { $this->entitiesToRemove[spl_object_hash($object)] = $object; } }
  40. contains /** * Checks if the object is part of

    the current UnitOfWork * and therefore managed. * * @param object $object * * @return bool */ public function contains($object);
  41. 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);
  42. 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);
  43. 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);
  44. 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);
  45. 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();
  46. 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 } }
  47. 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 // ... } }
  48. 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); } } }
  49. 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 ... } }
  50. 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);
  51. 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
  52. 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); }
  53. CACHING 3 types of Metadata Cache Query Cache Result Cache

    Your turn on repositories Other Doctrine caching ObjectCache
  54. 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