Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1170 lines (1076 sloc) 38.9 KB
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Doctrine 2 ORM Tutorial</title>
<!-- jQuery + jQuery history -->
<script type="text/javascript" src="vendor/slippy/src/jquery.min.js"></script>
<script type="text/javascript" src="vendor/slippy/src/jquery.history.js"></script>
<!-- Slippy -->
<script type="text/javascript" src="vendor/slippy/src/slippy.js"></script>
<link type="text/css" rel="stylesheet" href="vendor/slippy/src/slippy.css"/>
<link type="text/css" rel="stylesheet" href="vendor/slippy/src/slippy-pure.css"/>
<!-- Syntax highlighting core and style -->
<script type="text/javascript" src="vendor/syntax-highlighter/scripts/XRegExp.js"></script>
<script type="text/javascript" src="vendor/syntax-highlighter/scripts/shCore.js"></script>
<!--<script type="text/javascript" src="vendor/syntax-highlighter/scripts/shAutoloader.js"></script>-->
<link type="text/css" rel="stylesheet" href="vendor/syntax-highlighter/styles/shCore.css"/>
<link type="text/css" rel="stylesheet" href="vendor/syntax-highlighter/styles/shCoreMDUltra.css"/>
<!-- Highlighter brushes (php, bash) -->
<script type="text/javascript" src="vendor/syntax-highlighter/scripts/shBrushPhp.js"></script>
<script type="text/javascript" src="vendor/syntax-highlighter/scripts/shBrushBash.js"></script>
<script type="text/javascript">
/*SyntaxHighlighter.autoloader(
'applescript vendor/syntax-highlighter/scripts/shBrushAppleScript.js',
'actionscript3 as3 vendor/syntax-highlighter/scripts/shBrushAS3.js',
'bash shell vendor/syntax-highlighter/scripts/shBrushBash.js',
'coldfusion cf vendor/syntax-highlighter/scripts/shBrushColdFusion.js',
'cpp c vendor/syntax-highlighter/scripts/shBrushCpp.js',
'c# c-sharp csharp vendor/syntax-highlighter/scripts/shBrushCSharp.js',
'css vendor/syntax-highlighter/scripts/shBrushCss.js',
'delphi pascal vendor/syntax-highlighter/scripts/shBrushDelphi.js',
'diff patch pas vendor/syntax-highlighter/scripts/shBrushDiff.js',
'erl erlang vendor/syntax-highlighter/scripts/shBrushErlang.js',
'groovy vendor/syntax-highlighter/scripts/shBrushGroovy.js',
'java vendor/syntax-highlighter/scripts/shBrushJava.js',
'jfx javafx vendor/syntax-highlighter/scripts/shBrushJavaFX.js',
'js jscript javascript vendor/syntax-highlighter/scripts/shBrushJScript.js',
'perl pl vendor/syntax-highlighter/scripts/shBrushPerl.js',
'php vendor/syntax-highlighter/scripts/shBrushPhp.js',
'text plain vendor/syntax-highlighter/scripts/shBrushPlain.js',
'py python vendor/syntax-highlighter/scripts/shBrushPython.js',
'ruby rails ror rb vendor/syntax-highlighter/scripts/shBrushRuby.js',
'scala vendor/syntax-highlighter/scripts/shBrushScala.js',
'sql vendor/syntax-highlighter/scripts/shBrushSql.js',
'vb vbnet vendor/syntax-highlighter/scripts/shBrushVb.js',
'xml xhtml xslt html vendor/syntax-highlighter/scripts/shBrushXml.js'
);*/
$(function() {
$(".slide").slippy({});
SyntaxHighlighter.defaults['toolbar'] = false;
SyntaxHighlighter.all();
});
</script>
<!-- Custom styling -->
<link type="text/css" rel="stylesheet" href="style/slippy-styled.css"/>
</head>
<body>
<div class="slide" style="text-align: center;">
<h1>Doctrine 2 ORM</h1>
<h3>Object Relational Mapper for PHP 5.3</h3>
<p style="padding-top: 20px;">
<img src="image/common_icon.png" alt="Doctrine"/>
</p>
</div>
<div class="slide">
<h2>Some history of RDBMS persistence in PHP:</h2>
<ol>
<li>mysql_query, mysql_fetch_array, mysql_*</li>
<li>mysqli, PDO</li>
<li>Doctrine 1.2, Propel 1.6, Zend_Db</li>
</ol>
</div>
<div class="slide">
<h2>mysql_query</h2>
</div>
<div class="slide">
<pre class="brush: php">
mysql_connect('localhost', 'user', 'password');
mysql_select_db('test');
//simple example
mysql_query('INSERT INTO greetings (content) VALUES ("' . mysql_real_escape_string($_POST['greeting']) . '")');
//comments example
mysql_query('INSERT INTO users (username) VALUES ("' . mysql_real_escape_string($_POST['username']) . '")');
mysql_query('INSERT INTO comments (user_id, content) VALUES (' . mysql_insert_id() . ', "' . mysql_real_escape_string($_POST['content']) . '")');
</pre>
</div>
<div class="slide">
<h3>mysql_query</h3>
<ul>
<li class="cons">
We had to write our queries and our security (mysql_real_escape_string()) by ourselves.
</li>
<li class="cons">
Also, we had to keep track of identifiers of the inserted fields by ourselves.
</li>
<li class="cons">
We had to rewrite every function name (and probably also functionality) when switching database vendor.
</li>
<li class="cons">
Not really OO friendly, heh?
</li>
</ul>
</div>
<div class="slide">
<h2>PDO</h2>
</div>
<div class="slide">
<pre class="brush: php">
try {
$dbh = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
//simple example
$sth = $dbh-&gt;prepare('INSERT INTO greetings (content) VALUES (:content);
$sth-&gt;execute(array(':content' =&gt; $_POST['content']));
//comments example
$sth = $dbh-&gt;prepare('INSERT INTO users (username) VALUES (:username)');
$sth-&gt;execute(array(':username' =&gt; $_POST['username']));
$sth = $dbh-&gt;prepare('INSERT INTO comments (user_id, content) VALUES (user_id, :content)');
$sth-&gt;execute(array(':user_id' =&gt; $dbh-&gt;lastInsertId(), ':content' =&gt; $_POST['content']);
} catch (PDOException $e) {
echo $e-&gt;getMessage();
}
</pre>
</div>
<div class="slide">
<h3>PDO</h3>
<ul>
<li class="pro">
We now can connect to different databases. MySQL is no more the only option!
</li>
<li class="pro">
Security is given by named parameters, escaped for us by PDO!
</li>
<li class="pro">
Prepared statements make our queries easier to reuse!
</li>
<li class="pro">
Exceptions are used, we can now catch them!
</li>
<li class="cons">
Queries are still performed in the SQL dialect of the vendor, which could make our code not 100% portable.
</li>
<li class="cons">
Still have to keep track of identifiers of the inserted fields by ourselves.
</li>
</ul>
</div>
<div class="slide">
<h2>Doctrine 1.2</h2>
<p>An Active Record implementation</p>
</div>
<div class="slide">
<h3>Greeting record definition</h3>
<pre class="brush: php">
class Greeting extends Doctrine_Record
{
public function setTableDefinition()
{
$this-&gt;setTableName('greetings');
$this-&gt;hasColumn(
'content',
'string',
255,
array(
'type' =&gt; 'string',
'length' =&gt; '255'
)
);
}
}
</pre>
</div>
<div class="slide">
<pre class="brush: php">
//simple example
$greeting = new Greeting();
$greeting-&gt;content = $_POST['content'];
$greeting-&gt;save();
</pre>
</div>
<div class="slide">
<h3>User record definition</h3>
<pre class="brush: php">
class User extends Doctrine_Record
{
public function setTableDefinition()
{
$this-&gt;setTableName('users');
$this-&gt;hasColumn(
'username',
'string',
255,
array(
'type' =&gt; 'text',
)
);
}
public function setUp()
{
$this-&gt;hasMany(
'Comments as Comments',
array(
'refClass' =&gt; 'Comment',
'local' =&gt; 'id',
'foreign' =&gt; 'user_id'
)
);
}
}
</pre>
</div>
<div class="slide">
<h3>Person record definition</h3>
<pre class="brush: php">
class Comment extends Doctrine_Record
{
public function setTableDefinition()
{
$this-&gt;setTableName('comments');
$this-&gt;hasColumn(
'content',
'string',
255,
array(
'type' =&gt; 'string',
'length' =&gt; '255'
)
);
$this-&gt;hasColumn(
'user_id',
'string',
255,
array(
'type' =&gt; 'integer',
)
);
}
public function setUp()
{
$this-&gt;hasOne(
'User',
array(
'refClass' =&gt; 'User',
'local' =&gt; 'user_id',
'foreign' =&gt; 'id'
)
);
}
}
</pre>
</div>
<div class="slide">
<pre class="brush: php">
//comments example
$user = new User();
$user-&gt;username = $_POST['username'];
$comment = new Comment();
$comment->content = $_POST['content'];
$user-&gt;Comments[] = $comment;
$comment-&gt;User = $user;
$user-&gt;save();
</pre>
</div>
<div class="slide">
<h3>Doctrine 1.2</h3>
<ul>
<li class="pro">
Objects are written to DB by Doctrine: no more SQL!
</li>
<li class="pro">
Associations are managed by Doctrine: no more foreign key mess!
</li>
<li class="pro">
Object-Oriented approach!
</li>
<li class="pro">
Database is abstracted by a powerful DBAL layer: no more SQL portability troubles!
</li>
<li class="pro">
Database can be generated by our models!
</li>
<li class="cons">
Table definition is set in our domain logic.
</li>
<li class="cons">
A lot of PHP "magic" is happening (__set, __get, etc.).
</li>
<li class="cons">
Our objects must extend Doctrine_Record to be used with Doctrine
</li>
</ul>
</div>
<div class="slide">
<h2>Doctrine 2</h2>
</div>
<div class="slide">
<h3>What is Doctrine 2?</h3>
<ul>
<li class="pro">
Doctrine 2 is a Data Mapper implementation:
<br/>
<img src="image/databaseMapperSketch.gif" alt="Data Mapper" style="width: 60%; margin: 10px;"/>
</li>
<li class="pro">
Used the well known Java Persistence API (JPA, aka JSR 317) as initial blueprint
</li>
<li class="pro">
It separates persistence and domain logic. You are now able to port your logic to SQL, NoSQL, XML mappers, etc.
</li>
<li class="pro">
It provides around 3X better performance than Doctrine 1.2.
</li>
<li class="pro">
Your objects won't need to extend anything.
</li>
<li class="pro">
You are free to save anything you want to your database, just map it!
</li>
<li class="pro">
You can even map existing classes to database tables!
</li>
</ul>
</div>
<div class="slide">
<h2>Doctrine 2 Requirements</h2>
<ul>
<li>
PHP 5.3
</li>
<li>
Doctrine\Common (bundled)
<br/>
&#160;&#160;A set of utilities common to all Doctrine projects
</li>
<li>
Doctrine\DBAL (bundled)
<br/>
&#160;&#160;A very powerful Database ABstraction Layer
</li>
<li>
Symfony\Component\Console (bundled)
<br/>
&#160;&#160;A CLI runner tool
</li>
<li>
Symfony\Component\Yaml (bundled)
<br/>
&#160;&#160;If you work with YAML configuration
</li>
</ul>
</div>
<div class="slide">
<h2>Getting started</h2>
<pre class="brush: bash">
mkdir doctrine-demo
cd doctrine-demo
mkdir library
mkdir library/Entity
touch library/Entity/Greeting.php
mkdir library/EntityProxy
chmod +w library/EntityProxy
echo "{}" &gt; composer.json
curl -s https://getcomposer.org/installer | php
./composer.phar require doctrine/orm:2.*
touch bootstrap.php
</pre>
</div>
<div class="slide">
<h2>Create entities...</h2>
</div>
<div class="slide">
<pre class="brush: php">
namespace Entity;
class Greeting
{
/** @var int */
private $id;
/** @var string */
private $content;
}
</pre>
</div>
<div class="slide">
<h2>...add getters, setters, constructors, other methods...</h2>
</div>
<div class="slide">
<pre class="brush: php, highlight: [12, 19, 26, 33];">
namespace Entity;
class Greeting
{
/** @var int */
private $id;
/** @var string */
private $content;
public function __construct($content) {
$this->setContent($content);
}
/**
* @return int
*/
public function getId() {
return $this->id;
}
/**
* @return string
*/
public function getContent() {
return $this->content;
}
/**
* @param string $content
*/
public function setContent($content) {
$this->content = (string) $content;
}
}
</pre>
</div>
<div class="slide">
<h2>...add mappings (@Annotations) to entities...</h2>
</div>
<div class="slide">
<pre class="brush: php, highlight: [3, 6, 12, 13, 14, 20];">
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Greeting
{
/**
* @ORM\Id()
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @var int
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
* @var string
*/
private $content;
public function __construct($content) {
$this->setContent($content);
}
/**
* @return int
*/
public function getId() {
return $this->id;
}
/**
* @return string
*/
public function getContent() {
return $this->content;
}
/**
* @param string $content
*/
public function setContent($content) {
$this->content = (string) $content;
}
}
</pre>
</div>
<div class="slide">
<h2>Creating an EntityManager</h2>
</div>
<div class="slide">
<h3>Autoloading:</h3>
<pre class="brush: php">
&lt;?php
// bootstrap.php
use Doctrine\ORM\Tools\Setup,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration,
Doctrine\Common\Cache\ArrayCache as Cache,
Doctrine\Common\Annotations\AnnotationRegistry,
Doctrine\Common\ClassLoader;
//autoloading
require_once __DIR__ . '/vendor/autoload.php';
$loader = new ClassLoader('Entity', __DIR__ . '/library');
$loader-&gt;register();
$loader = new ClassLoader('EntityProxy', __DIR__ . '/library');
$loader-&gt;register();
</pre>
</div>
<div class="slide">
<h3>Configuration and metadata</h3>
<pre class="brush: php">
//configuration
$config = new Configuration();
$cache = new Cache();
$config-&gt;setQueryCacheImpl($cache);
$config-&gt;setProxyDir(__DIR__ . '/library/EntityProxy');
$config-&gt;setProxyNamespace('EntityProxy');
$config-&gt;setAutoGenerateProxyClasses(true);
//mapping (example uses annotations, could be any of XML/YAML or plain PHP)
AnnotationRegistry::registerFile(__DIR__ . '/library/doctrine-orm/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
$driver = new Doctrine\ORM\Mapping\Driver\AnnotationDriver(
new Doctrine\Common\Annotations\AnnotationReader(),
array(__DIR__ . '/library/Entity')
);
$config->setMetadataDriverImpl($driver);
$config->setMetadataCacheImpl($cache);
</pre>
</div>
<div class="slide">
<h3>Creating the EntityManager</h3>
<pre class="brush: php">
//getting the EntityManager
$em = EntityManager::create(
array(
'driver' =&gt; 'pdo_sqlite',
'path' =&gt; 'database.sqlite'
),
$config
);
</pre>
</div>
<div class="slide">
<h3>Final result:</h3>
<pre class="brush: php">
&lt;?php
// bootstrap.php
use Doctrine\ORM\Tools\Setup,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration,
Doctrine\Common\Cache\ArrayCache as Cache,
Doctrine\Common\Annotations\AnnotationRegistry,
Doctrine\Common\ClassLoader;
//autoloading
require_once __DIR__ . '/vendor/autoload.php';
$loader = new ClassLoader('Entity', __DIR__ . '/library');
$loader-&gt;register();
$loader = new ClassLoader('EntityProxy', __DIR__ . '/library');
$loader-&gt;register();
//configuration
$config = new Configuration();
$cache = new Cache();
$config-&gt;setQueryCacheImpl($cache);
$config-&gt;setProxyDir(__DIR__ . '/library/EntityProxy');
$config-&gt;setProxyNamespace('EntityProxy');
$config-&gt;setAutoGenerateProxyClasses(true);
//mapping (example uses annotations, could be any of XML/YAML or plain PHP)
AnnotationRegistry::registerFile(__DIR__ . '/library/doctrine-orm/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
$driver = new Doctrine\ORM\Mapping\Driver\AnnotationDriver(
new Doctrine\Common\Annotations\AnnotationReader(),
array(__DIR__ . '/library/Entity')
);
$config->setMetadataDriverImpl($driver);
$config->setMetadataCacheImpl($cache);
//getting the EntityManager
$em = EntityManager::create(
array(
'driver' =&gt; 'pdo_sqlite',
'path' =&gt; 'database.sqlite'
),
$config
);
</pre>
</div>
<div class="slide">
<h2>Generating the schema</h2>
<h3>(Configuring doctrine-cli.php)</h3>
</div>
<div class="slide">
<h3>CLI runner</h3>
<pre class="brush: php">
&lt;?php
//doctrine-cli.php
use Symfony\Component\Console\Helper\HelperSet,
Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper,
Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper,
Doctrine\ORM\Tools\Console\ConsoleRunner;
require_once __DIR__ . '/bootstrap.php';
$helperSet = new HelperSet(array(
'em' => new EntityManagerHelper($em),
'conn' => new ConnectionHelper($em->getConnection())
));
ConsoleRunner::run($helperSet);
</pre>
</div>
<div class="slide">
<h3>Generating the schema</h3>
<pre class="brush: bash, highlight: [5];">
$ php doctrine-cli.php orm:schema-tool:create
ATTENTION: This operation should not be executed in a production environment.
Creating database schema...
Database schema created successfully!
</pre>
</div>
<div class="slide">
<p>Generated database as seen by SQLite browser</p>
<img src="image/database-status.png" alt="Generated Database"/>
</div>
<div class="slide">
<h2>Working with the EntityManager</h2>
</div>
<div class="slide">
<h3>Saving a "Entity\Greeting" object</h3>
<pre class="brush: php, highlight: [11, 14];">
//examples/1.php
use Entity\Greeting;
require_once __DIR__ . '/../bootstrap.php';
//Creating our greeting
$greeting = new Greeting('Hello World!');
//Registering $greeting with the EntityManager
$em->persist($greeting);
//Flushing all changes to database
$em->flush();
echo 'OK!';
</pre>
</div>
<div class="slide">
<h3>Retrieving an "Entity\Greeting" object</h3>
<pre class="brush: php, highlight: [5, 9, 10];">
//examples/2.php
require_once __DIR__ . '/../bootstrap.php';
//Finding Greeting with id = 1
$greeting = $em->find('Entity\Greeting', 1);
if($greeting) {
//The EntityManager has already provided us an object of type Entity\Greeting!
echo 'Found a greeting (instance of ' . get_class($greeting)
. ') with content ' . $greeting->getContent();
}else{
echo 'Couldn\'t find Greeting with id=1';
}
</pre>
</div>
<div class="slide">
<h3>Updating an "Entity\Greeting" object</h3>
<pre class="brush: php, highlight: [5, 11, 13];">
//examples/3.php
require_once __DIR__ . '/../bootstrap.php';
//Finding Greeting with id = 1
$greeting = $em->find('Entity\Greeting', 1);
if($greeting) {
echo $greeting->getContent() . PHP_EOL;
echo 'Changing the contents of found Greeting to "Hello Test!"' . PHP_EOL;
//Using Entity\Greeting to set a new content for the $greeting!
$greeting->setContent('Hello Test!');
//Flushing changes to database (triggers SQL updates)
$em->flush();
echo 'Now try loading 2.php again!' . PHP_EOL;
}else{
echo 'Couldn\'t find Greeting with id=1';
}
</pre>
</div>
<div class="slide">
<h3>Finding "Entity\Greeting" objects</h3>
<pre class="brush: php, highlight: [5, 8, 11, 15, 20];">
//examples/4.php
require_once __DIR__ . '/../bootstrap.php';
//A repository is like a "Table" containing our entities of a specified type
$repository = $em->getRepository('Entity\Greeting');
//Finding all Entity\Greeting with content = "Hello World!"
$worldGreetings = $repository->findBy(array('content' => 'Hello World!'));
//Finding all Entity\Greeting with content = "Hello Test!"
$testGreetings = $repository->findBy(array('content' => 'Hello Test!'));
//Displaying results
echo 'Found ' . count($worldGreetings) . ' "Hello World!" greetings:' . PHP_EOL;
foreach($worldGreetings as $worldGreeting) {
echo ' - ' . $worldGreeting->getId() . PHP_EOL;
}
echo 'Found ' . count($testGreetings) . ' "Hello Test!" greetings:' . PHP_EOL;
foreach($testGreetings as $testGreeting) {
echo ' - ' . $testGreeting->getId() . PHP_EOL;
}
</pre>
</div>
<div class="slide">
<h3>Retrieving "Entity\Greeting" objects via DQL</h3>
<pre class="brush: php, highlight: [6, 11];">
//examples/5.php
require_once __DIR__ . '/../bootstrap.php';
//Creating a DQL query that selects all greetings with id >= 5 and id &lt;= 10
$greetings = $em
->createQuery('SELECT g FROM Entity\Greeting g WHERE g.id >= 5 AND g.id &lt;= 10')
->getResult();
//Displaying results
echo 'Found ' . count($greetings) . ' Entity\Greeting:' . PHP_EOL;
foreach($greetings as $greeting) {
echo ' - ' . $greeting->getId() . ' => ' . $greeting->getContent() . PHP_EOL;
}
</pre>
</div>
<div class="slide">
<h3>Deleting "Entity\Greeting" objects</h3>
<pre class="brush: php, highlight: [6, 7, 14, 16];">
//examples/6.php
require_once __DIR__ . '/../bootstrap.php';
//Finding the last inserted greeting
$greetings = $em
->createQuery('SELECT g FROM Entity\Greeting g ORDER BY g.id DESC')
->setMaxResults(1) //we want only one result
->getResult();
if(!empty($greetings)) {
$greeting = reset($greetings);
echo 'Found greeting with id "' . $greeting->getId()
. '" and content "' . $greeting->getContent() . '"' . PHP_EOL;
$em->remove($greeting);
//Triggers delete
$em->flush();
echo 'Greeting deleted!' . PHP_EOL;
} else {
echo 'Could not find any Greeting' . PHP_EOL;
}
</pre>
</div>
<div class="slide">
<h2>Working with associations</h2>
</div>
<div class="slide">
<h3>You will need additional code:</h3>
<ul>
<li>Entity\Post</li>
<li>Entity\Comment</li>
</ul>
</div>
<div class="slide">
<h3>We don't define IDs, we define how objects are related:</h3>
<p>
<strong>ONE</strong> Entity\Post
<br/>
&#160;&#160;<strong>HAS MANY</strong> Entity\Comment
</p>
<p>
<strong>ONE</strong> Entity\Comment
<br/>
&#160;&#160;<strong>HAS ONE</strong> Entity\Post
</p>
</div>
<div class="slide">
<h3>In Doctrine terms:</h3>
<p>
Entity\Post::comments
<br/>
&#160;&#160; is a <strong>OneToMany</strong> relation to <strong>Entity\Comment</strong>, <strong>mapped by</strong> Entity\Comment#post
</p>
<p>
Entity\Comment::post
<br/>
&#160;&#160; is a <strong>ManyToOne</strong> relation to <strong>Entity\Post</strong>, <strong>inversed by</strong> Entity\Post#comments
</p>
</div>
<div class="slide">
<h3>Available relations:</h3>
<ul>
<li>
OneToMany (mapped by ManyToOne)
</li>
<li>
ManyToOne (inversed by OneToMany)
</li>
<li>
OneToOne (inversed/mapped by OneToOne)
</li>
<li>
ManyToMany (inversed/mapped by ManyToMany)
</li>
</ul>
<p>
OneToMany and ManyToMany relations are represented in Doctrine by instances of the Doctrine\Common\Collections\Collection interface.
</p>
</div>
<div class="slide">
<h3>Relations ownership</h3>
<p>
Relationship between entities may be <strong>inversed (bidirectional)</strong> or <strong>unidirectional</strong>.
</p>
</div>
<div class="slide">
<p>
There is always an <strong>owning side</strong> of the relation.
</p>
<p>
A bidirectional relationship has both an <strong>owning side</strong> and an <strong>inverse side</strong>.
</p>
<p>
A unidirectional relationship has only an <strong>owning side</strong>.
</p>
<p>
The <strong>owning side</strong> of the relation <strong>is the one checked by Doctrine</strong> to determine changes to the relation graph.
</p>
</div>
<div class="slide">
<p>
Some rules apply to relations, you can read them at
<br/>
<a href="http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html" target="_blank">
http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html
</a>
</p>
<p>
<strong>Please be sure to have read them before getting to work with Doctrine.</strong>
</p>
</div>
<div class="slide">
<h2>Using associations</h2>
<h3>We will map some entities to check how associations work...</h3>
</div>
<div class="slide">
<pre class="brush: php, highlight: [12, 13, 18, 19, 25, 32];">
/** @ORM\Entity */
class User
{
/** @ORM\Id() @ORM\Column(type="integer") @ORM\GeneratedValue(strategy="AUTO") @var int */
private $id;
/** @ORM\Column(type="string", length=255) @var string */
private $login;
/**
* @ORM\OneToMany(targetEntity="Entity\Comment", mappedBy="user")
* @var Collection
*/
private $comments;
public function __construct($login) {
//Initializing collection. Doctrine recognizes Collections, not arrays!
$this->comments = new ArrayCollection();
$this->setLogin($login);
}
//Getters and setters
/** @return Collection */
public function getComments() {
return $this->comments;
}
/** @param Comment $comment */
public function addComment(Comment $comment) {
$this->comments->add($comment);
$comment->setUser($this);
}
}
</pre>
</div>
<div class="slide">
<pre class="brush: php, highlight: [12, 13, 23, 28];">
/** @ORM\Entity */
class Comment
{
/** @ORM\Id() @ORM\Column(type="integer") @ORM\GeneratedValue(strategy="AUTO") @var int */
private $id;
/** @ORM\Column(type="string", length=255) @var string */
private $content;
/**
* @ORM\ManyToOne(targetEntity="Entity\User", inversedBy="comments")
* @var User|null
*/
private $user;
public function __construct($content) {
$this->setContent($content);
}
//Setters, getters
/** @return User|null */
public function getUser() {
return $this->user;
}
/** @param User $user */
public function setUser(User $user) {
if($user === null || $user instanceof User) {
$this->user = $user;
} else {
throw new InvalidArgumentException('$user must be instance of Entity\User or null!');
}
}
}
</pre>
</div>
<div class="slide">
<h2>Now to the examples</h2>
</div>
<div class="slide">
<h3>Creating a User with a related Comment</h3>
<pre class="brush: php, highlight: [8, 9, 11, 12, 14, 17];">
//examples/7.php
use Entity\User,
Entity\Comment;
require_once __DIR__ . '/../bootstrap.php';
//Creating our user
$user = new User('Marco Pivetta');
$em->persist($user);
$comment = new Comment('This is a sample post!');
$em->persist($comment);
$user->addComment($comment);
//Flushing all changes to database
$em->flush();
echo 'OK!';
</pre>
</div>
<div class="slide">
<h3>Fetching the User and it's related Comment</h3>
<pre class="brush: php, highlight: [5, 11, 12, 13];">
//examples/8.php
require_once __DIR__ . '/../bootstrap.php';
//Finding previously persisted user
$user = $em->find('Entity\User', 1);
if($user) {
echo 'Found an Entity\User: ' . PHP_EOL
. $user->getId() . ' => ' . $user->getLogin() . '(' . get_class($user) . ')' . PHP_EOL
. 'and ' . $user->getComments()->count() . ' Entity\Comment attached to it: ' . PHP_EOL;
foreach($user->getComments() as $comment) {
echo ' ' . $comment->getId() . ' => ' . $comment->getContent()
. ' (' . get_class($comment) . ')' . PHP_EOL;
}
} else {
echo 'Could not find Entity\User with id=1';
}
</pre>
</div>
<div class="slide">
<h3>Attaching a Comment to an existing User</h3>
<pre class="brush: php, highlight: [7, 14, 15, 16, 17];">
//examples/9.php
use Entity\Comment;
require_once __DIR__ . '/../bootstrap.php';
//Finding previously persisted user
$user = $em->find('Entity\User', 1);
if($user) {
echo 'Found an Entity\User: ' . PHP_EOL
. $user->getId() . ' => ' . $user->getLogin() . '(' . get_class($user) . ')' . PHP_EOL
. 'and ' . $user->getComments()->count() . ' Entity\Comment attached to it: ' . PHP_EOL;
echo 'Adding a Comment to the user';
$comment = new Comment('Comment generated at ' . time());
$em->persist($comment);
$user->addComment($comment);
$em->flush();
echo 'Comment has been attached to the user, try 8.php!';
} else {
echo 'Could not find Entity\User with id=1';
}
</pre>
</div>
<div class="slide">
<h3>Removing a Comment attached to a User</h3>
<pre class="brush: php, highlight: [4, 10, 13, 14];">
//examples/10.php
require_once __DIR__ . '/../bootstrap.php';
$user = $em->find('Entity\User', 1);
if($user) {
echo 'Found an Entity\User: ' . PHP_EOL
. $user->getId() . ' => ' . $user->getLogin() . '(' . get_class($user) . ')' . PHP_EOL
. 'and ' . $user->getComments()->count() . ' Entity\Comment attached to it: ' . PHP_EOL;
if($comment = $user->getComments()->first()) {
echo 'Removing the first attached comment!' . PHP_EOL;
echo 'Removing comment with id=' . $comment->getId() . PHP_EOL;
$em->remove($comment);
$em->flush();
} else {
echo 'Could not find any comments to remove...' . PHP_EOL;
}
} else {
echo 'Could not find Entity\User with id=1';
}
</pre>
</div>
<div class="slide">
<h3>Joins in DQL: finding all users with a comment with id > 5</h3>
<pre class="brush: php, highlight: [6, 7, 10];">
//examples/11.php
require_once __DIR__ . '/../bootstrap.php';
echo 'Searching all users with a comment with id > 5: ' . PHP_EOL;
$users = $em
->createQuery('SELECT u FROM Entity\User u JOIN u.comments c WHERE c.id > :id')
->setParameter('id', 5)
->getResult();
echo 'Found ' . count($users) . ':' . PHP_EOL;
foreach($users as $user) {
echo ' ' . $user->getId() . ' => ' . $user->getLogin() . ' (' . get_class($user) . ')' . PHP_EOL;
}
</pre>
</div>
<div class="slide">
<h2>Other cool features of Doctrine 2</h2>
<p>
This tutorial should be extended with following examples and features:
</p>
<ul>
<li>
inheritance types
</li>
<li>
mapping types - custom mapping types
</li>
<li>
transactions
</li>
<li>
caches, performance, memory leaks
</li>
<li>
other mapping drivers
</li>
<li>
proxies
</li>
<li>
events
</li>
<li>
cascade operations
</li>
<li>
optimistic locking - pessimistic locking
</li>
<li>
NativeSQL
</li>
<li>
Query Builder
</li>
<li>
Custom repositories
</li>
</ul>
</div>
<div class="slide">
<h2>Other cool features of Doctrine 2</h2>
<p>
This tutorial should be extended with following examples and features:
</p>
<ul>
<li>
other annotations:
<ul>
<li>@Table</li>
<li>@Entity</li>
<li>@GeneratedValue</li>
<li>@JoinTable</li>
<li>@JoinColumn</li>
<li>@Index</li>
<li>@UniqueIndex</li>
<li>@OrderBy</li>
<li>@Version</li>
</ul>
</li>
</ul>
</div>
<div class="slide">
<h2>Warnings</h2>
__clone
__wakeup
dumping
entity state
</div>
<div class="slide">
<h2>Please help the community!</h2>
<p>
You can find, fork, edit and help me with these slides at
<br/>
<a href="https://github.com/Ocramius/Doctrine2ORMSlidesTutorial" target="_blank">
https://github.com/Ocramius/Doctrine2ORMSlidesTutorial
</a>
</p>
<p>
You can find the Doctrine project pages at
<br/>
<a href="http://www.doctrine-project.org/" target="_blank">
http://www.doctrine-project.org/
</a>
</p>
<p>
All what you have seen is possible only because of a good community helping in this project, so please be part of it and share!
</p>
</div>
<div class="slide">
<h2>Thank You</h2>
<p>
Marco Pivetta
<br/>
<a href="http://twitter.com/Ocramius" target="_blank">@Ocramius</a>
<br/>
<a href="mailto:ocramius@gmail.com">ocramius@gmail.com</a>
</p>
</div>
</body>
</html>