Nette a Doctrine 2 – mé řešení
- Panda
- Člen | 569
Přislíbil jsem, že sepíši něco o mém řešení Doctrine 2, takže tak činím… Na úvod jen upozornění, že se jedná o mé řešení integrace s Doctrine 2, neznamená to tedy, že se jedná o nejlepší řešení.
Ještě než začneme psát vůbec nějaký kód, přidáme si pár řádek
do config.ini
:
; doctrine configuration
[common.doctrine]
entityDir = '%appDir%/Entities'
proxyDir = '%appDir%/Proxies'
proxyNamespace = 'PandaWeb\Proxies'
; connection options
[development.doctrine.connection]
driver = pdo_pgsql
user = pandaweb
password = pandaweb
dbname = pandaweb
Sekce [common.doctrine]
obsahuje obecné nastavení složek a
namespaces naší aplikace, [development.doctrine.connection]
pak
nastavení parametrů připojení pro konkrétní prostředí.
Vedle klasického bootstrap.php
si vytvoříme další
speciální bootstrap čistě pro Doctrine, nazveme ho například
bootstrap-doctrine.php
. Bude se nám hodit při dalším použití
v aplikaci, například v bootstrapu pro testy, nebo ve skriptech pro
příkazovou řádku. Důležitá místa v kódu jsem se snažil okomentovat,
unsety jsou v kódu kvůli automatizovaným testům v PHPUnit, které si
snaží serializovat globální proměnné.
<?php
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Configuration;
use Nette\Environment;
// načítáme objekt s konfigurací
$doctrine = Environment::getConfig('doctrine');
$config = new Configuration();
// nastavíme složky a namespaces
$config->setProxyDir($doctrine->proxyDir);
$config->setProxyNamespace($doctrine->proxyNamespace);
// v produkčním prostředí nechceme generovat proxy třídy automaticky
// nastavením na FALSE můžeme vynutit manuální vytváření za všech okolností
$config->setAutoGenerateProxyClasses(!Environment::isProduction());
$metadataDriver = $config->newDefaultAnnotationDriver($doctrine->entityDir);
$config->setMetadataDriverImpl($metadataDriver);
// v produkčním prostředí zkusíme použít APCCache, jinak ArrayCache
if (Environment::isProduction() && extension_loaded('apc')) {
$cache = new Doctrine\Common\Cache\ApcCache();
} else {
$cache = new Doctrine\Common\Cache\ArrayCache();
}
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// cleanup
unset($cache);
$eventManager = new Doctrine\Common\EventManager();
// nastavení charsetů pro MySQL
if ($doctrine->connection->driver == 'pdo_mysql') {
$eventManager->addEventSubscriber(new \Doctrine\DBAL\Event\Listeners\MysqlSessionInit('utf8', 'utf8_czech_ci'));
}
// Vytvoření samotného EntityManageru
$entityManager = EntityManager::create((array) $doctrine->connection, $config, $eventManager);
Environment::getServiceLocator()->addService('Doctrine\ORM\EntityManager', $entityManager);
// cleanup
unset($doctrine, $config, $metadataDriver, $eventManager, $entityManager);
Environment::setServiceAlias('Doctrine\\ORM\\EntityManager', 'EntityManager');
Environment::setServiceAlias('PandaWeb\\Application\\DatabaseManager', 'DatabaseManager');
Povšímnětě si také přidání aliasu pro službu
PandaWeb\Application\DatabaseManager
– jedná se o třídu,
kterou používám jako obálku nad EntityManager
em a která mi
usnadňuje přístup k repositářům entit a některým funkcím
EntityManager
u:
<?php
namespace PandaWeb\Application;
use Doctrine\ORM\EntityManager;
use PandaWeb\Entities\BaseEntity;
/**
* @property-read \Doctrine\ORM\EntityManager $entityManager
* @property-read \Doctrine\ORM\EntityRepository $page
* @property-read \PandaWeb\Repositories\UserRepository $user
* ...
*
* @method void clear() clear()
* @method void flush() flush()
* @method void remove() remove(BaseEntity $entity)
* @method void refresh() refresh(BaseEntity $entity)
* @method void beginTransaction() beginTransaction()
* @method void commit() commit()
* @method void rollback() rollback()
*/
class DatabaseManager
{
/** @var EntityManager */
protected $entityManager;
public function __construct()
{
$this->entityManager = \Nette\Environment::getService('Doctrine\ORM\EntityManager');
}
public function __get($name)
{
if ($name == 'entityManager') {
return $this->entityManager;
} else {
return $this->entityManager->getRepository('PandaWeb\\Entities\\' . ucfirst($name));
}
}
public function __call($name, $arguments)
{
return \call_user_func_array(array($this->entityManager, $name), $arguments);
}
public function persist($entity)
{
$this->entityManager->persist($entity);
}
public function lock($entity, $version)
{
$this->entityManager->lock($entity, \Doctrine\DBAL\LockMode::OPTIMISTIC, $version);
}
}
Getter nám umožňuje jednoduše přistupovat k jednotlivým repositářům
entit: $databaseManager->page
nám získá reposítář pro
entitu PandaWeb\\Entities\\Page
a podobně. Metodu
lock()
jsem si napsal jako shortcut pro optimistické zamykání,
magický __call()
se nám postará o předání volání
neznámých funkcí přímo EntityManager
u. Třída samozřejmě
musí být zaregistrovaná jako služba v config.ini
:
[common.service]
; ...
PandaWeb-Application-DatabaseManager = PandaWeb\Application\DatabaseManager
; ...
Dále se nám bude hodit třída BaseEntity
, od které budeme
dědit konkrétní entity:
<?php
namespace PandaWeb\Entities;
use Nette\Object;
use Nette\Environment;
use Nette\Caching\Cache;
/**
* @MappedSuperclass
* @HasLifecycleCallbacks
*
* @property-read int $id
*/
abstract class BaseEntity extends Object
{
/**
* @Id
* @Column(type = "integer")
* @GeneratedValue
* @var int
*/
protected $id;
/** @var \Doctrine\ORM\EntityRepository */
private $repository;
public function persist()
{
Environment::getDatabaseManager()->persist($this);
}
public function remove()
{
Environment::getDatabaseManager()->remove($this);
}
public function setValues(array $data)
{
foreach ($data as $key => $value) {
$this->__set($key, $value);
}
}
/** @return \Doctrine\ORM\EntityRepository */
public function getRepository()
{
if ($this->repository === NULL) {
$this->repository = Environment::getEntityManager()->getRepository(\get_class($this));
}
return $this->repository;
}
public function free()
{
$this->repository = NULL;
}
public function __sleep()
{
$this->free();
}
public function getId()
{
return $this->id;
}
public function getCacheTags()
{
$tags = array();
if ($this->id !== NULL) {
$tags[] = get_class($this) . '#' . $this->id;
}
return $tags;
}
/**
* @PostPersist
* @PostUpdate
* @PostRemove
*/
public function cleanCache()
{
Environment::getCache()->clean(array(
Cache::TAGS => array_merge(array(get_class($this)), $this->getCacheTags())
));
}
}
Tato třída nám umožňuje volat operace persist()
a
remove()
přímo nad konkrétním objektem. Mnohem zajímavější
je však funkce cleanCache()
– ta se registruje jako lifecycle
callback a při operacích, které nějakým způsobem zasahují do databáze,
automaticky invalidují cache. To nám usnadňuje bezpečné a bezstarostné
cachování objektů závislých na databázi. Při cachování objektu
Nette\Security\Permission
tak můžeme jednoduše zapsat
$cache->save('permission', $permission, array(
Cache::TAGS => array(
'PandaWeb\Entities\Role',
'PandaWeb\Entities\Permission'
)
));
a cache se nám automaticky invaliduje při jakékoliv změně daných tabulek v databázi. Samotřejmě to funguje jen pro modifikace provedené skrz naší aplikaci.
V BasePresenter
u si pak objekt DatabaseManger
načteme do členské proměnné, abychom nemuseli zatěžovat
Nette\IServiceLocator
pokaždé, když budeme chtít pracovat
s databází:
<?php
// ...
abstract class BasePresenter extends Presenter
{
public function __construct(\Nette\IComponentContainer $parent = NULL, $name = NULL)
{
parent::__construct($parent, $name);
$this->databaseManager = Environment::getService('PandaWeb\Application\DatabaseManager');
}
// ...
}
Další věcí, kterou budeme potřebovat, je nástroj pro práci
s databází – tedy něco, co nám umožní při vývoji průběžně
aktualizovat databázové schéma podle toho, jak budeme přidávat a upravovat
entity. Vytvoříme si tedy na to několik skriptů. Vedle složky
app
si vytvoříme složku scripts
. Do ní si
vytvoříme obecný bootstrap pro naše konzolové skripty
(bootstrap.php
):
use Nette\Environment;
use Nette\Debug;
define('CLI_DIR', __DIR__);
define('APP_DIR', CLI_DIR . '/../app');
define('LIBS_DIR', CLI_DIR . '/../libs');
require LIBS_DIR . '/Nette/loader.php';
Debug::enable();
Environment::loadConfig();
require APP_DIR . '/bootstrap-doctrine.php';
K tomu přihodíme mírně modifikovanou verzi doctrine-cli.php
z dokumentace:
<?php
use Nette\Environment;
require __DIR__ . '/bootstrap.php';
$entityManager = Environment::getService('Doctrine\ORM\EntityManager');
$helpers = array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($entityManager->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager)
);
$helperSet = new \Symfony\Components\Console\Helper\HelperSet();
foreach ($helpers as $alias => $helper)
$helperSet->set ($helper, $alias);
$cli = new \Symfony\Components\Console\Application('Doctrine Command Line Interface', Doctrine\ORM\Version::VERSION);
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
$cli->addCommands(array(
// DBAL Commands
new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(),
new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(),
// ORM Commands
new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(),
new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(),
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(),
new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand(),
new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand(),
new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(),
new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(),
new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(),
new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(),
));
$cli->run();
A pokud pracujeme na Windows, bude se nám možná hodit
doctrine-cli.bat
:
@echo off
REM cestu k PHP si nastavte podle potřeby
C:\webserverX64\php\php.exe doctrine-cli.php %*
Nyní můžeme jednoduše z příkazové řádky ve složce
scripts
vytvořit naše databázové schéma:
doctrine-cli orm:schema-tool:create
A aktualizace provádíme příkazem
doctrine-cli orm:schema-tool:update
Příkazy poslušně načtou informace z entity a vytvoří/upraví podle nich databázové schéma.
Pokud jsme odvážnější a chceme jít ještě kousek dál, můžeme si vytvořit vlastní skript, který kromě standartních operací v Doctrine bude umět na databázovém schématu provádět vlastní změny načtené z SQL skriptů. To nám umožní využívat funkce, které jsou specifické pro náš databázový stroj – například triggery, uložené procedury, views, FTS konfigurace v PostgreSQL…
Nejdříve uvedu zdrojové kódy a až pak vysvetlím práci s nimi.
SQLImporter.php
:
<?php
namespace PandaWeb;
use Doctrine\ORM\EntityManager;
use Nette\Environment;
use Nette\String;
class SQLImporter
{
private static $driver;
private static function getDriver()
{
if (self::$driver === NULL) {
self::$driver = Environment::getConfig('doctrine')->connection->driver;
if (String::startsWith(self::$driver, 'pdo_')) {
self::$driver = substr(self::$driver, 4);
}
}
return self::$driver;
}
public static function importDir(EntityManager $entityManager, $subdir)
{
$connection = $entityManager->getConnection();
if (file_exists($dir = __DIR__ . '/' . self::getDriver() . '/' . $subdir)) {
$files = array();
$iterator = new \RecursiveIteratorIterator($dir = new \RecursiveDirectoryIterator($dir));
foreach ($iterator as $file) {
/* @var $file SplFileInfo */
if ($file->isFile() && String::endsWith($file->getFilename(), '.sql')) {
$files[$dir->getSubPathname()] = $file->getPathname();
}
}
ksort($files);
foreach ($files as $subpath => $file) {
echo 'Importing ' . $subdir . '/' . $subpath . '...';
$connection->exec(file_get_contents($file));
echo ' ok' . PHP_EOL;
}
}
}
}
schema-update.php
:
<?php
use Nette\Environment;
use Nette\String;
if (!class_exists('\Nette\Environment')) {
require_once __DIR__ . '/bootstrap.php';
}
require_once __DIR__ . '/SQLImporter.php';
$entityManager = Environment::getService('Doctrine\ORM\EntityManager');
/* @var $entityManager Doctrine\ORM\EntityManager */
$metadata = $entityManager->getMetadataFactory()->getAllMetadata();
echo 'Generating proxies...';
$entityManager->getProxyFactory()->generateProxyClasses($metadata);
echo ' ok' . PHP_EOL;
\PandaWeb\SQLImporter::importDir($entityManager, 'clean');
echo 'Performing schema update...';
$schemaTool = new \Doctrine\ORM\Tools\SchemaTool($entityManager);
$schemaTool->updateSchema($metadata);
echo ' ok' . PHP_EOL;
\PandaWeb\SQLImporter::importDir($entityManager, 'init');
// cleanup
unset($entityManager, $metadata, $schemaTool);
echo 'Schema update done.' . PHP_EOL;
Metoda SQLImporter::importDir
bude načítat skripty ze složky
%doctrine.connection.driver%/$subdir
s tím, že pokud driver bude
začínat na pdo_
, tak se odtrhne. Skripty pro PostgeSQL se tedy
budou načítat z pgsql/$subdir
. Aktualizace bude probíhat ve
3 fázích – načtení skriptů z podsložky clean
, změna
schématu a načtení skriptů z podsložky init
.
Fáze clean
uvede databázi do stavu, v jakém se nacházela
těsně před provedením fáze init
. Tento krok je nutný,
protože skripty z fáze init
mohou modifikovat existující
objekty tak, že analýza schématu z Doctrine si nebude vědět s objekty
radu (například nebude znát daný typ indexu, sloupce, …) a
aktualizace selže.
Lepší ale bude příklad. Ve složce pgsql/init
mám skript
20_pages.sql
:
CREATE TRIGGER pages_fts_update AFTER INSERT OR UPDATE
ON pages FOR EACH ROW
EXECUTE PROCEDURE fts_update('Page');
Vytvořený trigger by měl být ve fázi clean
odstraněn,
proto ve složce pgsql/clean
musím také vytvořit skript
20_pages.sql
:
DO $$BEGIN
IF EXISTS(SELECT tablename FROM pg_tables WHERE tablename = 'pages') THEN
DROP TRIGGER IF EXISTS pages_fts_update ON pages;
END IF;
END$$ LANGUAGE plpgsql;
Pokud bych tento skript nenapsal a později se rozhodl tabulku pro entity
PandaWeb\Entities\Page
později přejmenovat, došlo by při
přištím spuštění aktualizace k nepěkné výjimce – Doctrine by se
snažilo odstranit starou tabulku, ale na té by závisel trigger, o kterém
Dotrine nemá potuchy a databáze by odmítla takovou operaci provést.
To by bylo asi vše. Doufám, že se mi podařilo to sepsat srozumitelně a že to bude alespoň někomu užitečné. Připomínky, návrhy a Vaše řešení uvítám! A jsem si jistý, že nejen já…
Editoval Panda (17. 8. 2010 20:58)
- Panda
- Člen | 569
Na nette@conf.netlab.cz se
mě někteří ptali ohledně té cache, takže sem hodím malou ukázku kusu
mé třídy Permission
, který krásně ilustruje mojí
myšlenku:
<?php
namespace PandaWeb\Application;
use Nette\Security\IResource;
use Nette\Caching\Cache;
class Permission extends \Nette\Security\Permission
{
private $privileges = array();
static public function createService()
{
$cache = \Nette\Environment::getCache('PandaWeb.Application.Permission');
if (isset($cache['instance'])) {
return $cache['instance'];
} else {
$permission = new self();
$databaseManager = \Nette\Environment::getDatabaseManager();
/* @var $databaseManager DatabaseManager */
$application = \Nette\Environment::getApplication();
/* @var $application Application */
foreach ($databaseManager->role->findAll() as $role) {
/* @var $role \PandaWeb\Entities\Role */
$permission->addRole($role->getRoleId());
}
$application->getModules()->setupPermission($permission);
foreach ($databaseManager->permission->findAll() as $privilege) {
$permission->allow((string) $privilege->roleId, $privilege->resource, $privilege->privilege);
}
$cache->save('instance', $permission, array(
Cache::TAGS => array(
'PandaWeb\Entities\Role',
'PandaWeb\Entities\Permission'
)
));
return $permission;
}
}
// ...
}
Jakákoliv změna daných tabulek v databázi prostřednictvím entit kdekoliv v aplikaci způsobí automatickou invalidaci tohoto objektu a jeho sestavení z aktuálních dat. A já jako programátor se o to už nemusím starat…
- Patrik Votoček
- Člen | 2221
Super a hezky sepsáno. Líbí se mě hlavně ta cache. (hned jsem si to taky implementoval). Jinak máme snad všechno úplně jinak.
Když už tady je tohle vlákno chci se zeptat ostatních co používají Doctrine 2 na pár věcí, na které narážím. Jsou to věci, které buďto nevím jak je vyřešit nebo se mě stávající řešení nelíbí.
- Nastavení repository už u MappedSuperclass
- Chtěl bych si udělat BaseEntity (MappedSupperclass i pro) ale narážím
na to jak definovat associaci ($parent) jako target totiž nemůžu uvést
MappedSupperclass nebo snad ano?
- stromečky (BaseTreeEntity)
- traverzy (BaseNestedEntity)
- verzování článků (zachovávání revizí)
- u class i table inheritance mě vadí že musím uvádět potomky v anotaci u předka… :-/
Jak to řešíte vy?
- Majkl578
- Moderator | 1364
Mně se to líbí, ale taky dělám kupu věcí jinak (nemám tolik zkratek pro volání metod).
vrtak-cz napsal(a):
- Nastavení repository už u MappedSuperclass
Nelze, nicméne by to možná byl dobrý feature, něco jako defaultní namespace pro proxy, co myslíš?
- Chtěl bych si udělat BaseEntity (MappedSupperclass i pro) ale narážím na to jak definovat associaci ($parent) jako target totiž nemůžu uvést MappedSupperclass nebo snad ano?
Ne, protože to není entita.
- stromečky (BaseTreeEntity)
- traverzy (BaseNestedEntity)
Tohle ale můžeš, implementuješ rozhraní už v mapované supertřídě a pak jen podědíš. Co používám (funguje lépe než to co používá Panda, jen nemá metody na přesun mezi stromy) víš.
- verzování článků (zachovávání revizí)
DoctrineExtensions\Versionable (povídání okolo).
- u class i table inheritance mě vadí že musím uvádět potomky v anotaci u předka… :-/
Což je logické, aby Doctrine věděla co ji rozšiřuje, ne? :)
- Patrik Votoček
- Člen | 2221
Majkl578 napsal(a):
vrtak-cz napsal(a):
- Nastavení repository už u MappedSuperclass
Nelze, nicméne by to možná byl dobrý feature, něco jako defaultní namespace pro proxy, co myslíš?
Právě proto se ptám jesli to mám zadat jako issue nebo ne… :-)
- Chtěl bych si udělat BaseEntity (MappedSupperclass i pro) ale narážím na to jak definovat associaci ($parent) jako target totiž nemůžu uvést MappedSupperclass nebo snad ano?
- stromečky (BaseTreeEntity)
- traverzy (BaseNestedEntity)
Tohle ale můžeš, implementuješ rozhraní už v mapované supertřídě a pak jen podědíš. Co používám (funguje lépe než to co používá Panda, jen nemá metody na přesun mezi stromy) víš.
což je mě ****** když nemůžu uvést parent… :-( (Stromová data bez parentu jsou na nic…)
- u class i table inheritance mě vadí že musím uvádět potomky v anotaci u předka… :-/
Což je logické, aby Doctrine věděla co ji rozšiřuje, ne? :)
Což je na *** protože to potřebuju dělat dynamicky… Ale nemůžu… Dozvědět se to může projede strom tříd a zjistí jestli jí nějaká nedědí… (stejně se to kešuje)
- romansklenar
- Člen | 655
S tou invalidací cache je to dobrý nápad, jen podle mě BaseEntity pro to není vhodné místo. Spíš bych to zkusil nacpat do událostí (netestováno):
<?php
namespace DoctrineExtensions;
use Doctrine\Common\EventSubscriber,
Doctrine\ORM\Events,
Doctrine\ORM\Event\OnFlushEventArgs,
Doctrine\ORM\EntityManager,
Nette\Environment,
Nette\Caching\Cache;
class NetteCacheSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(Events::onFlush);
}
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$tags = array();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
$tags[] = $class = get_class($entity);
$tags[] = $class . '#' . $uow->getEntityIdentifier($entity);
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
...
}
foreach ($uow->getScheduledEntityDeletions() as $entity) {
...
}
foreach ($uow->getScheduledCollectionDeletions() as $col) {
...
}
foreach ($uow->getScheduledCollectionUpdates() as $col) {
...
}
$this->clean(array_unique($tags));
}
private function clean(array $tags = array())
{
$cache = $this->getCache();
$cache->clean(array(
Cache::TAGS => $tags
));
}
private function getCache()
{
return Environment::getCache();
}
}
Zaregistrování pak vložit někde k inicializaci samotné Doctrine.
/* @var $evm Doctrine\Common\EventManager */
$evm->addEventSubscriber(new DoctrineExtensions\NetteCacheSubscriber());
Nebo někde v průběhu aplikace:
/* @var $em Doctrine\ORM\EntityManager */
$em->getEventManager()->addEventSubscriber(new DoctrineExtensions\NetteCacheSubscriber());
- misiak
- Člen | 28
Pre ľudí, ktorý pracujú pod Windows som pripravil menšiu utilitku, ktorá zaregistruje do shell context menu windows explorera možnosti na spustenie skriptu priamo v php.exe cez konzolu.
Taktiež pre doctrine-cli tam je možnosť spustiť tento skript s parametrom, ktorý si vyberiete z menu :)
Screenshot ako to vyzerá: https://github.com/…reenshot.jpg
Samozrejme viem, že to nie je žiadna nette feature ale možno sa vám to zíde a možno vás napadne ako to mám vylepšiť :)
Jednoduchý návod ako aj 32 a 64bit aplikácia na stiahnutie sú tu. https://github.com/…PHPCommander
Týmto by som ešte veľmi rád požiadal ak niekto má 32bit Vista/Seven aby to otestoval. Na Windows 7 x64 to funguje.
- Patrik Votoček
- Člen | 2221
K tomuhle přejmenování došlo nedávno… Tipnul bych že když to panda psal ještě to nebylo. (myslím že to je BETA 3 vs. BETA 4)
- srigi
- Nette Blogger | 558
Toto Pandove riesenie ma celkom zasadny nedostatok (alebo lepsie povedane, nepodchytenu situaciu) – neumoznuje jednoduche instancovanie noveho EM.
Ak totiz operacia s EM vyhodi vynimku, EM sa uzamkne. Vo vacsine pripadov staci na nu zareagovat (prepisat hodnotu v unique key a pod.) a vyviest app z hazardu. Ale zamknuty EM uz nie je na toto pouzitelny, treba novu instanciu EM.
Nemate niekto napad ako toto riesit? Ja sa musim nad tymto poriadne zamysliet, ale momentalne nie je cas, bolo fajn, keby niekto prisiel s jednoduchou upravou tohto kodu.
- arron
- Člen | 464
Uz jsem nad tim premyslel, ale zatim jsem to nepotreboval, cili neimplementoval. Nicmene asi bych to vyresil tak, ze bych udelal nejakou tovarnu na EntityManager, ktere bych v boobstrapu jenom predal vsechny dulezite parametry, a ktera by v pripade, ze by byl EntityManager uzavreny, tak by z tech parametru vytvorila novy. Jinak by vracela uz hotovou instanci. Misto hotove instance bych pak do ServiceLocatoru dal tu tovarnu.
Takze mam obavu, ze jednoducha uprava tohohle kodu asi nebude. A jestli ano, tak pochybuju, ze prilis systemova.
- juzna.cz
- Člen | 248
Ze zacatku jsem nezavysle prisel na stejne reseni jako Panda, cili bootstrap-doctrine.php. Ale pozdeji jsem si uvedomil, ze to neni to prave orechove. Lepsi je vytvaret instanci EM az ve chvili, kdy je potreba, pomoci tovarnicky. Pekne to ma vyresene @honzamarek, see https://github.com/…actories.php#L34
Diky tomu nemusis premyslet, jestli v CLI skriptech budes doctrine potrebovat nebo ne, ale proste az prijde na radu kod, kteri chce EM, tak si ho vytvori. Zda se mi to jako o hodne cistejsi reseni, ted jsem si to implementoval (https://github.com/…actories.php) a jsem s tim velice spokojen