Skip to content

Latest commit

 

History

History

.docs

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Contributte Doctrine ORM

Integration of Doctrine ORM for Nette Framework.

Content

Installation

Install package using composer.

composer require nettrine/orm

Register prepared compiler extension in your config.neon file.

extensions:
  nettrine.orm: Nettrine\ORM\DI\OrmExtension

Note

This is just ORM, for DBAL please use nettrine/dbal.

Configuration

Minimal configuration

nettrine.orm:
  managers:
    default:
      connection: default
      mapping:
        App:
          directories: [%appDir%/Database]
          namespace: App/Database

Advanced configuration

Here is the list of all available options with their types.

nettrine.orm:
 managers:
   <name>:
     connection: <string>
     entityManagerDecoratorClass: <class>
     configurationClass: <class>

     proxyDir: <path>
     autoGenerateProxyClasses: <boolean>
     proxyNamespace: <string>
     metadataDriverImpl: <service>
     entityNamespaces: <mixed[]>
     customStringFunctions: <mixed[]>
     customNumericFunctions: <mixed[]>
     customDatetimeFunctions: <mixed[]>
     customHydrationModes: <string[]>
     classMetadataFactoryName: <string>
     defaultRepositoryClassName: <string>
     namingStrategy: <class-string>
     quoteStrategy: <class-string>
     entityListenerResolver: <class-string>
     repositoryFactory: <class-string>
     defaultQueryHints: <mixed[]>
     filters:
       <name>:
         class: <string>
         enabled: <boolean>

     mapping:
       <name>:
         type: <attributes|xml>
         directories: <string[]>
         namespace: <string>

     defaultCache: <class-string|service>
     queryCache: <class-string|service>
     resultCache: <class-string|service>
     hydrationCache: <class-string|service>
     metadataCache: <class-string|service>

     secondLevelCache:
       enable: <boolean>
       cache: <class-string|service>
       logger: <class-string|service>
       regions:
         <name>:
           lifetime: <int>
           lockLifetime: <int>

For example:

# See more in nettrine/dbal
nettrine.dbal:
  debug:
    panel: %debugMode%

  connections:
    default:
      driver: pdo_pgsql
      host: localhost
      port: 5432
      user: root
      password: root
      dbname: nettrine

nettrine.orm:
  managers:
    default:
      connection: default
      mapping:
        App:
          directories: [%appDir%/Database]
          namespace: App\Database

Tip

Take a look at real Nettrine ORM configuration example at contributte/doctrine-project.

Auto configuration

By default, this extension will try to autoconfigure itself.

  • proxyDir: %tempDir%/proxies, if %tempDir% is not defined,, you have to define it manually.
  • autoGenerateProxyClasses: %debugMode%, if %debugMode% is not defined, you have to define it manually.
    • 0 means that the proxy classes must be generated manually.
    • 1 means that the proxy classes are generated automatically.
    • 2 means that the proxy classes are generated automatically when the proxy file does not exist.
    • 3 means that the proxy classes are generated automatically using eval() (useful for debugging).
    • 4 means that the proxy classes are generated automatically when the proxy file does not exist or when the proxied file changed.

EntityManager

EntityManager is a central access point to ORM functionality. It is a wrapper around ObjectManager and holds the metadata and configuration of the ORM.

EntityManagerDecorator

You can use entityManagerDecoratorClass to decorate EntityManager.

nettrine.orm:
  managers:
    default:
      connection: default
      entityManagerDecoratorClass: App\MyEntityManagerDecorator

Close & Reset

If you hit The EntityManager is closed. exception, you can use reset method to reopen it.

$managerRegistry = $container->getByType(Doctrine\Persistence\ManagerRegistry::class);
$managerRegistry->resetManager(); // default
$managerRegistry->resetManager('second');

Warning

Resetting the manager is a dangerous operation. It is also black magic, because you cannot just create a new EntityManager instance, you have to reset the current one using internal methods (reflection & binding). Class responsible for this operation is Nettrine\ORM\ManagerRegistry.

Caching

Tip

Take a look at more information in official Doctrine documentation:

A Doctrine ORM can automatically cache query results and metadata. The feature is optional though, and by default, no cache is configured. You can enable the result cache by setting the defaultCache configuration option to an instance of a cache driver or metadataCache, queryCache, resultCache, hydrationCache separately.

Warning

Cache adapter must implement Psr\Cache\CacheItemPoolInterface interface. Use any PSR-6 + PSR-16 compatible cache library like symfony/cache or nette/caching.

In the simplest case, you can define only defaultCache for all caches.

nettrine.orm:
  managers:
    default:
      # Create cache manually
      defaultCache: App\CacheService(%tempDir%/cache/orm)

      # Use registered cache service
      defaultCache: @cacheService

Or you can define each cache separately.

nettrine.orm:
  managers:
    default:
      queryCache: App\CacheService(%tempDir%/cache/orm-query)
      resultCache: App\CacheService(%tempDir%/cache/orm-result)
      hydrationCache: App\CacheService(%tempDir%/cache/orm-hydration)
      metadataCache: App\CacheService(%tempDir%/cache/orm-metadata)

Second level cache is a bit different. Be sure you know what you are doing, lear more in official Doctrine documentation.

nettrine.orm:
  managers:
    default:
        secondLevelCache:
          enable: true
          cache: App\CacheService(%tempDir%/cache/orm-slc)
          logger: App\LoggerService()
          regions:
            region1:
              lifetime: 3600
              lockLifetime: 60
            region2:
              lifetime: 86000
              lockLifetime: 60

If you like symfony/cache you can use it as well.

nettrine.orm:
    managers:
      default:
        # Use default cache
        defaultCache: Symfony\Component\Cache\Adapter\FilesystemAdapter(namespace: doctrine-orm, defaultLifetime: 0, directory: %tempDir%/cache/orm)

        # Or use separate caches
        queryCache: Symfony\Component\Cache\Adapter\FilesystemAdapter(namespace: doctrine-orm-query, defaultLifetime: 0, directory: %tempDir%/cache/orm-query)
        resultCache: Symfony\Component\Cache\Adapter\FilesystemAdapter(namespace: doctrine-orm-result, defaultLifetime: 0, directory: %tempDir%/cache/orm-result)
        hydrationCache: Symfony\Component\Cache\Adapter\FilesystemAdapter(namespace: doctrine-orm-hydration, defaultLifetime: 0, directory: %tempDir%/cache/orm-hydration)
        metadataCache: Symfony\Component\Cache\Adapter\FilesystemAdapter(namespace: doctrine-orm-metadata, defaultLifetime: 0, directory: %tempDir%/cache/orm-metadata)

If you like nette/caching you can use it as well. Be aware that nette/caching is not PSR-6 + PSR-16 compatible, you need contributte/psr16-caching.

nettrine.orm:
    managers:
      default:
        defaultCache: Contributte\Psr6\CachePool(
          Nette\Caching\Cache(
            Nette\Caching\Storages\FileStorage(%tempDir%/cache)
            doctrine/dbal
          )
        )

Important

You should always use cache for production environment. It can significantly improve performance of your application. Pick the right cache adapter for your needs. For example from symfony/cache:

  • FilesystemAdapter - if you want to cache data on disk
  • ArrayAdapter - if you want to cache data in memory
  • ApcuAdapter - if you want to cache data in memory and share it between requests
  • RedisAdapter - if you want to cache data in memory and share it between requests and servers
  • ChainAdapter - if you want to cache data in multiple storages

Mapping

There are several ways how to map entities to Doctrine ORM. This library supports attributes and xml out of the box.

Attributes

Since PHP 8.0, we can use #[attributes] for entity mapping.

<?php declare(strict_types=1);

namespace App\Database;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'customer')]
class Customer
{

    #[ORM\Column(length: 32, unique: true, nullable: false)]
    protected string $username;

    #[ORM\Column(columnDefinition: 'CHAR(2) NOT NULL')]
    protected string $country;

}

Configuration for attribute mapping looks like this:

nettrine.orm:
  managers:
    default:
      connection: default
      mapping:
        App:
          directories: [%appDir%/Database]
          namespace: App\Database

XML

The XML mapping driver enables you to provide the ORM metadata in form of XML documents. It requires the dom extension in order to be able to validate your mapping documents against its XML Schema.

Tip

Take a look at more information in official Doctrine documentation:

<doctrine-mapping
  xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
  xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
    http://doctrine-project.org/schemas/orm/doctrine-mapping
    https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd
">

  ...

</doctrine-mapping>

Configuration for XML mapping looks like this:

nettrine.orm:
  managers:
    default:
      connection: default
      mapping:
        App:
          type: xml
          directories: [%appDir%/Database]
          namespace: App\Database

Helper

You can use MappingHelper to add multiple mappings at once. This is useful when you have multiple modules with entities. Create your own compiler extension and use MappingHelper to add mappings.

It's a good practice if you have separated modules in your applications.

<?php declare(strict_types=1);

namespace App\Model\DI;

use Nette\DI\CompilerExtension;use Nettrine\ORM\DI\Helpers\MappingHelper;

class DoctrineMappingExtension extends CompilerExtension
{

  public function beforeCompile(): void
  {
    MappingHelper::of($this)
        ->addAttribute($connection = 'default', $namespace = 'App\Model\Database', $path = __DIR__ . '/../app/Model/Database')
        ->addAttribute('default', 'Forum\Modules\Database', __DIR__ . '/../../modules/Forum/Database')
        ->addXml('default', 'Gallery1\Modules\Database', __DIR__ . '/../../modules/Gallery1/Database')
        ->addXml('default', 'Gallery2\Modules\Database', __DIR__ . '/../../modules/Gallery2/Database')
  }

}

Do not forget to register your extension in config.neon.

extensions:
  category: App\Model\DI\DoctrineMappingExtension

DBAL

Tip

Doctrine ORM needs DBAL. You can use doctrine/dbal or nettrine/dbal.

composer require nettrine/dbal
extensions:
  nettrine.dbal: Nettrine\DBAL\DI\DbalExtension
  nettrine.orm: Nettrine\ORM\DI\OrmExtension

Console

Tip

Doctrine DBAL needs Symfony Console to work. You can use symfony/console or contributte/console.

composer require contributte/console
extensions:
  console: Contributte\Console\DI\ConsoleExtension(%consoleMode%)

  nettrine.orm: Nettrine\ORM\DI\OrmExtension

Since this moment when you type bin/console, there'll be registered commands from Doctrine DBAL.

Console Commands

Static analyses

You can use PHPStan to analyze your code.

  1. Install PHPStan and Doctrine extension.
composer require --dev phpstan/phpstan phpstan/phpstan-doctrine
  1. Create ORM loader for PHPStan, e.q. phpstan-doctrine.php.
<?php declare(strict_types = 1);

require __DIR__ . '/../vendor/autoload.php';

return App\Bootstrap::boot()
	->createContainer()
	->getByType(Doctrine\ORM\EntityManagerInterface::class);
  1. Configure PHPStan in phpstan.neon.
includes:
	- vendor/phpstan/phpstan-doctrine/extension.neon

parameters:
	level: 9
	phpVersion: 80200

	tmpDir: %currentWorkingDirectory%/var/tmp/phpstan

	fileExtensions:
		- php
		- phpt

	paths:
		- app

	doctrine:
		objectManagerLoader: phpstan-doctrine.php
  1. And run PHPStan.
vendor/bin/phpstan analyse -c phpstan.neon

Troubleshooting

  1. Are you looking for custom types? You can register custom types in DBAL, see Nettrine DBAL.

  2. You have to configure entity mapping (for example attributes), otherwise you will get It's a requirement to specify a Metadata Driver error.

Examples

Tip

Take a look at more examples in contributte/doctrine.