Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Make your likes visible on Facebook?

Connect your Facebook account to Prezi and let your likes appear on your timeline.
You can change this under Settings & Account at any time.

No, thanks

Doctrine 2 Best Practices

No description
by

Kevin Steinmetz

on 17 June 2015

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of Doctrine 2 Best Practices

• Doctrine is a very flexible and powerful system.
• Doctrine's ORM adds more value then the
performance penalty, IF you use it correctly.
• Get what you need, not what you get.
• It is your job to know what Doctrine is doing.
• Use the best practices.
• Do what is comfortable for you based on
where you are at in the code life cycle.
Doctrine Best Practices
It is highly recommended to make use of a bytecode cache like APC.
Improved Performance
• Don’t use public properties on entities
• Avoid composite keys
• Use events judiciously
• Use cascades judiciously
• Use prepared statements
• Don’t use special characters
• Don’t use identifier quoting
• Initialize collections in the constructor
• Don’t map foreign keys to fields in an entity
• Use explicit transaction demarcation
Coding Best practices For Doctrine
Doctrine is very powerful. It will try to get out of your database anything you ask it to.

What tools can we use to monitor what is going on?
Know What Doctrine Is Doing
Finding What You Need
Doctrine provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs.
Doctrine 2 - Best Practices
There is a lot of talk out there talking about how best to use Doctrine. Here is a collection of Doctrine best practices, ways to improve performance and things to avoid.
• DBAL is the primary connection to the Database.

• It can be configured with more than one connection to more than one kind of database.

• It is what you use on the command line.

• It accepts raw SQL and prepared queries.
DBAL - DataBase Abstraction Layer
Doctrine 2 Best Practices
The Maze of Multiple Model Manipulation
• Provides transparent relational persistence for plain PHP objects.

• Has a Query Builder interface.

• Has a DQL interface.

• Has an Entity Manager Interface.
ORM - Object Relational Mapping
MY
SQL
DQL
Query Builder
Entity Manager
And the best one to use is ...
The one that best fits the situation and your comfort level.
"Don’t use an ORM if you don’t understand SQL.
If you do understand SQL, you already know not to use an ORM."

Drew McLellan
Thank You
Still
Rocks
How can we use the ORM to it's fullest
and still get the performance we want?
<?php
// Frist parm is an entity, second is an id.
$user = $em->find('MyProject\Domain\User', $id);

Same as
<?php
$user = $em->getRepository('MyProject\Domain\User')->find($id);
1. You can find by primary key
2. Entity Repositories’ findBy and findOneBy
<?php
// $em instanceof EntityManager

// All users that are 20 years old
$users = $em->getRepository('MyProject\Domain\User')->
findBy(array('age' => 20));

// All users that are 20 years old and have a surname of 'Miller'
$users = $em->getRepository('MyProject\Domain\User')->
findBy(array('age' => 20, 'surname' => 'Miller'));

// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->
findOneBy(array('nickname' => 'romanb'));

3. By DQL In Repositories
<?php
// $em instanceof EntityManager. Get all users with an age
// between 20 and 30 (inclusive).

$q = $em->createQuery(
"select u from MyDomain\Model\User u
where u.age >= 20 and u.age <= 30"
);

$users = $q->getResult();
[4]
It is important to constrain relationships as much as possible.
This means:
• Impose a traversal direction (avoid bidirectional associations if possible)
• Eliminate nonessential associations
This has several benefits:
• Reduced coupling in your domain model
• Simpler code in your domain model
(no need to maintain bidirectionality properly)
• Less work for Doctrine
Constrain relationships as much as possible
Beware of lazy loading when querying
entities with associations
Good
<?php

$articles = $article_repository->findAll();

foreach($articles as $article)
{
echo $article->getTitle();
echo $article->getCommentCount();
}

Bad
<?php

$articles = $article_repository->findAll();

foreach($articles as $article)
{
echo $article->getTitle();
echo count($article->getComments());
}

Better
//$em is the entity manager
$qb = $em->createQueryBuilder();

$qb
->select('Article', 'Comment')
->from('Entity\Article', 'Article')
->leftJoin('Article.comment', 'Comment');

$query = $qb->getQuery();

return $query->getResult();
Ugly
$em = $this->getEntityManager();

$articlesRepo = $em->getRepository
('OctiviTestBundle:Article');
$articles = $articlesRepo->findAll();

In the view:

{% for article in articles %}
{{ article.author.name }}
{{ article.content }}
{% endfor %}
Better
<?php
$em = $this->getEntityManager();

$qb = $em->createQueryBuilder('qb1');
$qb->add('select', 'a, au')
->add('from', 'OctiviTestBundle:Article a')
->join('a.author', 'au');

In the view:
{% for article in articles %}
{{ article.author.name }}
{{ article.content }}
{% endfor %}
Extra-Lazy loading is an option too.
<?php

//$em is the entity manager
$qb = $em->createQueryBuilder();

$qb
->select('Article', 'Author')
->from('Entity\Article', 'Article')
->leftJoin('Article.author', 'Author');

$query = $qb->getQuery();

return $query->getResult();
Better
Use the advantages of Extra Lazy Collections
/**
* @Entity
*/
class CmsGroup
{
/**
* @ManyToMany(
targetEntity="CmsUser",
mappedBy="groups",
fetch="EXTRA_LAZY") // "EAGER"
*/
public $users;
}
$users = $users_repository->findAll();

Now, calling Collection’s methods like:

$users->count();
$users->slice($offset, $length = null)

Won’t load the whole collection from the database to the memory. Great for pagination.

Extra-Lazy loading Entities
Use array hydration for read only actions
The process of Hydration is one of the most time and memory consuming operations the ORM does.

Therefore Doctrine documentation recommends the use of array hydration for read only actions.
You can mark entities as read only
In the entity annotations you add:

@Cache()

Optional attributes:
"READ_ONLY",
"READ_WRITE" or
"NONSTRICT_READ_WRITE",

By default this is READ_ONLY.
Temporarily change fetch mode
DQL:
$query->getResult(
Doctrine\ORM\Query::HYDRATE_ARRAY);

Query Builder:
$qb = new \Doctrine\ORM\QueryBuilder;

$arrayResults = $qb->getQuery()->
getArrayResult();

$scalarResults = $qb->getQuery()->
getScalarResult();
By default Doctrine will fetch all of the properties for a given entity.
If you do not need them, only fetch what you need.

Don’t load the whole entity if you only need a reference to it.
Partial-select statements
Partial-select statements
Partial-select statements
Partial-select statements
Partial-select statements
Doctrine’s Reference Proxies
<?php

// $em instanceof EntityManager
// $itemId comes from anywhere, probably a request parameter
$itemId = $filteredItemId;

$cart = new Cart;
$item = $em->getReference(
'MyProject\Model\Item', $itemId
);
$cart->addItem($item);

$em->persist($cart);
$em->flush();

//$em is the entity manager
$qb = $em->createQueryBuilder();

$qb
->select('Article')
->from('Entity\Article')
;

//$em is the entity manager
$qb = $em->createQueryBuilder();

$qb
->select('Article.title')
->from('Entity\Article')
;
//$em is the entity manager
$qb = $em->createQueryBuilder();

$qb
->select('partial Article.{title}')
->from('Entity\Article')
;

//$em is the entity manager
$qb = $em->createQueryBuilder();

$qb
->select('partial Article.{id,title,summary}')
->from('Entity\Article')
;

• You can always refine the search to be more
specific, later.
• Constrain relationships as much as possible.
• The good and bad of lazy loading.
• Hydration Modes
• Read only Entities
• Partial-select statements
• Doctrine’s Reference Proxies

Review
What is wrong with this code?
public function switchAdminTo($userId)
{
$currentAdmin = $this->getAdmin();
/* @var User $newAdmin */
$newAdmin = $this->users->get($userId);
if (!$newAdmin) {
throw new \DomainException(
'New administrator account is not found'
);
}
$currentAdmin->revokeAdmin();
$newAdmin->grantAdmin();
}

get
Doctrine and var_dump()
Lazy–load proxies always contain an instance of Doctrine’s EntityManager and all its dependencies.

You can use Doctrine\Common\Util\Debug::dump() to restrict the dumping to a human readable level.

Debug::dump() method just ignores any occurrences of it in Proxy instances.

var_dump(array(
'dql' => $query->getDQL(),
'parameters' => $query->getParameters(),
));
DQL and Updates
DQL UPDATE statements are ported directly into a Database UPDATE statement.

They bypass any locking scheme and do not increment the version column.

Entities that are already loaded into the persistence context will NOT be synced with the updated database state.

It is recommended to call EntityManager#clear() and retrieve new instances of any affected entity.

Built-in profiler for monitoring
If you are using Symfony 2, you can use the in-built profiler for monitoring the number of queries generated by doctrine.

Thanks to it, you can always spot weak points of your application – rise of the number of queries can show you that something may be inefficient, some queries may leak.

In the Symfony Profiler you can also get into details of the database queries.
• First we will look at the biggest area of varied performance and options, getting data out of the database.

• Then we will look at what Doctrine is doing behind the scenes.

• Next we will look at some other best practices.

• Finally we will look at other performance enhancements.
Doctrine 2 - Best Practices
PDO - PHP Data Objects
DBAL - Database Abstraction Layer
ORM - Object Relational Mapper
Doctrine 2 - What is it?
Coding Best practices For Doctrine
• Don’t use public properties on entities
• Avoid composite keys
Whenever you access a public property of a proxy object the return value will be null. Doctrine uses this fact to make the entity lazy load.
Composite keys require additional work by Doctrine and thus have a higher probability of errors.
Coding Best practices For Doctrine
• Use events judiciously
• Use cascades judiciously
Avoid listeners (or merge them by event type (flush, update), it will avoid the lookup time and the loop for subscribed events)
Automatic cascades of the persist/remove/merge/etc. operations are very handy but should be used wisely.
Coding Best practices For Doctrine
• Don’t use special characters
• Don’t use identifier quoting
Avoid using any non-ASCII characters in class, field, table or column names. Doctrine itself is not unicode-safe in many places.
Identifier quoting is a workaround for using reserved words that often causes problems in edge cases. Better to avoid the issue.
Coding Best practices For Doctrine
• Initialize collections in the constructor
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;

class User {
private $addresses;
private $articles;

public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;
}
}
Coding Best practices For Doctrine
• Don’t map foreign keys to fields in an entity
• Use explicit transaction demarcation
Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships.
While Doctrine will automatically wrap all DML operations in a transaction on flush(), it is considered best practice to explicitly set the transaction boundaries yourself.
Coding Best practices For Doctrine
• Use Prepared statements
“If you care about performance and don’t use a bytecode cache then you don’t really care about performance.
Please get one and start using it.”

Stas Malyshev, Core Contributor to PHP and Zend Employee
Improved Performance
1. Bytecode Cache
2. Metadata and Query Caches
3. Alternative Query Result Formats
4. Temporarily change fetch mode in DQL
5. Read-Only Entities
6. Extra-Lazy Collections
7. Apply Best Practices
$qb = $em->createQueryBuilder();
$qb
->select('Article', 'Comment')
->from('Entity\Article', 'Article')
->leftJoin('Article.comment', 'Comment')
->where($qb->expr()->eq('Article.title', ':filter_title')
->setParameter('filter_title', $some_user_input)
;
Improved Performance
Thin Controller actions:
/**
* @Route("/edit", name="product_update")
* @Method("POST")
* @Secure("ROLE_ADMIN")
* @Template("AcmeDemoBundle:Product:edit.html.twig")
*/
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$product = $em->getRepository('AcmeDemoBundle:Product')->find($id);

if (!$product) {
throw $this->createNotFoundException('Unable to find Product entity.');
}

$editForm = $this->createForm(new ProductType(), $product);
Improved Performance
Thin Controller actions:
/**
* @Route("/edit", name="product_update")
* @Method("POST")
* //@ParamConverter
* @Secure("ROLE_ADMIN")
* @Template("AcmeDemoBundle:Product:edit.html.twig")
*/

public function updateAction(Request $request, Product $product)
{
$editForm = $this->createForm(new ProductType(), $product)
Improved Performance
One of the Best practices is encouragement to ...
Bulk processing
$users = $query->execute();
foreach ($users as $user) {
foreach ($users->getGroups() as $group) {
}
}

$userList = $query->iterate()
foreach ($userList as $user) {
echo sizeof($userList); // always 1
foreach ($users->getGroups() as $group) {
}
}
Use APC
Hydration is expensive
JUST DO IT
Full transcript