Bezbolestná integrace Doctrine 2 ORM do Nette – Kdyby/Doctrine

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

Pokud myslíš přidáním do definice parametru tak to tam mám a nechce fungovat:

class User extends \System\Doctrine\Entity
{
	/**
	 * @ORM\OneToOne(targetEntity="System\AccountModule\Models\SecurityQuestions\SecurityQuestion", mappedBy="user", cascade={"persist"})
	 */
	protected $securityQuestion;
}
Filip Procházka
Moderator | 4668
+
0
-

@akadlec Viz https://forum.nette.org/…yby-doctrine#…

Začínám si říkat, že bych tu save metodu měl deprecatnout :)

akadlec
Člen | 1326
+
0
-

@Filip Procházka: jo tohohle jsem si samozřejmě všimnul a jak jsem psal, chápal bych to u kolekce one-to-many ale proč u one-to-one? Ono na tom sem udělal celkem dost editů ještě předtím než jsem přešel na Kdyby a takhle tam budu muset přidat kupu DAO pro jednotlivé entity.

Filip Procházka
Moderator | 4668
+
0
-

@akadlec to tak dělá přímo doctrine, když dáš persistovat jednu entitu. Když má cascade persist tak uloží i relace, ale už je neupdatuje. Ono je asi lepší (když chceš ukládat i relace) tak používat $em->flush().

enumag
Člen | 2118
+
0
-

@Filip Procházka: Na to taky často narážim. V podstatě vždy místo $dao->flush() píšu $dao->getEntityManager()->flush().

akadlec
Člen | 1326
+
0
-

@Filip Procházka: Díky tohle již funguje. K původní save metodě sem si přidal saveAll která provede save a flushne celý EM

Jinak narazil sem ještě na jeden problém a to nastavování autoGenerateProxyClasses. Ve tvém DI je to řešeno na detekci debugMode kdy pro TRUE to děla generování vždy a pro FALSE to generování proxy deaktivuje což je problém pokud ty proxy nejsou vygenerovány. Je potřeba aby se nastavovaly hodnoty 1 (generovat vždy) a 2 (generovat když není) pokud tedy nějak neprovádíte generování mimo prod

akadlec
Člen | 1326
+
0
-

Tak další dotaz ;) Když mám relaci one-to-one, mám nastaveno cascade a podentita neexistuje, neměla by se pak automaticky vytvořit?

class TopEntity
{
	/**
	 * @ORM\OneToOne(targetEntity="SubEntity", mappedBy="topEntity", cascade={"persist"})
	 */
	protected $subEntity;
}

class SubEntity
{
	/**
	 * @ORM\OneToOne(targetEntity="TopEntity", inversedBy="subEntity")
	 * @ORM\JoinColumn(name="id", referencedColumnName="id", onDelete="CASCADE")
	 */
	protected $topEntity;
}

V topEntity už nějaký záznam mám, ale není pro něj vytvořen záznam v subEntity a když pak mám vytaženou topEntitu a přidám ji subEntitu a provedu save topEntity neměla by se založit i ta subEntita? Při editaci je to ok dojde k editaci, ale při insertu se nic nestane.

Filip Procházka
Moderator | 4668
+
0
-

Určitě nemělo :)

Buďto si to rovnou vytvoř

class TopEntity
{
	/**
	 * @ORM\OneToOne(targetEntity="SubEntity", mappedBy="topEntity", cascade={"persist"})
	 */
	protected $subEntity;

	public function __construct()
	{
		$this->subEntity = new SubEntity();
	}

}

Popřípadě pokud ti to stačí lazy tak

class TopEntity
{
	/**
	 * @ORM\OneToOne(targetEntity="SubEntity", mappedBy="topEntity", cascade={"persist"})
	 */
	private $subEntity;

	public function getSubEntity()
	{
		if ($this->subEntity === NULL) {
			$this->subEntity = new SubEntity();
		}

		return $this->subEntity;
	}

}
akadlec
Člen | 1326
+
0
-

A co v okamžiku kdy tu subentitu nepotřebuji až do určitého okamžiku. Mám třeba entitu Uživatel a subentitu BezpečnostníOtázka. Při vytvoření účtu ta bezpečnostní otázka neexistuje takže není potřeba subentitu zakládat. Pak se uživatel přihlásí a edituje nastavení zabezpečení kde se zjistí že otázku nemá založenou tak ať si ji založí a v tomto okamžiku se má subentita založit.

EDIT:
Takže beru zpět, žádná změna a začalo to fungovat ;) Možná nějaké erupce na slunci to zablokovaly ;)

Editoval akadlec (9. 12. 2013 13:28)

akadlec
Člen | 1326
+
0
-

A když už jsme u Nette a Doctrine…Filipe jak jste na tom s DoctrineForms? Je to už nějaké použitelné verzi?

Filip Procházka
Moderator | 4668
+
0
-

@Akadlec: mapování by mělo být jakš takš funkční (i testy na to mám), pokud to chceš postrčit, můžeš to začít opatrně! používat a hlásit bugy :)

Jiří Nápravník
Člen | 710
+
0
-

Mám cca takovýto kód,
Entita Photo má ManyToOne source atribut a ManyToMany keywords kolekci

	$em = $this->photoDao->getEntityManager();
	$keywordDao = $em->getDao('Keyword');

	foreach($photoArray['keywords'] as $keyword){
		$keyword = $keywordDao->findOneBy(array('keyword' => $keyword));
		$photo->addKeyword($keyword);
	}

	$photoSourceDao = $em->getDao('PhotoSource');
	$source = $photoSourceDao->findOneBy(array('source' => $photoArray['source']));
	$photo->setSource($source);

	$this->photoDao->save($photo);

když zavolám save, tak dostanu výjimku: „Entity is not instanceof PhotoSource, instanceof Photo given.“

Co dělám špatně? Pokud si vytáhnu entitymanager a použiji persist + flush, tak to proběhne v pořádku, jak bych očekával.

akadlec
Člen | 1326
+
0
-

proč ty DAO vytahuješ přes EM?

Jiří Nápravník
Člen | 710
+
0
-

Já jsem vůl, já si předal DAO pro blbou entitu v neonu, omlouvám se…

akadlec: Jak jinak bych ji měl předávat? Pokud myslíš přes konstruktor, tak to dělám, ale s těmito dvěma DAO pracuji jen v jedné metodě, tak je lepší si to vytáhnout až tady. Nebo je nějaký jiný postup?

akadlec
Člen | 1326
+
0
-

Tak DAO máš jako službu a pracuješ přímo s ní a né že ji použiješ jen pro získání EM ne?

Jiří Nápravník
Člen | 710
+
0
-

Však s ní pracuji, tohle je osekané hodně a s photoDao tam pracuju dost. Ale kdyý dotanu z formuláře jenom id pro kolekci třeba klíčových slov, tak si potřebuju někde získat DAO pro klíčová slova a pro ně si vytahat konkrétní entity ne? Většinou to řeším přes $dao->related(‚nazev_asociace‘) a mám to dao, tady jsem to řešil přes EM, protože mi to nešlo (díky tomu, jak píšu, že jsem si předal špatné DAO v neonu).

Ale když potřebuji EM třeba pro flush více změn v různých entitách, tak si jej vytáhnu přes DAO->getEM, ne?

Filip Procházka
Moderator | 4668
+
0
-

Pokud potřebuješ i EntityManager (což je validní požadavek) tak je lepší si předat jenom EM a DAO si vytáhnout až v konstruktoru, ušetříš se psaní konfigurace :)

public function __construct(EntityManager $em)
{
	$this->em = $em;
	$this->articles = $em->getDao(App\Articles::class);
	// $this->articles = $em->getDao('App\Articles'); # nebo pro PHP < 5.5
}
Jiří Nápravník
Člen | 710
+
0
-

To je pravda, že to takhle možná bude lepší v některých případech, díky

akadlec
Člen | 1326
+
0
-

@Filip Procházka: nechtěl by jsi udělat nějaký best practise příklad na použití kdybydoctrine? Protože mám pocit že ji implementuji asi špatně ;)

Mám vytvořeny entity:

/**
 * @ORM\Entity(repositoryClass="UserDAO")
 * @ORM\Table(name="users")
 */
class User extends \BaseEntity
{
	/**
	 * @ORM\Id
	 * @ORM\Column(type="integer", name="user_id")
	 * @ORM\GeneratedValue
	 */
	protected $id;
	.
	.
	.
}

K entitě mám vytvořenou DAO:

class UserDAO extends \Kdyby\Doctrine\EntityDao
{

}

A nad tím stojí model:

class UserModel extends \EntityModel
{
	/**
	 * @var UserDAO
	 */
	protected $dao;

	public function __construct(UserDAO $dao)
	{
		$this->dao = $dao;
	}

	/**
	 * @param $username
	 * @return object
	 */
	public function getByUsername($username)
	{
		return $this->dao->findOneBy(array('username' => $username));
	}
}

Model pak přidám mezi služby v neonu:

services:
	# MODELS
	account.userModel:
		class: Models\Users\UserModel(@doctrine.dao(Models\Users\User))
		tags: [cms.models]

A v presenteru nebo komponentě si předám model a volám nad ním potřebné metody třeba: getByUsername

Není ta třída modelu tedy už zbytečná? Původně jsem používal repozitáře a toto vzniklo přechodem na kdyby.

Filip Procházka
Moderator | 4668
+
0
-

Jediné co máš špatně, tak že dědíš DAO.

akadlec
Člen | 1326
+
0
-

@Filip Procházka: Takže nepoužívat UserDAO, etc. ale rovnou \Kdyby\Doctrine\EntityDao?

Jiří Nápravník
Člen | 710
+
0
-

Vždyť už ti na to Filip odpovídal před 3 měsíci:

„Hlavní myšlenkou tohoto návrhu je nedědit už dále DAO, protože tím porušuješ SRP“

a o dva příspěvky níže:
„Nelíbí se mi dědit dao jen kvůli autowiringu.“

akadlec
Člen | 1326
+
0
-

aaa sypu si popel na hlavu, věděl jsem že jsem se o tom někde bavil, jen ta paměť už neslouží ;)

Co se mi zatím na to ModelClass nelíbí to řešení kde tam vytvořím nějakou metodu getByNeco() která pak dělá že zavolá dao->getByNeco()

Editoval akadlec (11. 12. 2013 13:06)

Filip Procházka
Moderator | 4668
+
0
-

Na prototypování nebo na malé aplikace můžeš DAO používat rovnou v presenteru.

Ale ano, zdá se to absurdní, jenže když ti aplikace naroste tak to najednou dává smysl, protože tohle jedno volání nebude to jediné co v té metodě budeš mít.

elevate
Člen | 31
+
0
-

ahoj,
skvělá práce!
Mám jen jeden dotaz. Kam dáváte rozšiřující metody pro složitější dotazy do db, když se nemá dědit od DAO? Nějaký samostatný repozitář, nebo rovnou do facade?

Dík.

elevate

akadlec
Člen | 1326
+
+1
-

@elevate: do speciální classy QueryObject:

př.:

class OwnerStatusItemQuery extends \Kdyby\Doctrine\QueryObject
{
	/**
	 * @var \AccountModule\Models\Users\User
	 */
	private $owner;

	/**
	 * @var string
	 */
	private $status;

	/**
	 * @param \AccountModule\Models\Users\User $owner
	 * @param string $status
	 */
	public function __construct(User $owner, $status)
	{
		$this->owner	= $owner;
		$this->status	= $status;
	}

	/**
	 * @param \Kdyby\Persistence\Queryable $dao
	 *
	 * @return \Doctrine\ORM\Query|\Doctrine\ORM\QueryBuilder
	 */
	protected function doCreateQuery(Queryable $dao)
	{
		return $dao->createQueryBuilder()
			->select('s')
			->from('\MarketModule\Models\Items\Item', 's')
			->where('s.owner = :owner')
			->andWhere('s.status = :status')
			->setParameter('owner', $this->owner)
			->setParameter('status', $this->status);
	}
}
elevate
Člen | 31
+
0
-

Aha, to je fajnový :-)

Děláš si na ty queries nějaký továrničky, nebo vytváříš normálně new SomeQuery ?

David Matějka
Moderator | 6445
+
0
-

mrkni sem http://filip-prochazka.com/…o-dava-smysl

ja na QueryObjecty nepouzivam factory (ve smyslu sluzeb). u nejakych slozitejsich query objectu mam akorat nekolik statickych metod, ktere mi vrateji trosku nakonfigurovany QueryObject

class OrderQuery extends QueryObject
{


	public static function createDispatchedOrdersQuery()
	{
		$query = new static;
		$query->setState('dispatched');
		$query->setSorting('newest');

		return $query;
	}
}

a pouziti je pak hezky jednoduchy:

$this->orderDao->fetch(OrderQuery::createDispatchedOrdersQuery());
elevate
Člen | 31
+
0
-

Jo to bude asi ideální :-)

Akorát mi tam zásadně chybí metody getOneOrNullResult, getSingleResult atp. Je tam metoda fetchOne, ale té se nedá nastavit Hydration mode. Chtěl sem si udělat nějakou ArrayHydrationQuery pro získávání default hodnot pro formuláře, ale trošku jsem narazil.

Query si podědim a trošku rozšířim, ale mohlo by to být už „builtin“.

Jinak díky za hinty ;)

elevate
Člen | 31
+
0
-

Btw pokud by ste někdo měl nějaký trait s efektivními metodami typu "hydrateArray a extractArray pro entity, sem sním.


I když jestli by nebylo inteligentnější naučit Form → setDefaults žrát entity, než z entity dolovat pole :-)

Ideas?

Editoval elevate (23. 12. 2013 13:55)

Filip Procházka
Moderator | 4668
+
0
-

@elevate vyzkoušej https://github.com/…octrineforms :)

elevate
Člen | 31
+
0
-

A máme nějakou drobnou dokumentaci kromě tý instalace? :-)

„Kdyby“ se mi strašně líbí, velká poklona pro autory! :) Řeší podle mého největší nedostatek Nette a to kompabilitu s pořádným databázovým frameworkem, ale chtělo by to pár „use-cases“. Vždy potřebuju vidět, co získám, když strávím čas instalací a zkoumáním api.

elevate

Filip Procházka
Moderator | 4668
+
0
-

Kdyby/Doctrine má být především integrace Doctrine (byť některé věci usnadňuje). Tedy mělo by stačit rozumět Doctrine a umět připojit extension do Nette (jeden řádek v configu) a jednoduše používat :)

Ale souhlasím, chce to lepší dokumentaci. Jenže ona se ta dokumentace sama nenapíše :)


Protože formulářový mapper je zatím jen betaverze, tak existují pouze testy

akadlec
Člen | 1326
+
0
-

Já jsem ten tvůj formulářový mapper zkusil a jako jo, funguje, ale jen na některé entity, resp. entity co mají složitější závislosti vyhazují výjimky.

jedelex
Člen | 16
+
0
-

Zdravim,

presiel som z Nette 2.0.13 na Nette 2.1.0 . Doctrine 2 integrujem pomocou Kdyby\Doctrine cez composer.
Predtym upgradom na Nette 2.1.0 som nemal ziadny problem, teraz neviem upravit composer.json tak aby som nedostal nasledovnu hlasku:

Declaration of Kdyby\Annotations\DI\AnnotationsExtension::afterCompile() should be compatible with Nette\DI\CompilerExtension::afterCompile(Nette\PhpGenerator\ClassType $class)

Z hlasky mi je jasne, ze mam nekompatibilne verzie ale neviem korektne upravit composer.json :/

Moj composer.json vyzera nasledovne:

{

	"require": {
		"php": ">= 5.3.0",
		"nette/nette": "~2.1",
		"dg/adminer-custom": "1.*",
		"kdyby/doctrine": "~0.10"
	},
	"require-dev": {
		"nette/tester": "~0.9.5"
	}
}

Viete mi s tym niekto poradit ?

Dik za info:)

akadlec
Člen | 1326
+
0
-

Zkus: „kdyby/doctrine“: "@dev"

David Matějka
Moderator | 6445
+
0
-

@jedelex: viz instalace v doc, pouzij @dev namisto ~0.10

//edit: pozde :)

Editoval matej21 (6. 1. 2014 17:42)

kudlajz
Člen | 70
+
0
-

Muze nekdo poradi zase pro zmenu mne? :)

V composeru mam: „kdyby/doctrine“: "@dev", v configu mam zadefinovany extensions podle github readme, haze mi to Class ‚Kdyby\Annotations\DI\AnnotationsExtension‘ not found.

Pokud pridam „kdyby/annotations“: "@dev", tak mi to haze Interface ‚Doctrine\ORM\Mapping\EntityListenerResolver‘ not found.

Pouzivam Nette 2.1.0 stable.

Dekuji :)

David Matějka
Moderator | 6445
+
0
-

@kudlajz: mas starou verzi doctrine, pouzij 2.4

jedelex
Člen | 16
+
0
-

@matej21:myslel som ze Nette 2.1.0 je stable verzia a „kdyby/doctrine“: „@dev“ sa pouziva pre develop verziu alebo som nieco este prehliadol ?

David Matějka
Moderator | 6445
+
0
-

@jedelex: je to develop verze kdyby/doctrine, ale neboj se ji pouzit, je stabilni :)

Filip Procházka
Moderator | 4668
+
0
-

Pro verzi 2.1 ještě nemám otagovaný release, ale @dev by měla fungovat.

kudlajz
Člen | 70
+
0
-

Muze mi nekdo poradit, jak se s timto doplnkem pracuje? Cetl jsem readme, ale nejak jsem to z toho nepochopil.

App/Users ma byt co, repository, ktera dedi odkud?
App/User je entita, ktera dedi z BaseEntity, jake se pouzivaji anotace v entitach? Standardni Doctrine mi nefunguji.

Hledal jsem kde to slo, ale nic jsem nenasel.

David Matějka
Moderator | 6445
+
0
-

@kudlajz: Users – servisni trida/fasada, ktera od niceho nededi (nebo treba jen od nette\object) a jako zavislost pozaduje konkretni DAO, nad kterym pracuje

anotace fungujou, nebo specifikuj, co to je „nefunguji“

kudlajz
Člen | 70
+
0
-

Diky, tak napr. u fieldu $id se napr. pouziva /** @id @generatedValue **/ a hazelo mi to exception, ze ty anotace neexistuji, ale kolega mi poradil, ze pred to staci dat ten namespace Doctrine\ORM\Mapping.

Jiří Nápravník
Člen | 710
+
0
-

Ještě lepší než tam celý ten namespace vypisovat je lepší udělat.

Na záčátek nad třídu dát:

use Doctrine\ORM\Mapping as ORM;

a pak budeš psát jen:

/** @ORM\Id  */

apod

akadlec
Člen | 1326
+
0
-

Dá se u této extension použít následující kód ?

use Doctrine\Common\Collections\Criteria;

$group          = $entityManager->find('Group', $groupId);
$userCollection = $group->getUsers();

$criteria = Criteria::create()
    ->where(Criteria::expr()->eq("birthday", "1982-02-17"))
    ->orderBy(array("username" => Criteria::ASC))
    ->setFirstResult(0)
    ->setMaxResults(20)
;

$birthdayUsers = $userCollection->matching($criteria);

Snažím se jej použít na vyfiltrování podentit a nějak mě to nechce sežrat

Call to undefined method Kdyby\GeneratedProxy\__CG__\XXX\Entita::matching

Čeho se snažím dosáhnout je načtení Entity se kterou pracuji a ta pomocí vazeb obsahuje i další prvky, ale nechci použít všechny v té jedné kolekci ale chci na ně aplikovat filtr.

Editoval akadlec (7. 1. 2014 0:10)

pepakriz
Člen | 246
+
0
-

akadlec: matching se nepoužívá u entit, ale u kolekcí – tedy nad objektem ArrayCollection.

Filip Procházka
Moderator | 4668
+
0
-

@kudlajz Tak si tu dokumentaci přečti ještě jednou, ale až potom, co si přečteš dokumentaci Doctrine.

Velice důležitý je ten klíč metadata, který říká v jakém namespacu jsou tvé entity a ve které složce.

Kdyby jsi četl dokumentaci Doctrine tak bys veděl, že entita nemusí dědit od ničeho. Ovšem Kdyby/Doctrine obsahuje utility classu BaseEntity, kterou podědit můžeš a budeš díky ní mít automatické gettery a settery (je rozšířením Nette\Object pro speficika entity).


akadlec napsal(a):

Dá se u této extension použít následující kód ?

Ano dá, ale musíš jej volat nad kolekcí (toMany v entitě) nikoliv nad entitou. Máš to napsané i v té chybové zprávě, entita neobsahuje metodu matching, tu obsahuje kolekce.

akadlec
Člen | 1326
+
0
-

pepakriz, Filip Procházka ale j8 to používám na kolekci. Vytáhnu si entitu Group s konkrétním ID a tato entita obsahuje kolekci OneToMany users a nad ní zavolám matching nebo filter a když se to zpracovává tak nevím proč ale volá se to nad entiou Group proto ta chyba.