doctrine [Docs]

User Tools

Site Tools


doctrine

DOCTRINE

Projects

MongoDB Object Document Mapper Annotations, Cache, PHPCS (standards)

PHPCR ODM ¿?¿?¿?¿?

Collections Refelction Instantiator

MongoDB ODM

PHP Doctrine MongoDB Object Document Mapper (ODM) provides transpare per MongoDB

  1. Gettint Started
  2. Mapping Objects onto a Database
  3. Working with Object
  4. Adavanced Topics
  5. Cookbook

1. Getting Started

1.a Getting Started

Doctrine: Project that aims to handle the persistende of your domain model in a non-interfering way. You have two PHP classes User and BlogPost, to make them persistent you have to with them some mapping information.

use  Doctrine\ODM\MongoDB\Mapping\Annotations  as  ODM;
/** @ODM\Document */  
class User { 
	/** @ODM\Id */  
	private $id; 
	/** @ODM\Field(type="string") */  
	private $name;
	/** @ODM\ReferenceMany(targetDocument="BlogPost", cascade="all") */  
	private $posts = array();
	//....
}
//_-------------------
 
// create user 
$user = new User(); 
$user->setName('Bulat S.'); 
$user->setEmail('email@example.com'); 
// tell Doctrine 2 to save $user on the next flush() 
$dm->persist($user);
// create blog post 
$post = new BlogPost(); 
$post->setTitle('My First Blog Post'); 
$post->setBody('MongoDB + Doctrine 2 ODM = awesomeness!'); 
$post->setCreatedAt(new DateTime()); 
$user->addPost($post); 
// store everything to MongoDB 
$dm->flush();

1.b Introduction

Doctrine ODM is build fro PHP 5.3+

Features
  • Transparent persistence.
  • Map one or many embedded documents.
  • Map one or many referenced documents.
  • Create references between documents in different databases.
  • Map documents with Annotations, XML, YAML or plain old PHP code.
  • Documents can be stored on the MongoGridFS.
  • Collection per class(concrete) and single collection inheritance supported.
  • Map your Doctrine 2 ORM Entities to the ODM and use mixed data stores.
  • Inserts are performed using MongoCollection::batchInsert()
  • Updates are performed using atomic operators.

If we update a property and call ->flush() again we'll get an efficient update query using the atomic operators:¿?

Setup
composer require "doctrine/mongodb-odm"
use Doctrine\MongoDB\Connection; 
use Doctrine\ODM\MongoDB\Configuration; 
use Doctrine\ODM\MongoDB\DocumentManager; 
use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
 
if ( ! file_exists($file = __DIR__.'/vendor/autoload.php')) { 
	throw  new RuntimeException('Install dependencies to run this script.'); 
} 
$loader = require_once $file;
// namespace to match the classes
$loader->add('Documents', __DIR__);
$connection = new Connection(); 
$config = new Configuration();
 
$config->setProxyDir(__DIR__ . '/Proxies'); 
$config->setProxyNamespace('Proxies'); 
$config->setHydratorDir(__DIR__ . '/Hydrators'); 
$config->setHydratorNamespace('Hydrators'); 
$config->setDefaultDB('doctrine_odm');
 
// Set annotations for mappings
$config->setMetadataDriverImpl(AnnotationDriver::create(__DIR__ . '/Documents')); AnnotationDriver::registerAnnotationClasses();
 
$dm = DocumentManager::create($connection, $config);
PHP 7

Since the legacy driver (referred to as ext-mongo) is not available on PHP 7, you will need the new driver (ext-mongodb) installed and use a polyfill to provide the API of the legacy driver.

composer config "platform.ext-mongo" "1.6.16" && composer require "alcaeus/mongo-php-adapter"

1.c Architecture

Documents

A Document is a persistent domain object, a PHP class following these restrictions:

  • NOT final class or final methods
  • Porperties private or protected to make lazu loading working
  • Do not implement clone or wakeup
  • Inherint Documents cant have properties with the same name

  • __contruct is only invoked when you call new
  • Documents support inheritance, polymorphic associations and polypmorphic queries. Abstract and concrete classes.
    Document states
  • NEW: Document instance has no persistent identity, not associated with DocumentManager and UniOfWork
  • MANAGED: Document instance is an instance with persistent identity associated with a DocumentManager an whose persistence is managed
  • DETACHED: Document instance is an instance with a persistent identity that is not (or no longer) associated with a DocumentManager and a UnitOfWork.
  • REMOVED: document instance is an instance with a persistent identity, associated with a DocumentManager, that will be removed from the database upon transaction commit.
    Persistent fields
    Serializing documents
    The DocumentManager
    Transactional write-behind
    The unit of work

2. Mapping objects onto a Database

2.1 Basic Reference

2.1.1 Objects and Fields

This chapter explains the basic mapping of objects and properties. Mapping of references and embedded documents will be covered in the next chapter "Reference Mapping".

Mapping Drivers
  • Docblock Annotations, XML, YAML, PHP Code
    Docblock Annotations

    The Doctrine MongoDB ODM defines its own set of docblock annotations for supplying object document mapping metadata.

    Persistent classes

    In order to mark a class for object-relational persistence it needs to be designated as a document. This can be done through the @Document marker annotation.

    Doctrine Mapping Types

    bin, bin_bytearray, bin_custom, bin_func, bin_md5, bin_uuid, boolean int, float string timestamp (string to MongoTimestamp), date collection (array to MongoDB array) hash (associative array to MongoDB object) custom_id, id(string to MongoId by default), key, object_id raw (any type)

    Property Mapping
    Identifiers

    /** @Id */ You can configure custom ID strategies if you don't want to use the default MongoId.

    Fields

    /** @Field(type="string") */

    Custom Mapping Types
    Multiple document Types in a Collection
    <?php  
    /** 
    * @Document(collection="my_documents") 
    * @DiscriminatorField("type") 
    * @DiscriminatorMap({"article"="Article", "album"="Album"}) 
    **/  
    class Article { // ... } 
    /**
    * @Document(collection="my_documents") 
    * @DiscriminatorField("type")
    * @DiscriminatorMap({"article"="Article", "album"="Album"}) 
    **/  
    class Album { // ... }

    2.1.2 References

    Collections

    Many-valued References use Collection interface and a corresponding ArrayCollection implementation. Defined in Doctrine\Common\Collections, They are decoupled from the ODM.

    ReferenceOne
    ReferenceMany
    Mixing Document Types
    /** @ReferenceMany */  private $favorites = array();

    Now the $favorites property can store a reference to any type of document The name of the field within the DBRef object can be customized via the discriminatorField option @ReferenceMany(discriminatorField="type")

    Storing references
  • dbRefWithDb: Uses a DBRef with $ref, $id, and $db fields (this is the default)
  • dbRef: Uses a DBRef with $ref and $id
  • ref: Uses a custom embedded object with an id field
  • id: Uses the identifier of the referenced object

@ReferenceOne(targetDocument="Profile", storeAs="id") Now, the profile field will only store the MongoId of the referenced Profile document.

Cascading Operations

By default, Doctrine will not cascade any UnitOfWork operations to referenced documents. You must explicitly enable this functionality. @ReferenceOne(targetDocument="Profile", cascade={"persist"}) Valid values: all, detach, merge, refresh, remove, persist

Orphan Removal

If a Document of type A contains references to privately owned Documents B then if the reference from A to B is removed the document B should also be removed, because it is not used anymore.

OrphanRemoval works with both reference one and many mapped fields.

When using the orphanRemoval=true option Doctrine makes the assumption that the documents are privately owned and will NOT be reused by other documents.

When flush is called not only are the references removed but both the old standing data and the one address documents are also deleted from the database.

2.1.3 Bi-Directional References

By default when you map a bi-directional reference, the reference is maintained on both sides of the relationship and there is not a single "owning side". Both sides are considered owning and changes are tracked and persisted separately. When you persist, the references (DRefs) would exist on both sides.

Owning and Inverse Sides

A user may have lots of posts and we don't need to store a reference to each post on the user. In order to map this you can use the inversedBy and mappedBy options

/** @Document */  
class BlogPost { 
// ...  
/** @ReferenceOne(targetDocument="User", inversedBy="posts") */  
private $user; 
} 
/** @Document */  
class User { 
// ...  
/** @ReferenceMany(targetDocument="BlogPost", mappedBy="user") */  
private $posts; 
}
 
$user = $dm->find('User', $user->id); 
$posts = $user->getPosts();
 
The above will execute a query like the following to lazily load the collection of posts to iterate over:
db.BlogPost.find( { 'user.$id' : user.id } )

Remember that the inverse side, the side which specified mappedBy is immutable and any changes to the state of the reference will not be persisted.

OneToOne

When specifying inverse one-to-one relationships the referenced document is loaded directly when the owning document is hydrated instead of using a proxy. In the example above, loading a Customer object from the database would also cause the corresponding Cart to be loaded. This can cause performance issues when loading many Customer objects at once.

Self-Referencing Many to Many

2.1.4 Complex references

Sometimes you may want to access related documents using custom criteria or from the inverse side of a relationship.

  • criteria - Query criteria to apply to the cursor.
  • repositoryMethod - The repository method used to create the cursor.
  • sort - Sort criteria for the cursor.
  • skip - Skip offset to apply to the cursor.
  • limit - Limit to apply to the cursor.
  • 2.1.5 Indexes

    Working with indexes in the MongoDB ODM is pretty straight forward. You can have multiple indexes, they can consist of multiple fields, they can be unique and you can give them an order. In this chapter we'll show you examples of indexes using annotations.

2.1.6 Inheritance

2.2 More

2.2.1 Embedded Mapping

Embed One, Embed Many All operations on embedded documents are automatically cascaded.

2.2.2 Trees

// Ver Mongo

2.2.3 GridFS

2.2.4 aions rence

3 Working with Objects

3.1 Basic Reference

3.1.1 Documents

Understanding DM and UoW

Unit of Work: is similar to an object-level transaction. Is created when a DM is created or after DocumentManager#flush(). A Unit of Work is committed (and a new one started) by invoking DocumentManager#flush(). Can be manually closed by calling DocumentManager#close(). Any changes to objects within this Unit of Work that have not yet been persisted are lost.

Size of UoW: Number of managed documents at a particular point in time.

$uowSize = $dm->getUnitOfWork()->size();

This size affects the performance of flush() operations.

$uow = $dm->getUnitOfWork(); // modifying it is not recommended
Persisting

DocumentManager#persist($document) -> document becomes MANAGED ; persistence is from now on managed by an DocumentManager Will be synchronized with DB when DocumentManager#flush() is invoked.

Doctrine applies a strategy called "transactional write-behind", which means that it will delay most operations until DocumentManager#flush() is invoked which will then issue all necessary queries to synchronize your objects with the database in the most efficient way.

The document identifier is generated during persist if not previously specified. Users cannot rely on a document identifier being available during the prePersist event.

Removing

SAME for REMOVED state

Detaching and Merging ¿?
Transitive persistence

Each reference to another document or a collection of documents can be configured to automatically cascade certain operations. By default, no operations are cascaded.

Querying

$user = $dm->find('User', $id); // by primary key $users = $dm->getRepository('User')->findBy(array('age' => 20)); //By simple conditions // A single user by its nickname (__call magic) $user = $dm->getRepository('User')->findOneByNickname('romanb'); by lazy loading Whenever you have a managed document instance at hand, you can traverse and use any associations of that document as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading.

$qb = $dm->createQueryBuilder('User') ->field('age')->range(20, 30); // by qb

$group = $dm->find('Group', $id); $usersWithGroup = $dm->createQueryBuilder('User') ->field('group')->references($group) ->getQuery()->execute(); // by reference

3.1.2 Repositories

A repository mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. The default repository implementation provides the following methods:: find, findBy, findOneBy, findAll, matching

Custom repositories

A custom repository allows filtering logic to be consolidated into a single class instead of spreading it throughout a project

/** @Document(repositoryClass="Repositories\UserRepository") */
// ------
class UserRepository extends DocumentRepository { 
public  function findDisabled() { 
	return  $this->findBy(['disabled' => true, 'activated' => true]); 
	} 
}

3.1.1 Events

Doctrine features a lightweight event system that is part of the Common package. The DocumentManager and UnitOfWork trigger several events during the life-time of their registered documents: prePersist, preLoad, onFlush These can be hooked into by two different types of event listeners: lifecycle callbacks, listeners

Lifecycle callback
/** @Document @HasLifecycleCallbacks */  
class User {
/** @PrePersist */  
public  function doStuffOnPrePersist(\Doctrine\ODM\MongoDB\Event\LifecycleEventArgs $eventArgs) {
	$this->createdAt = date('Y-m-d H:i:s'); 
	}
}
Lifecycle event listeners

Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the document classes. They allow to implement re-usable behaviours between different document classes, There's a different implementation for each event

3.1.2 Migrations

Even though MongoDB is schemaless, introducing some kind of object mapper means that your object definitions become your schema. While you could use MongoDB's $rename operator to migrate everything, sometimes a lazy migration is preferable.

Renaming a Field

You need rename name to fullName; however, you'd like to hydrate fullName from name if the new field doesn't exist.

/** @Field(type="string") @AlsoLoad("name") */  
public $fullName;

You cannot query all values you have to query with and or for both properties

Transforming Data

Complete name to separate first and lastname

/** @AlsoLoad({"name", "fullName"}) */  
public  function populateFirstAndLastName($fullName) { 
	list($this->firstName, $this->lastName) = explode(' ', $fullName); 
}
Moving fields
  • @AlsoLoad - load values from old fields or transform data through methods
  • @NotSaved - load values into fields without saving them again
  • @PostLoad - execute code after all fields have been loaded
  • @PrePersist - execute code before your document gets saved

3.2 Query Reference

3.2.1 Query Builder API

FIND, FIND_AND_UPDATE, FIND_AND_REMOVE, INSERT, UPDATE, REMOVE, GROUP, MAP_REDUCE, DISTINCT_FIELD, GEO_LOCATION

$qb = $dm->createQueryBuilder('User'); 
$query = $qb->getQuery();
// Debug
$query = $qb->getQuery(); 
$debug = $query->debug();
// Eager cursor
$dm->createQueryBuilder('User') ->eagerCursor(true);
foreach ($cursor as $user) { // queries for all users and data is held internally  
	// each User object is hydrated from the data one at a time. 
}
// Distinct, Select
// Refreshing Documents

The query builder's refresh() method may be used to instruct ODM to override the managed document with data from the query result

Fetching Documents as Read-Only ¿?
Disabling Hydration, limiting results, sorting,
Map reduce
$qb = $this->dm->createQueryBuilder('Event') ->field('type')->equals('sale') 
->map('function() { emit(this.userId, 1); }') 
->reduce("function(k, vals) { var sum = 0; for (var i in vals) { sum += vals[i]; } return sum; }");
Conditional Operators

where($javascript), in($values), notIn($values), equals($value), notEqual($value), gt($value), gte($value), lt($value), lte($value), range($start, $end), size($size), exists($bool), type($type), all($values), mod($mod), addOr($expr), references($document), includesReferenceTo($document)

$qb = $dm->createQueryBuilder('Document') ->language('it') ->text('parole che stai cercando');
Atomic update operators

set($name, $value, $atomic = true), setNewObj($newObj), inc($name, $value), unsetField($field), push($field, $value), pushAll($field, array $valueArray), addToSet($field, $value), addManyToSet($field, array $values), popFirst($field), popLast($field), pull($field, $value), pullAll($field, array $valueArray)

Update muliple docs

$dm->createQueryBuilder('User') ->updateMany()

Remove

->remove()

3.2.2 Aggregation builder

$builder = $dm->createAggregationBuilder(\Documents\Orders::class); 
$builder 
	->match() 
		->field('purchaseDate') ->gte($from) ->lt($to) 
		->field('user') ->references($user) 
	->group()
	// ...
Hydration

By default, aggregation results are returned as PHP arrays. This is because the result of an aggregation pipeline may look completely different from the source document. In order to get hydrated aggregation results, you first have to map a QueryResultDocument. /** @QueryResultDocument */ class UserPurchases { ... }

Aggregation pipeline stages:

$count, $limit, $out ...

3.2.2 Find and modify

$job = $dm->createQueryBuilder('Job')
    // Find the job
    ->findAndUpdate()
    ->field('in_progress')->equals(false)
    ->sort('priority', 'desc')
 
    // Update found job
    ->field('started')->set(new \MongoDate())
    ->field('in_progress')->set(true)
    ->getQuery()
    ->execute();
// Also => findAndRemove()

3.2.3 Priming

Priming references allows you to consolidate database queries when working with one and many reference mappings. This is useful for avoiding the n+1 problem in your application.

$qb = $dm->createQueryBuilder('User')
    ->field('accounts')->prime(true)
    ->limit(100);
$query = $qb->getQuery();
 
/* After querying for the users, ODM will collect the IDs of all referenced
 * accounts and load them with a single additional query.
 */
$users = $query->execute();
 
foreach ($users as $user) {
    /* Accounts have already been loaded, so iterating through accounts will
     * not query an additional query.
     */
    foreach ($user->getAccounts() as $account) {
 
    }
}

In this case, priming will allow us to load all users and referenced accounts in two database queries. Also workds for invers references

/** @Document */
class User
{
    /** @ReferenceMany(targetDocument="Account", prime={"user"}) */
    private $accounts;
}

Passing true to prime() instructs ODM to load the referenced document(s) on its own

3.2.4 Eager Cursors

With a typical MongoDB cursor, it stays open during iteration and fetches batches of documents as you iterate over the cursor. This isn't bad, but sometimes you want to fetch all of the data eagerly. For example when dealing with web applications, and you want to only show 50 documents from a collection you should fetch all the data in your controller first before going on to the view.

Benefits:

  • The cursor stays open for a much shorter period of time.
  • Data retrieval and hydration are consolidated operations.
  • Doctrine has the ability to retry the cursor when exceptions during interaction with mongodb are encountered.
$qb = $dm->createQueryBuilder('User')
    ->eagerCursor(true);
$query = $qb->getQuery();
$users = $query->execute(); // returns instance of Doctrine\MongoDB\ODM\EagerCursor
 
//At this point all data is loaded from the database and cursors to MongoDB have been closed but hydration //of the data in to objects has not begun. Once insertion starts the data will be hydrated in to PHP //objects.
 
foreach ($users as $user) { echo $user->getUsername()."\n"; }
// Not all documents are converted to objects at once, the hydration is still done one document at a time during iteration. The only change is that all data is retrieved first.

N+1 Dos colecciones relacionada one to many => Coche y Rueda Se recorren los coches y por cada coche las ruedas => N coches => 1 Consulta para sacar los coches y N para sacar las ruedas => N+1 Sol: Sacar en una query todos los ids de las ruedas y en la siguiente query se le pasa el array

3 Advanced topics

3.2 Collections

3.2.1 Capped Collections

Capped collections are fixed sized collections that have a very high performance Capped collections automatically maintain insertion order for the objects in the collection; this is very powerful for certain use cases such as logging. (ex: error logs)

<?php
 
/**
 * @Document(collection={
 *   "name"="collname",
 *   "capped"=true,
 *   "size"=100000,
 *   "max"=1000
 * })
 */
class Category
{
 
}

3.2.1 Storage Strategies

Different strategies for storing, floats, ints, embeds and references. increment, addToSet, set, setArray, pushAll, atomicSet, atomicSetArray

3.3 Transactions and Concurrency# 3 Working with Objects

Written with StackEdit.

doctrine.txt · Last modified: 2020/07/02 16:06 (external edit)