From 0f974c562c26eb4ce2930b015a7be30fa8668f09 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 5 Feb 2011 13:43:45 +0100 Subject: [PATCH] Add Working with Indexed Associations Tutorial --- en/index.rst | 1 + en/reference/query-builder.rst | 2 +- .../working-with-indexed-assocations.rst | 294 ++++++++++++++++++ 3 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 en/tutorials/working-with-indexed-assocations.rst diff --git a/en/index.rst b/en/index.rst index 59c227056..3aca1abca 100644 --- a/en/index.rst +++ b/en/index.rst @@ -42,6 +42,7 @@ Tutorials :maxdepth: 1 tutorials/getting-started-xml-edition + tutorials/working-with-indexed-associations Cookbook -------- diff --git a/en/reference/query-builder.rst b/en/reference/query-builder.rst index 8cf459b15..da5bbf2cd 100644 --- a/en/reference/query-builder.rst +++ b/en/reference/query-builder.rst @@ -276,7 +276,7 @@ complete list of supported helper methods available: public function from($from, $alias); // Returns Expr\From instance // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55'); - // Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); + // Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55')); public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance // Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100'); diff --git a/en/tutorials/working-with-indexed-assocations.rst b/en/tutorials/working-with-indexed-assocations.rst new file mode 100644 index 000000000..5e82617d9 --- /dev/null +++ b/en/tutorials/working-with-indexed-assocations.rst @@ -0,0 +1,294 @@ +Working with Indexed Assocations +================================ + +.. note: + + This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series. + +Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in +the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY`` +was used. Starting with Doctrine 2.1 you can index your collections by a value in the related entity. +This is a first step towards full ordered hashmap support through the Doctrine ORM. +The feature works like an implicit ``INDEX BY`` for the selected association but has several +downsides also: + +- You have to manage both the key and field if you want to change the index by field value. +- On each request the keys are regenerated from the field value not from the previous collection key. +- Values of the Index-By keys are never considered during persistence, it only exists for accessing purposes. +- Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities + with the same index-by field value is undefined. + +As an example we will design a simple stock exchange list view. The domain consists of the entity ``Stock`` +and ``Market`` where each Stock has a symbol and is traded on a single market. Instead of having a numerical +list of stocks traded on a market they will be indexed by their symbol, which is unique across all markets. + +Mapping Indexed Assocations +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can map indexed assocations by adding: + + * ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation. + * ``index-by`` attribute to any ```` or ```` xml element. + * ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files. + +The code and mappings for the Market entity looks like this: + +.. configuration-block:: + .. code-block:: php + + name = $name; + $this->stocks = new ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + public function getName() + { + return $this->name; + } + + public function addStock(Stock $stock) + { + $this->stocks[$stock->getSymbol()] = $stock; + } + + public function getStock($symbol) + { + if (!isset($this->stocks[$symbol])) { + throw new \InvalidArgumentException("Symbol is not traded on this market."); + } + + return $this->stocks[$symbol]; + } + + public function getStocks() + { + return $this->stocks->toArray(); + } + } + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: yml + + Doctrine\Tests\Models\StockExchange\Market: + type: entity + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type:string + oneToMany: + stocks: + targetEntity: Stock + mappedBy: market + indexBy: symbol + +Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol, +so that we can work with the indexed assocation directly after invoking ``addStock()``. Inside ``getStock($symbol)`` +we pick a stock traded on the particular market by symbol. If this stock doesn't exist an exception is thrown. + +The ``Stock`` entity doesn't contain any special instructions that are new, but for completeness +here are the code and mappings for it: + +.. configuration-block:: + .. code-block:: php + + symbol = $symbol; + $this->market = $market; + $market->addStock($this); + } + + public function getSymbol() + { + return $this->symbol; + } + } + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: yml + + Doctrine\Tests\Models\StockExchange\Stock: + type: entity + id: + id: + type: integer + generator: + strategy: AUTO + fields: + symbol: + type: string + manyToOne: + market: + targetEntity: Market + inversedBy: stocks + +Querying indexed associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Now that we defined the stocks collection to be indexed by symbol we can take a look at some code, +that makes use of the indexing. + +First we will populate our database with two example stocks traded on a single market: + +.. code-block:: php + + persist($market); + $em->persist($stock1); + $em->persist($stock2); + $em->flush(); + +This code is not particular interesting since the indexing feature is not yet used. In a new request we could +now query for the market: + +.. code-block:: php + + find("Doctrine\Tests\Models\StockExchange\Market", $marketId); + + // Access the stocks by symbol now: + $stock = $market->getSymbol($symbol); + + echo $stock->getSymbol(); // will print "AAPL" + +The implementation ``Market::addStock()`` in combination with ``indexBy`` allows to access the collection +consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not. + +The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association. + +.. code-block:: php + + createQuery($dql) + ->setParameter(1, $marketId) + ->getSingleResult(); + + // Access the stocks by symbol now: + $stock = $market->getSymbol($symbol); + + echo $stock->getSymbol(); // will print "AAPL" + +Outlook into the Future +~~~~~~~~~~~~~~~~~~~~~~~ + +For the inverse side of a many-to-many associations there will be a way to persist the keys and the order +as a third and fourth parameter into the join table. This feature is discussed in `DDC-213`_ +This feature cannot be implemeted for One-To-Many associations, because they are never the owning side. +