Nette a Doctrine 2 – mé řešení

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Panda
Člen | 569
+
0
-

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 EntityManagerem a která mi usnadňuje přístup k repositářům entit a některým funkcím EntityManageru:

<?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 EntityManageru. 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 BasePresenteru 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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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.

srigi
Nette Blogger | 558
+
0
-

Panda napsal(a):

Mas tam chybku v subore doctrine-cli.php – Nema tam byt

Symfony\Components\Console\Helper\HelperSet ale

Symfony\Component\Console\Helper\HelperSet

Editoval srigi (12. 9. 2010 13:54)

Patrik Votoček
Člen | 2221
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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