On Github Ocramius / a-complex-orm-faster-than-sql
I annoy people on twitter as @Ocramius.
I write dangerous software on Github.
An incubator for persistence-oriented libraries
Inspired by Hibernate and the JPA (JSR-317)
Based on a DBAL (DataBase Abstraction Layer)
Saves/Loads POPO through an SQL RDBMS
As long as your domain logic is free from dependencies, you can model it as you prefer
You don't really need Doctrine ORM for that, but it helps.
If you want more of this, check DDDinPHP!
INSERT INTO address (street, city, zip, country) VALUES ('Zemědělská 1', 'Brno', '61300', 'CZ');
INSERT INTO user (username, name, surname, address_id) VALUES ('Ocramius', 'Marco', 'Pivetta', LAST_INSERT_ID());
INSERT INTO invoice (created, user_id) VALUES (NOW(), LAST_INSERT_ID());
SET @invoice_id = LAST_INSERT_ID(); INSERT INTO line_item (invoice_id, product_id) VALUES (@invoice_id, 123), (@invoice_id, 456);
$user = new User( 'Ocramius', 'Marco', 'Pivetta', new Address('Zemědělská 1', 'Brno', '61300', 'CZ') ); $product1 = $entityManager->find('Product', 123); $product2 = $entityManager->find('Product', 456); $invoice = new Invoice($user, [ new LineItem($product1), new LineItem($product2) ]); $entityManager->persist($invoice); $entityManager->flush();
Calling Doctrine\ORM\EntityManager#flush() with invalid data causes a ROLLBACK and an exception.
It's pretty much always at the Performance cost.
Why hit screws with a hammer?
Why use nails with a screwdriver?
$user = $entityManager->find('User', 123); $entityManager->remove($user); $entityManager->persist($thing); $entityManager->flush();
$user = $repository->find(123); $activeUsers = $repository->findBy(['active' => true]);
$user = $entityManager->find('User', 123); $city = $user->getAddress()->getCity();
$adultBirthDate = new DateTime('-18 years'); $adultsCriteria = new Doctrine\Common\Collections\Criteria(); $adultsCriteria->andWhere( $criteria->expr()->lte('birthDate', $adultBirthDate) ); $adults = $repository->matching($criteria);
$users = $entityManager->createQuery(' SELECT u FROM Users u JOIN u.address a JOIN a.city c WHERE c.name = :cityName ') ->setParameter('cityName', 'Brno) ->getResult();
(if all else fails, and you use voodoo)
$query = $entityManager->createNativeQuery( <<<'SQL' WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n + 1 FROM t WHERE n < 100 ) SELECT sum(n) AS foo FROM t; 'SQL' ); $rsm = new ResultSetMappingBuilder($entityManager); $rsm->addScalarResult('foo', 'sum'); $query->setResultSetMapping($rsm); $sum = $query->getResult();
When you call EntityManager#flush(), the UnitOfWork is iterated!
You can avoid a lot of queries!
You need this in production!
Caches ORM bootstrapping information
Caches query generation:
SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u
SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3 FROM cms_users c0_
Do not use it with dynamic DQL!
Caches query results:
$usersCount = $entityManager ->createQuery('SELECT COUNT(u) FROM User u') ->setResultCacheDriver($redisCache) ->useResultCache(true) ->setResultCacheLifeTime(7200) ->getResult();
Cache ID depends on query string and parameters!
Also called SLC or L2 Cache
* To be released soon
SQL RDBMS are slow at executing simple fetches
SELECT * FROM articles WHERE id = :id
That's part of why MongoDB, CouchDB and other NoSQL DBs are very successful!
Saves entity state in the DB and in the Cache
Tries to load entities from cache first
Only the ORM writes to DB
Inserts and updates handled by EntityManager#flush()
When setting up the ORM:
$configuration->setSecondLevelCacheEnabled(); $configuration ->getSecondLevelCacheConfiguration() ->setCacheFactory(new DefaultCacheFactory( new RegionsConfiguration(), new RedisCache() ));
Mark your entities as "cacheable"
/** * @Cache(usage="NONSTRICT_READ_WRITE") */ class MyEntity { // ... }
Very useful when different data types require different cache backends
$users = $entityManager->getRepository('User')->findAll(); $approvedUsers = array_filter( $users, function (User $user) { return $user->getEmailVerification()->isApproved(); } );