Orm & Dbal 4.0

Today we are releasing v4.0 for both Orm & Dbal. The future is now!

Orm 4.0

Take a look at release notes. Also, read an upgrade guide:

Dbal 4.0

  • added support for Symfony (Bundle + DataCollector),
  • reworked db reflection and added proper multi schema support,
  • introduced new Logger for observing Connection events,
  • added SSL support to MysqliDriver,
  • allow specifying INDEX HINTS for MySQL in QueryBuilder,
  • and many more small fixed & tuning; see closed issues.

Take a look at release notes.

Orm-PHPStan 0.6

We know that to adopt a library it is necessary to provide additional tooling. So we are releasing PHPStan extension with support for latest Orm & PHPStan version. See release notes.

Orm-PhpStorm 0.7

Another tooling is an extension for PhpStorm. We are releasing new version with extensive support for Nextras Orm 4.0. Check release notes 0.7 and 0.7.1 and download from Plugins portal.

You may support further development by:

  • reporting bugs & feature requests,
  • implementing new functionality (sending PR),
  • proofreading & enhancing documentation,
  • writing an article how you use Orm and how it helps you,
  • giving a GitHub star ⭐,
  • sponsoring me.

Thanks @stpnkcrk, @VaclavPavek and @chemix for sponsorship.

Dbal 4: introducing Symfony Bundle

Dbal was from the beginning independent on any framework. Since version 1.0 until now it had not required any external dependency. Of course, you should have had the basic PHP extensions present for accessing the database, but no other dependency is needed.

From the start Dbal was providing a Nette DI extension that eased the integration. And it wasn’t just a DIC registration of few services, Dbal also provided Tracy panel and Bluescreen integration.

Tracy with Nextras Dbal’s panel

Today I am proud that I can announce Symfony support via Bundle; available directly in Nextras Dbal. Simply add NextrasDbalBundle and you app will get auto DI’ registration of Connection service and profiler toolbar support.

// config/bundles.php

return [
    // ...
    Nextras\Dbal\Bridges\SymfonyBundle\NextrasDbalBundle::class => ['all' => true],
# config/packages/nextras_dbal.yaml
  driver: mysqli
  username: root
  password: root
  database: your_db

Suddenly, everything is ready, all you need to obtain Nextras\Dbal\IConnection instance. The instance is available under the interface name and identifier with connection name: nextras_dbal.default.connection.

Symfony toolbar with Nextras Dbal’s tab
Symfony profiler with Nextras Dbal’s section

As you may see from the screenshots, the functionality in both integrations is the same. But feel free to send some enhancements.

Orm 4: optimized relationship

Orm 4.0 comes with performance changes for relationships. Let’s see what has been enhanced.

You could already filter a relationship to minimize the number of fetched entities (by filtering or limiting). Let’s say we want to get the 10 latest books that publisher has published.

$books = $publisher->books->toCollection()
    ->orderBy('publishedAt', ICollection::DESC)

foreach ($books as $book) { /* ... */ }

The code correctly fetches only the needed books and iterates over them.

But, what about the situation when developer creates another book associated with the publisher? Will this code work?

$newBook = new Book();
$newBook->publishedAt = new \DateTimeImmutable();
// + other initialization


$books = $publisher->books->toCollection()
    ->orderBy('publishedAt', ICollection::DESC)

foreach ($books as $book) { /* ... */ }

Yes! It will, however until Orm 4.0 it will behave in quite non-optimized way.

In Orm <4.0 the code will

  • fetch all persisted books,
  • add the new book to the collection,
  • process the collection in-memory (e.g. it may process hundreds of entities),
  • return the correct result.

This is changing in Orm 4.0, which will do this the smart(er) way. In Orm 4.0+ the code will:

  • fetch all persisted books which match the conditions
    (e.g. only the 10 latest books),
  • add the new book to the collection,
  • process the collection in-memory (e.g. it will process only the 11 latest books and take the first 10),
  • return the correct result.

From the description you may see that we will do the processing twice (in storage and in-memory), but the number of processed entities should be significantly lower and the memory benefits should be huge.

The earlier behavior could be quite problematic since some your code wouldn’t expected that they work with unpersisted relationships and therefore working with the collection inefficiently.

Thanks @DavidMatejka for coming up with the idea and proof-of-concept implementation.

Orm 4: NULLs ordering

Orm 4.0 will arrive later this quarter and will bring handful of small features. One of them is explicit NULLs ordering.

The ordering of NULLs is a topic with a “random” default points of view. It depends if you ask a PHP developer, MySQL developer or even PostgreSQL developer. The each platform, language, SQL server choose how to behave when one operand is a NULL and it seems almost everyone differ in the opinion on it.

Let’s see the following table with default behavior when NULL appears.


What’s more, Oracle behaves the same as PostgreSQL.

This differences in default behavior led us to decision remain default ordering behavior as “undefined” and provide new ordering constants which will unify NULLs ordering for ArrayCollection & DbalCollection regardless SQL engine.

We are introducing new ordering constants:

  • ICollection::ASC_NULLS_FIRST
  • ICollection::ASC_NULLS_LAST
  • ICollection::DESC_NULLS_FIRST
  • ICollection::DESC_NULLS_LAST
// sort books by their publish date and put unpublished books first
$books = $books->orderBy('publishedAt', ICollection::DESC_NULLS_FIRST);

These new constants provides unified & safe experience when needed. As usual, you may combine them with other ordering expressions when two entities are considered the same.

Only with your support we can make Orm the best Orm ever. Test it by requiring "nextras/orm": "4.0.x-dev". Comment or open an issue on GitHub. Thank you.

Orm 4: Aggregations

During the 2 last years I was training Nextras Orm few times and immediately realized that my take on collection extensions (aka custom collection functions) is not easy to explain, understand and use.

Collection functions should brought a way to implement advanced filtering & ordering, such as an aggregation filtering (“select all authors who have written more than 2 books”). Such filtering was missing and I was commonly asked if there is somewhere (even in an external repository) a package, which would implement basic count/sum/min/max aggregation filtering through the collection functions. But, quite expected, nobody done it and published it.

In the end, three weeks back I needed such filtering myself and I was lazy to switch to the mapper layer. I’ve tried to implement such aggregate function with the current collection function interface, but I failed to do it like I wanted and quickly realized it’s not so easy to write a generic version of aggregate function (both for filtering or ordering).

New collection functions

To easy the pain Orm 4 introduces new collection functions interfaces. (And removes the old ones, sorry.) The overall API is reduced and simplified; now if you want to create a collection function, just implement the specific interface for specific storage – namely Nextras/Orm/Collection/Functions/IQueryBuilderFunction for Dbal’s support and Nextras/Orm/Collection/Functions/IArrayFunction for non-persisted (array) collection support.

Newly, collection functions just return an expression (Dbal) or expression’s value (Array). This allows to combine them with other operators easily. The expression is enough for ordering. Filtering will require an additional comparison operator.

The second enhancement is just for Dbal – previously you could return just an Dbal expression for WHERE clause, but aggregation filtering requires putting these filtering conditions into HAVING clause. So we introduced a DbalExpressionResult object that wraps the information (WHERE/HAVING clause) and also provides new simplified interface to work with passed expression, which may be just a simple property name, property expression (needing an auto-join), or even other collection function’s result.

Aggregation functions

The described refactoring allowed a simple implementation of aggregate functions. We bring those aggregation functions into Orm distribution directly. Let me introduce:

  • CountAggregateFunction
  • SumAggregateFunction
  • AvgAggregateFunction
  • MinAggregateFunction
  • MaxAggregateFunction

All those functions are implemented both for Dbal and Array collections and are registered in repository as commonly provided functions.

The rules for using collection functions stayed the same. First, pass the function name and then its arguments – all the aggregation functions take only one argument – an expression that should be aggregated. Let’s see an example:

use Nextras\Orm\Collection\Functions\CountAggregateFunction;

    [CountAggregateFunction::class, 'books->id']

In the example we sort the collection of authors by the count of their books, e.g. authors with the least books will be at the beginning. The example allows the same “property expression” you may use for filtering. This is new for orderBy() method. Also, you can reverse the ordering:

use Nextras\Orm\Collection\Functions\CountAggregateFunction;
use Nextras\Orm\Collection\ICollection;

    [CountAggregateFunction::class, 'books->id'],

We can see the expression syntax is very light and simple. Let’s filter the collection by authors who have written more than 2 books. Using CountAggregationFunction itself won’t be enough. We need to compare its result with the wanted number, 2 this time. To do that use built-in CompareFunction. This function takes a property expression on the left, a comparison operator, and a value to compare.

use Nextras\Orm\Collection\Functions\CompareFunction;
use Nextras\Orm\Collection\Functions\CountAggregateFunction;

        [CountAggregateFunction::class, 'books->id'],

As you can see, you can nest these function calls together. This approach is very powerful and flexible, though, sometimes quite verbose. To ease this issue you may create own wrappers (not included in Orm!).

class Aggregate {
    public static function count(string $expression): array {
        return [CountAggregateFunction::class, $expression];
class Compare {
    public static function gt(string $expression, $value): array {
        return [

// filters authors who have more than 2 books 
// and sorts them by the number of their books descending
    ->findBy(Compare::gt(Aggregate::count('books->id'), 2))
    ->orderBy(Aggregate::count('books->id'), ICollection::DESC);

The time will show if such functions and helpers are a good approach, for Orm 4.0 you have to create them by yourself.

This is a fresh feature and I’d like to ask you to test it and give us feedback. Only with your support we can make Orm the best Orm ever. Test it by requiring "nextras/orm": "4.0.x-dev". Comment or open an issue on GitHub. Thank you.

Orm 3.1 – Property containers

Today we are releasing Nextras Orm 3.1 – a release without much features, but with plenty small fixes and enhancements. Let’s examine the major enhancement of property containers. Also, see full release notes.

Property containers

Until now, Orm property containers were quite limited to pretty simple functionality, such as converting encapsulating object as a JSON. (You may have read an article about implementing such container.) Some advanced transformations to class-backed enums were possible, but limited just to the entity interface. The conversion may have failed you just minute later. ICollection required non-converted values for Dbal queries and converted values for in-memory queries. In 3.1 this is fixed. Property containers may define the reverse deserialize function, which will be used in ICollection for proper comparison/query building. Let’s see an example:

We define an enum with marc-mabe/php-enum:

class GeometryType extends MabeEnum\Enum 
    const PLACE = 1;
    const CITY = 2;
    const COUNTRY = 3;
    const CONTINENT = 4;

Then we define generic reusable enum property container for enums. To this, we inherit from abstract helper class ImmutableValuePropertyContainer that already implements a lot of IPropertyContainer interface.

The definition of EnumContainer is reusable, e.g. you may use it multiple times in different properties with different enum classes. Of your you may write just one-time-purpose property containers.

use MabeEnum\Enum;
use Nextras\Orm\Entity\ImmutableValuePropertyContainer;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;

class EnumContainer extends ImmutableValuePropertyContainer
    /** @var string */
    private $enumClass;

    public function __construct(PropertyMetadata $propertyMetadata)
        // check the property has one valid type
        assert(count($propertyMetadata->types) === 1);
        $this->enumClass = key($propertyMetadata->types);
        // check the enum class exists

    public function convertToRawValue($value)
        assert($value instanceof Enum);
        return $value->getValue();

    public function convertFromRawValue($value)
        $enumClass = $this->enumClass;
        return $enumClass::byValue($value);

By default ImmutableValuePropertyContainer allows null values, if you want the null to be part of the enum, you have to override setRawValue, getRawValue respectively.

    public function setRawValue($value)
        $this->value = $this->convertFromRawValue($value);

    public function getRawValue()
        return $this->convertToRawValue($this->value);

The usage then is pretty simple:

 * @property int|null          $id   {primary}
 * @property GeometryType|null $type {container EnumContainer}
final class Geometry extends Entity

$geometry = new Geometry();
$geometry->type = GeometryType::PLACE();

Filtering requires using proper enum instances:

    'type' => GeometryType::CONTINENT()

Property containers may be uses to other data encapsulation use-cases. This enum examples nicely adds type-safety to your code without much effort.

JSON in Nextras Orm 3.0

Storing an array/std-like object structure as json structure in one db column is pretty common use-case. However, the correct approach in Nextras Orm may not be obvious.

🔝 Use property container 😉

First, create your JSON container, by implementing abstract methods of predefined helper class ImmutableValuePropertyContainer.

use Nette\Utils\Json;
use Nextras\Orm\Entity\ImmutableValuePropertyContainer;

class JsonContainer extends ImmutableValuePropertyContainer
    protected function serialize($value)
        return Json::encode($value); // or simple json_encode()

    protected function deserialize($value)
        return Json::decode($value); // or simple json_decode()

Then, simply define your entity property with container:

 * ...
 * @property Nette\Utils\ArrayHash $data {container JsonContainer}
class Users extends Nextras\Orm\Entity\Entity {}

Please note that ImmutableValuePropertyContainer API may lightly change in the future minor versions.

🆗 The second possible way is to write custom mapping in StorageReflection – add converter callbacks for your specific property.

🙈 The last possibility is to transform value in setters, getters and entity event callbacks. Please do not do this.

Nextras Orm 3.0

It’s here. It took more time than I have expected. Nextras Orm 3.0 comes with few major features, huge internal refactoring and many small fixes.

Check the release notes on GitHub and upgrade guide.

Collection Custom Functions & OR

Collection custom functions are a powerful enhancement of repository layer. Now you may do more advanced filtering & sorting of ICollection. We also added support for disjunction – OR filtering. As you might have guessed, it is internally also implemented as custom functions.

Custom functions hold the high level abstraction when being applied, though, they internally allow implementing low level behavior specific for the array and SQL storage.

// filter collection by entities with name Jan or age 10
     ['name' => 'Jan'], 
     ['age' => 10], 

Custom functions are quite powerful because the can be nested!

// filter collection by entities where name starts with Jan or age is 10
// note thet LikeFunction is not included in Orm
     [LikeFunction::class, 'name', 'Jan'], 
     ['age' => 10], 


We have enhanced also the general model. Now it comes with two important functions:

  • clear() – flushes all the caches & references to entities;
  • refreshAll() – refreshes all data directly from the storage;


We have dropped support for \DateTime – sorry for that. On the other hand, we now support much better \DateTimeImmutable type.

PHP 7.0 scalar types & PHP 7.2

This release also adds scalar types where is it possible. We currently require PHP 7.0, therefore we do not use nullable types.

Also we have fixed support for PHP 7.2.

MSSQL support

We do not expect many of you will use Microsoft’s SQL server, though, this is quite nice feature and hopefully someone will be happy about that. Some of edge cases are not fully supported (e.g. there is an internal workaround). Please test it and report bugs 🙂


I’d like to thank all the contributor and users. You support, question and Orm usage drives me in the further development. Namely I want to thank to Jan Tvrdik and David Matejka for the help with the development and valuable feedback and review. Thank to you guys.


We have partnered with GeekyEdu and as a result there is the first Nextras Orm training ever in Prague. 🙂

The progress of Orm 3.0

Hi there! It’s been a while I have published some info about the upcoming release of Nextras Orm 3.0. So, what’s the current schedule?

Nextras Orm 3.0 will be released in 2017!

So, few notes to the current release plan:

  1. I apologize for such delay. The plans were quite extreme. I was not able to fulfill them. Though, many new great features will come, specifically: OR operator and custom functions.
  2. Some feature will be postponed: The most difficult part of these feature is the design. I would have a time to code them, but I had not enough time to design the API, to think it through, to validate the ideas. Also, this is a great oportunity for you to contribute: take a look and think how should the storage reflection factory look, how it should behave.
  3. Currently, only some minor features are missing. Personally, I have deployed current master branch to production of my quite big project. So I have already validated the stability and I expect quite a short RC phase.

The major features of Orm 3.0 will be:

  • OR operator: add a disjunction operator on collection: $collection->findBy(ICollection::OR, [...], [...]);
  • custom functions: create own filtering function to filter data over database and array collection;
  • IModel::refreshAll(): you may refresh the whole entity cache;
  • IModel::clear(): safely clear entity cache;
  • enforced DateTimeImmutable and full support for it;
  • fixed relationship caching, login, traversing;
  • PHP 7.0 type hints;
  • MS SQL Server support;

Orm will be released also with Dbal 3.0. Dbal will bring these features:

  • IConnection interface;
  • enhanced Tracy panel;
  • %json modifier;
  • nested transactions;
  • transaction isolation level;
  • MS SQL Server support;