Bezbolestná integrace Doctrine 2 ORM do Nette – Kdyby/Doctrine
- akadlec
- Člen | 1326
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
@akadlec Viz https://forum.nette.org/…yby-doctrine#…
Začínám si říkat, že bych tu save metodu měl deprecatnout :)
- akadlec
- Člen | 1326
@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
@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()
.
- akadlec
- Člen | 1326
@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
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
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
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)
- Filip Procházka
- Moderator | 4668
@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
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.
- Jiří Nápravník
- Člen | 710
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?
- Jiří Nápravník
- Člen | 710
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
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
}
- akadlec
- Člen | 1326
@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.
- Jiří Nápravník
- Člen | 710
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.“
- Filip Procházka
- Moderator | 4668
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.
- akadlec
- Člen | 1326
@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);
}
}
- David Matějka
- Moderator | 6445
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
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
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
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
- jedelex
- Člen | 16
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:)
- David Matějka
- Moderator | 6445
@jedelex: viz instalace v doc,
pouzij @dev namisto ~0.10
//edit: pozde :)
Editoval matej21 (6. 1. 2014 17:42)
- kudlajz
- Člen | 70
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
@jedelex: je to develop verze kdyby/doctrine, ale neboj se ji pouzit, je stabilni :)
- Filip Procházka
- Moderator | 4668
Pro verzi 2.1 ještě nemám otagovaný release, ale @dev
by
měla fungovat.
- kudlajz
- Člen | 70
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
@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“
- Jiří Nápravník
- Člen | 710
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
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)
- Filip Procházka
- Moderator | 4668
@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.