1
0
mirror of synced 2024-12-15 07:36:03 +03:00

Merge commit 'origin/master'

This commit is contained in:
Roman S. Borschel 2010-05-13 13:06:20 +02:00
commit 7d79777bfb
8 changed files with 279 additions and 10 deletions

View File

@ -295,11 +295,11 @@ We then go on specifying the definition of a Bug:
<field name="created" column="bug_created" type="datetime" />
<field name="status" column="bug_status" type="string" />
<many-to-one target-entity="User" field="reporter">
<many-to-one target-entity="User" field="reporter" inversed-by="reportedBugs">
<join-column name="reporter_id" referenced-column-name="account_id" />
</many-to-one>
<many-to-one target-entity="User" field="engineer">
<many-to-one target-entity="User" field="engineer" inversed-by="assignedBugs">
<join-column name="engineer_id" referenced-column-name="account_id" />
</many-to-one>
@ -326,7 +326,9 @@ After the field definitions the two qualified references to the user entity are
the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity`
attribute, which is enough information for the database mapper to access the foreign-table. The
`join-column` tags are used to specifiy how the foreign and referend columns are named, an information
Doctrine needs to construct joins between those two entities correctly.
Doctrine needs to construct joins between those two entities correctly. Since `reporter` and `engineer`
are on the owning side of a bi-direcitonal relation we also have to specify the `inversed-by` attribute.
They have to point to the field names on the inverse side of the relationship.
The last missing property is the `Bug::$products` collection. It holds all products where the specific
bug is occouring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many`

View File

@ -0,0 +1,208 @@
This recipe will give you a short introduction on how to design similar entities without using expensive (i.e. slow) inheritance but with not more than
* the well-known strategy pattern
* event listeners
++ Scenario / Problem
Given a Content-Management-System, we probably want to add / edit some so-called "blocks" and "panels". What are they for?
* A block might be a registration form, some text content, a table with information. A good example might also be a small calendar.
* A panel is by definition a block that can itself contain blocks. A good example for a panel might be a sidebar box: You could easily add a small calendar into it.
So, in this scenario, when building your CMS, you will surely add lots of blocks and panels to your pages and you will find yourself highly uncomfortable because of the following:
* Every existing page needs to know about the panels it contains - therefore, you'll have an association to your panels. But if you've got several types of panels - what do you do? Add an association to
every paneltype? This wouldn't be flexible. You might be tempted to add an AbstractPanelEntity and an AbstractBlockEntity that use class inheritance. Your page could then only confer to the AbstractPanelType and Doctrine 2 would do the rest for you, i.e. load the right entites. But - you'll for sure have lots of panels and blocks, and even worse, you'd have to edit the discriminator map *manually* every time you or another developer implements a new block / entity. This would tear down any effort of modular programming.
Therefore, we need something thats far more flexible.
++ Solution
The solution itself is pretty easy. We will have one base class that will be loaded via the page and that has specific behaviour - a Block class might render the frontend and even the backend, for example. Now, every block that you'll write might look different or need different data - therefore, we'll offer an API to these methods but internally, we use a strategy that exactly knows what to do.
First of all, we need to make sure that we have an interface that contains every needed action. Such actions would be rendering the frontend or the backend, solving dependencies (blocks that are supposed to be placed in the sidebar could refuse to be placed in the middle of your page, for example).
Such an interface could look like this:
[php]
/**
* This interface defines the basic actions that a block / panel needs to support.
*
* Every blockstrategy is *only* responsible for rendering a block and declaring some basic
* support, but *not* for updating its configuration etc. For this purpose, use controllers
* and models.
*
* @author Christian Heinrich
* @version $Id: BlockStrategyInterface.php 657 2010-03-16 00:08:50Z cheinrich $
*/
interface BlockStrategyInterface {
/**
* This could configure your entity
*/
public function setConfig(Config\EntityConfig $config);
/**
* Returns the config this strategy is configured with.
* @return Core\Model\Config\EntityConfig
*/
public function getConfig();
/**
* Set the view object.
*
* @param \Zend_View_Interface $view
* @return \Zend_View_Helper_Interface
*/
public function setView(\Zend_View_Interface $view);
/**
* @return \Zend_View_Interface
*/
public function getView();
/**
* Renders this strategy. This method will be called when the user
* displays the site.
*
* @return string
*/
public function renderFrontend();
/**
* Renders the backend of this block. This method will be called when
* a user tries to reconfigure this block instance.
*
* Most of the time, this method will return / output a simple form which in turn
* calls some controllers.
*
* @return string
*/
public function renderBackend();
/**
* Returns all possible types of panels this block can be stacked onto
*
* @return array
*/
public function getRequiredPanelTypes();
/**
* Determines whether a Block is able to use a given type or not
* @param string $typeName The typename
* @return boolean
*/
public function canUsePanelType($typeName);
public function setBlockEntity(AbstractBlock $block);
public function getBlockEntity();
}
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
[php]
/**
* This is the base class for both Panels and Blocks.
* It shouldn't be extended by your own blocks - simply write a strategy!
*/
abstract class AbstractBlock {
/**
* The id of the block item instance
* @var integer
*/
private $id;
// Add code for relation to the parent panel, configuration objects, ....
/**
* This var contains the classname of the strategy
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2)
*
* @var string
*/
protected $strategyClassName;
/**
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2
* @var BlockStrategyInterface
*/
protected $strategyInstance;
/**
* Returns the strategy that is used for this blockitem.
*
* The strategy itself defines how this block can be rendered etc.
*
* @return string
*/
public function getStrategyClassName() {
return $this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
* @return BlockStrategyInterface
*/
public function getStrategyInstance() {
return $this->strategyInstance;
}
/**
* Sets the strategy this block / panel should work as. Make sure that you've used
* this method before persisting the block!
*
* @param BlockStrategyInterface $strategy
*/
public function setStrategy(BlockStrategyInterface $strategy) {
$this->strategyInstance = $strategy;
$this->strategyClassName = get_class($strategy);
$strategy->setBlockEntity($this);
}
Now, the important point is that $strategyClassName is a Doctrine 2 field, i.e. Doctrine will persist this value. This is only the class name of your strategy and not an instance!
Finishing your strategy pattern, we hook into the Doctrine postLoad event and check whether a block has been loaded. If so, you will initialize it - i.e. get the strategies classname, create an instance of it and set it via setStrategyBlock().
This might look like this:
[php]
use \Doctrine\ORM,
\Doctrine\Common;
/**
* The BlockStrategyEventListener will initialize a strategy after the
* block itself was loaded.
*
* @author Christian Heinrich
*/
class BlockStrategyEventListener implements Common\EventSubscriber {
protected $view;
public function __construct(\Zend_View_Interface $view) {
$this->view = $view;
}
public function getSubscribedEvents() {
return array(ORM\Events::postLoad);
}
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
$blockItem = $args->getEntity();
// Both blocks and panels are instances of Block\AbstractBlock
if ($blockItem instanceof Block\AbstractBlock) {
$strategy = $blockItem->getStrategyClassName();
$strategyInstance = new $strategy();
if (null !== $blockItem->getConfig()) {
$strategyInstance->setConfig($blockItem->getConfig());
}
$strategyInstance->setView($this->view);
$blockItem->setStrategy($strategyInstance);
}
}
}
In this example, even some variables are set - like a view object or a specific configuration object.

View File

@ -130,6 +130,8 @@ the methods as you wish. Here is an example skeleton of such a custom type class
*/
class MyType extends Type
{
const MYTYPE = 'mytype'; // modify to match your type name
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
// return the SQL used to create your column type. To create a portable column type, use the $platform.
@ -144,6 +146,11 @@ the methods as you wish. Here is an example skeleton of such a custom type class
{
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
}
public function getName()
{
return self::MYTYPE; // modify to match your constant name
}
}
Restrictions to keep in mind:

View File

@ -120,7 +120,7 @@ The annotation driver can be configured with a factory method on the `Doctrine\O
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is requied for the annotation driver, because otherwise
The path information to the entities is required for the annotation driver, because otherwise
mass-operations on all entities through the console could not work correctly.
+++ Metadata Cache (***RECOMMENDED***)

View File

@ -143,8 +143,8 @@ Doctrine is already shipped with two implementations for the "PostConnect" event
You can register events by subscribing them to the `EventManager` instance passed to the Connection factory:
[php]
$evm = new EventManager(),
$evm->addEventSubscriber(new MysqlSessionInit('UTF-8'));
$evm = new EventManager();
$evm->addEventSubscriber(new MysqlSessionInit('UTF8'));
$conn = DriverManager::getConnection($connectionParams, null, $evm);

View File

@ -469,7 +469,7 @@ The following methods exist on the `AbstractQuery` which both `Query` and `Nativ
++++ Parameters
Prepared Statements that use numerical or named wildcards require additional parameters to be executable
agains the database. To pass parameters to the query the following methods can be used:
against the database. To pass parameters to the query the following methods can be used:
* `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value.
* `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs.
@ -516,7 +516,7 @@ are to be used in userland:
* Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query
hint can be used to handle memory consumption problems with large result-sets that contain char or binary data.
Doctrine has no way of implicitly reloaded this data. Partially loaded objects have to be passed to
Doctrine has no way of implicitly reloading this data. Partially loaded objects have to be passed to
`EntityManager::refresh()` if they are to be reloaded fully from the database.
* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell.
If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the

View File

@ -146,7 +146,7 @@ The result would look like this:
Note that this would be a partial object if the entity has more fields than just id and name. In the example above the column and field names are identical but that is not necessary, of course. Also note that the query string passed to createNativeQuery is **real native SQL**. Doctrine does not touch this SQL in any way.
In the previous basic example, a User had no relations and the table the class is mapped to owns no foreign keys.
The next an example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress,
The next example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress,
where the User is the owning side and thus owns the foreign key.
[php]

View File

@ -72,6 +72,56 @@ This method is the responsable to build every piece of DQL. It takes 3 parameter
->add('where', 'u.id = ?1')
->add('orderBy', 'u.name ASC');
++++ Binding parameters to your query
Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Binding parameters can simply be achieved as follows:
[php]
// $qb instanceof QueryBuilder
// example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support
$qb->add('select', 'u')
->add('from', 'User u')
->add('where', 'u.id = ?1')
->add('orderBy', 'u.name ASC');
->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100
You are not forced to enumerate your placeholders as the alternative syntax is available:
[php]
// $qb instanceof QueryBuilder
// example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support
$qb->add('select', 'u')
->add('from', 'User u')
->add('where', 'u.id = :identifier')
->add('orderBy', 'u.name ASC');
->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100
Note that numeric placeholders start with a ? followed by a number while the named placeholders start with a : followed by a string.
If you've got several parameters to bind to your query, you can also use setParameters() instead of setParameter() with the following syntax:
[php]
// $qb instanceof QueryBuilder
// Query here...
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2', 'whatever' => 'your value for :whatever'));
Getting already bound parameters is easy - simply use the abovementioned syntax with "getParameter()" or "getParameters()":
[php]
// $qb instanceof QueryBuilder
// See example above
$params = qb->getParameters(array(1, 2, 'whatever'));
// Equivalent to
$param = array($qb->getParameter(1), $qb->getParameter(2), $qb->getParameter('whatever'));
Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL.
++++ Expr\* classes
When you call `add()` with string, it internally evaluates to an instance of `Doctrine\ORM\Query\Expr\Expr\*` class.
@ -197,6 +247,8 @@ Here it is a complete list of supported helper methods available:
public function not($restriction); // Returns Expr\Func instance
// Example - $qb->expr()->in('u.id', array(1, 2, 3))
// Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception.
// Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above)
public function in($x, $y); // Returns Expr\Func instance
// Example - $qb->expr()->notIn('u.id', '2')
@ -353,4 +405,4 @@ Here is a complete list of helper methods in `QueryBuilder`:
// Example - $qb->addOrderBy('u.firstName')
public function addOrderBy($sort, $order = null); // Default $order = 'ASC'
}
}