Nettrine / Doctrine – Doctrine DBAL 4.x + ORM 3.x
- Felix
- Nette Core | 1271
Nettrine balicky prosly tento mesic velkou aktualizaci a pridaly spoustu novych funkci. Osobne z toho mam velmi dobry pocit.
Features
- Podpora aktualni Doctrine DBAL 4.x.
- Podpora aktualni Doctrine ORM 3.x.
- Podpora pro vice databazi.
- Podpora pro Doctrine middlewares.
Deprecations
- Odstranena podpora pro anotace.
- Odstranena podpora pro doctrine/cache.
Doctrine DBAL 4.x + ORM 3.x
Co noveho prinasi Doctrine se muzete docist primo na oficialnich strankach:
- https://www.doctrine-project.org/…eleased.html
- https://www.doctrine-project.org/…2/index.html
- https://www.doctrine-project.org/…3/index.html
Existuji i upgrade manualy primo od Doctrine teamu:
- Upgrade DBAL: https://github.com/…x/UPGRADE.md
- Upgrade ORM: https://github.com/…x/UPGRADE.md
Konfigurace
Soucasti zmen bylo i vylepseni dokumentace.
Stara konfigurace
Takto vypadala stare konfigurace, kde bylo zapotrebi pouzivat vice extensions. To jiz v nove verzi neni potreba.
extensions:
console: Contributte\Console\DI\ConsoleExtension(%consoleMode%)
nettrine.annotations: Nettrine\Annotations\DI\AnnotationsExtension
nettrine.cache: Nettrine\Cache\DI\CacheExtension
nettrine.dbal: Nettrine\DBAL\DI\DbalExtension
nettrine.dbal.console: Nettrine\DBAL\DI\DbalConsoleExtension
nettrine.orm: Nettrine\ORM\DI\OrmExtension
nettrine.orm.cache: Nettrine\ORM\DI\OrmCacheExtension
nettrine.orm.console: Nettrine\ORM\DI\OrmConsoleExtension(%consoleMode%)
nettrine.orm.annotations: Nettrine\ORM\DI\OrmAnnotationsExtension
nettrine.dbal:
connection:
driver: %database.driver%
host: %database.host%
port: %database.port%
dbname: %database.dbname%
user: %database.user%
password: %database.password%
charset: UTF8
serverVersion: '15.0'
debug:
panel: %debugMode%
sourcePaths: [%appDir%]
nettrine.orm:
entityManagerDecoratorClass: App\Model\Database\EntityManagerDecorator
nettrine.orm.annotations:
mapping:
App\Domain\Database: %appDir%/Domain/Database
Nova konfigurace
Nova konfigurace vyzaduje pouze DBAL a ORM extension. Vse konfiguruje automaticky a zaroven je mozne nove pridat vice pripojeni do DB a taktez vice EntityManageru.
extensions:
console: Contributte\Console\DI\ConsoleExtension(%consoleMode%)
nettrine.dbal: Nettrine\DBAL\DI\DbalExtension(%debugMode%)
nettrine.orm: Nettrine\ORM\DI\OrmExtension
nettrine.dbal:
debug:
panel: %debugMode%
connections:
default:
driver: %postgres.driver%
host: %postgres.host%
port: %postgres.port%
dbname: %postgres.dbname%
user: %postgres.user%
password: %postgres.password%
charset: UTF8
serverVersion: 15.0.0
second:
driver: %mariadb.driver%
host: %mariadb.host%
port: %mariadb.port%
dbname: %mariadb.dbname%
user: %mariadb.user%
password: %mariadb.password%
charset: UTF8
serverVersion: 10.10.0
nettrine.orm:
managers:
default:
connection: default
mapping:
App:
directories: [%appDir%/Domain/Database]
namespace: App\Domain\Database
second:
connection: second
mapping:
App:
directories: [%appDir%/Domain/Database]
namespace: App\Domain\Database
Balicky
Takto muzete pouzit novou verzi jeste pred finalnim vydanim.
{
"require": {
"php": ">=8.2",
"contributte/console": "^0.11.0",
"contributte/event-dispatcher": "^0.10.0",
"nettrine/dbal": "^0.10.0",
"nettrine/orm": "^0.10.0",
"nettrine/extra": "^0.2.0",
"nettrine/fixtures": "^0.8.0",
"nettrine/migrations": "^0.10.0"
},
"prefer-stable": true,
"minimum-stability": "dev"
}
Dejte vedet jak se vam s tim pracuje.

- Gappa
- Nette Blogger | 211
Migroval jsem konfiguraci podle návodu výše a zatím jsem narazil jen tohle, že se stihlo změnit :)
Většinu věcí jsem dohledal přímo ve schématu (DbalExtension, resp. OrmExtension), protože registruji vlastní types/typesMapping atp.
Co se samotného fungovaní týče, tak to na první pohled funguje 👍😁 Bude potřeba ale víc testování.
Tracy panel Queries – koukám, že zmizel source dotazu (škoda), ale přibyl sloupec s parametry (super).
Díky! :)
- redwormik
- Člen | 9
Ahoj,
podařilo se někomu s Doctrine ORM 3.x rozchodit entity s
Nette\SmartObject
? Od 3.0 Doctrine používá „nové“ proxy,
co využívají symfony/var-exporter
(https://github.com/…ostTrait.php)
a ty se nekamarádí s magickými metodami ve SmartObject
u.
Mám entitu:
<?php
declare(strict_types=1);
namespace App\Model;
use Doctrine\ORM\Mapping as ORM;
use Nette;
/**
* @property-read int $id
* @property-read string $name
*/
#[ORM\Entity]
class Role
{
use Nette\SmartObject;
#[ORM\Id]
#[ORM\Column]
private int $id;
#[ORM\Column]
private string $name;
public function __construct(int $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
public function getId(): id
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
}
Entitu dostanu jako proxy (přes lazy asociaci nebo EntityManager::getReference) a chci použít magické properties:
$role = $em->getReference(App\Model::class, 1);
echo $role->id; // nepustí inicializaci, vrátí 1; tohle dokonce funguje lépe než 2.x
echo $role->name; // spadne
Jak funguje LazyGhostTrait
?
- dostane od Doctrine lazy properties a inicializátor a properties unsetne,
aby se volal jeho
__get
/__set
- pokud se zavolá
__get
/__set
(chci proměnnou, co nevidím/neexistuje)- zjistí, jestli property je lazy a jestli je (resp. byla by, kdyby nebyla
unsetnutá) přístupná z místa, odkud se přistupuje
(
debug_backtrace
magie) - pokud je lazy+přístupná a objekt není inicializovaný, pustí inicializaci a simuluje přístup z předchozího scope
- pokud je lazy+přístupná, objekt+proměnná jsou inicializované, tak pro
__get
simuluje přístup z předchozího scope (tohle je IMHO edge-case, takový přístup většinou nepůjde přes__get
) - pokud existují původní (tady
Nette\SmartObject
)__get
/__set
, tak je zavolá - jinak (ne-lazy/neexistující/nepřístupná property) simuluje přístup z předchozího scope
- zjistí, jestli property je lazy a jestli je (resp. byla by, kdyby nebyla
unsetnutá) přístupná z místa, odkud se přistupuje
(
První problém – nespuštění inicializace / „dvojitý“
__get
: LazyGhostTrait
při přístupu „z venku“
nepustí inicializaci a pustí (přes Nette\SmartObject
__get
) metodu getName
, ten v příkladu vrací
$this->name
. Ten ale není inicializovaný, takže
getName
spadne.
Aby to fungovalo, musel by se znova pustit LazyGhostTrait
__get
(tentokrát z privátního scopu), ale to se nestane,
protože __get
na name
už probíhá. To se dá
vyřešit – magické a skutečné properties se musí jmenovat jinak.
Druhý problém – inicializace v Doctrine / „kouzelný“
__set
: Při inicializaci Doctrine nastavuje hodnoty proměnných
přes ReflectionProperty
. To normálně magické metody nepouští,
ale pokud je property unsetnutá, tváří se jako by nebyla, takže nastavení
z privátního scope i přes ReflectionProperty
(kterou
nezískám, pokud by proměnná normálně neexistovala) pouští
__set
. Doctrine nastaví objekt jako inicializovaný předtím,
než nastavuje property (https://github.com/…itOfWork.php#…),
takže LazyGhostTrait
nenastaví property přímo (jinak přístup
přes ReflectionProperty
pozná), ale volá
Nette\SmartObject
__set
. Setter tady neexistuje, proto
to spadne. Navíc kvůli prvnímu problému chci mít magickou property
pojmenovanou jinak, tady bych ji musel mít pojmenovanou stejně.
Editoval redwormik (31. 1. 8:53)
- Felix
- Nette Core | 1271
@redwormik Ja uz dlouho SmartObject nepouzivam, tak nedokazu rict. Ale dnes v dobe kdy existuje v PHP 8.4 property hooks (https://www.php.net/…ty-hooks.php), tak jeste potrebujes SmartObject?
- redwormik
- Člen | 9
Tak možná s přejmenováním magických properties + trochou magie, co fixne nastavení přes Reflection:
<?php
declare(strict_types=1);
namespace App\Model;
use Nette;
trait EntitySmartObject
{
use Nette\SmartObject {
__set as private netteSet;
}
public function __set(string $name, mixed $value): void
{
$frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
if (
($frame['class'] ?? null) === \ReflectionProperty::class &&
$frame['object']->name === $name &&
$frame['object']->class === self::class &&
!$frame['object']->isInitialized($this) &&
!$frame['object']->isDynamic()
) {
$this->$name = $value;
return;
}
$this->netteSet($name, $value);
}
}
<?php
declare(strict_types=1);
namespace App\Model;
use Doctrine\ORM\Mapping as ORM;
use Nette;
/**
* @property-read int $Id
* @property-read string $Name
*/
#[ORM\Entity]
class Role
{
use EntitySmartObject;
#[ORM\Id]
#[ORM\Column]
private int $id;
#[ORM\Column]
private string $name;
public function __construct(int $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
public function getId(): id
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
}
$role = $em->getReference(App\Model::class, 1);
echo $role->Id;
echo $role->Name;
Editoval redwormik (31. 1. 10:07)
- redwormik
- Člen | 9
@Felix Nepotřebuju, řešim to spíš pro úplnost, jestli to jde/nejde.
EDIT: případně kvůli upgradu Doctrine ve starších projektech, co SmartObject používají
BTW – property hooks ještě v Doctrine entitách nejdou, ne? Alespoň 2.20 je nepustí, nevim, jak to mají vymyšlený ve 3.x, podle https://github.com/…issues/11624 nejdou, ale výjimka (https://github.com/…m/pull/11628) ve 3.x není.
Editoval redwormik (31. 1. 10:01)
- VáclavČerný
- Člen | 6
@Felix Ahoj, díky za aktualizaci. Při vyzkoušení jsem narazil na dva drobné problémy:
1.
The item 'nettrine.dbal › connections › default › password' expects to be string, dynamic given.
Pro nastavení hesla používám ENV a do konfigurace se pak dostane
Nette\DI\DynamicParameter
.
Bylo by řešením v extension ve schématu configu povolit dynamické
parametry 'password' => Expect::string()->dynamic()
, nebo lze
řešit jinak?
2. Service of type Doctrine\ORM\Configuration not found.
,
BeberleiBehaviorExtension.php:169
,
nettrine/extensions-beberlei
Službu není problém do DI přidat. Spíš je otázka, zda by to neměla
udělat automaticky už ORM extension.
Díky
- VáclavČerný
- Člen | 6
Narazil jsem ještě na jeden další problém. Jakmile u DBAL extension
nastavím resultCache
, tak “cached query” spadne na
Cannot serialize Symfony\Component\Cache\Adapter\AbstractAdapter
,
protože parametr se dostane do Doctrine\DBAL\Connection
a
QueryCacheProfile::generateCacheKeys
se pokusí serializovat
AbstractAdapter
(serialize($connectionParams)
).
- Felix
- Nette Core | 1271
Ahoj @VáclavČerný, zrovna vcera jsem vydal verzi s podporou dynamicke konfigurace.
https://github.com/…/tag/v0.10.1
U toho druheho problemu, zalozis mi prosim ticket a posles tam ukazkovou konfiguraci? Pridam to do testu a odhalime kde je problem.