Doctrine helpers and extensions for Nette Framework



v4.7.2 2017-03-31 16:45 UTC


Provides some helpers and extension for Doctrine 2 ORM into Nette Framework.


First install package via Composer:

$ composer require etten/doctrine

Then register basic DI Extension in config.neon:

	etten.doctrine: Etten\Doctrine\DI\DIExtension

EntityManager Facades

Doctrine's default EntityManager is a GOD class. Monster. It does too much, violates SRP, mocks are difficilt, ...

But you can use:

  • Etten\Doctrine\Facade (abstract class)
  • Etten\Doctrine\Persister
  • Etten\Doctrine\RepositoryLocator
  • Etten\Doctrine\Transaction

Services are registered automatically with Etten\Doctrine\DI\DIExtension.



When you have i.e. Product:Tags (M:N) associations and set-up all values via setter Product::setTags, you'll probably get a Duplicate entry... error. Because you set-up new associations (replace Product's Tags Collection instance).

But this kind of operation can be very easy with Etten\Doctrine\Helpers\Collections.

Just use:

	public function setTags(array $tags):Product
		\Etten\Doctrine\Helpers\Collections::replace($this->tags, $tags);
		return $this;

The $this->tags instance is preserved, only new items are added and removed items removed from Collection.


Sometimes, you need sort items by order given by another array.

In MySQL, you can use ORDER BY FIELD.

But when we use Doctrine, our code should be platform independent and we shouldn't use DBMS-specific functions directly.

You can sort items in PHP via Etten\Doctrine\Helpers\FieldOrderHelper. See implementation.

Note: This kind of operations in PHP is inefficient. Use it only for few filtered items. If you have hundreds of items, rather rewrite the code and sort items directly in DBMS, not in PHP.


Randomizer helps you find random-look results without ORDER BY RAND() clause.

It counts all items in given Query object and selects random offset.

Then applies limit and returns items as a shuffled array result.

For better results, offset + limit is performed in multiple iterations (by default for each 25 items). It increases a cost of the operation, but results are not in order as-inserted into database.



Etten/Doctrine provides comfortable cache invalidation when a specific entity is changed (in term of Doctrine: persisted, updated, removed).

Entity must implements Etten\Doctrine\Entities\Cacheable, see interface.

Basic implementation of Cacheable you'll get with Etten\Doctrine\Entities\Attributes\Cache, see trait.

You can also inherit from Etten\Doctrine\Entities\Entity (implementation) and you'll get both of these requirements.

Finally, you must register a Nette DI extension:

# app/config.neon

	etten.doctrine.cache: Etten\Doctrine\DI\CacheExtension

When the extension is registered, cache is automatically invalidated. How? It depends on concrete implementations. For default, see Etten\Doctrine\Entities\Attributes\Cache and Etten\Doctrine\Caching\NetteCacheInvalidator.


If you need item's ID before persist and flush, you can use UUID as a primary index.

Doctrine 2 has native support of UUID (GUID) but it's auto-generated value by RDMS. And we don't know the ID before persist and flush.

But you can generate UUID before persist and flush, in PHP.

For more information see ramsey/uuid-doctrine and Percona blog.

In etten/doctrine, UUID types are used a bit differently:

  • UuidBinary has really a binary attribute (PHP string, SQL binary), not Uuid instance. But you can get hexdec value when you need it (i.e. select in a form). If you need insert UUID manually in a raw SQL, use something like UNHEX(REPLACE(UUID(), '-', '')).
  • Uuid has really a string attribute (both PHP + SQL), not Uuid instance. See UuidBinary above.
  • UuidBinary uses InnoDB optimized variant by default.
  • When you need simple conversion of types, you can use UuidConverter.

In a Nette Framework application, simple add UUID support by registering an extension:

# app/config.neon

	etten.doctrine.uuid: Etten\Doctrine\DI\UuidExtension


If you need item's ID before persist and flush, you can use InstanceId as a primary index.

It's globally unique integer generated by PHP, not RDMS.

You can add the InstanceId support by registering a Nette DI extension:

# app/config.neon

	etten.doctrine.instanceId: Etten\Doctrine\DI\InstanceIdExtension

	path: safe://%storageDir%/



The original source of the code is here.

Register a DQL in config:

# app/config.neon

			MATCH: Etten\Doctrine\DQL\MatchAgainstFunction



public function search(string $q)
	$qb = $this->createQueryBuilder()
		->addSelect('MATCH (a.title) AGAINST (:search) as HIDDEN score')
		->addWhere('MATCH (a.title) AGAINST (:search) > 1')
		->setParameter('search', $q)
		->orderBy('score', 'desc');

	return new Paginator($qb);


The original source of the code is here.

Register a DQL in config:

# app/config.neon

			MATCH: Etten\Doctrine\DQL\FieldFunction



public function search(array $ids)
	$qb = $this->createQueryBuilder()
		->addSelect('FIELD(, :ids) as HIDDEN score')
		->andWhere(' IN :ids')
		->setParameter('ids', $ids)
		->orderBy('score', 'desc');

	return new Paginator($qb);

Others (not included in this package)

For more functions plese visit beberlei/DoctrineExtensions.