Working with Indexed Associations ================================= .. 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 Associations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can map indexed associations 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:: yaml 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 association 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:: yaml 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->getStock($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->getStock($symbol); echo $stock->getSymbol(); // will print "AAPL" If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally indexed associations also work with the ``Collection::slice()`` functionality, no matter if marked as LAZY or EXTRA_LAZY. 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 implemented for One-To-Many associations, because they are never the owning side.