Modul ve více projektech – entita (doctrine)

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

Zdravím,
mám několik projektu u nichž jsou některé moduly totožné. Proto bude asi nejlepší dát je přes composer. Řeším ale i zda by nebylo nejlepší dát tam i ty moduly co se nějak liší v nějakém drobném detailu.

Jak byste řešili, když máte třeba v jednom projektu o nějaký atribut u entity (používám doctrine) více. Stejně tak potřebuji k tomu atributu udělat ukládání.

Ukládání mě napadá, že by bylo relativně lehké, v extension bych definoval obsluhující třídu a tu bych si podstrčil. Nebo vás to napadá nějak lépe?

Problém vidím hlavně v té entitě, jak byste to řešili? Bylo by ideální něco ve smyslu, „mám tenhle projekt přidje tenhle atribut, jinde ne.“ Nabízí se i nechat tam ten atribut a bude prázdný u těch projektů, co ho nevyužijí, ale to mi nepřijde úplně ideální.

akadlec
Člen | 1326
+
0
-

No s těmi entitami nevim nevim, služby dokážeš přetížit, ale entity jsou složitější. Leda přetížit celý modul a v něm ty entity.

Něco takového taky řeším. Konkrétně s uživateli. Mám modul kde se spravují uživatelé se základními daty a pak druhý modul kde se spravují kontakty jednotlivých uživatelů. A v entitě uživatele mě právě chybí atribut určující relaci uživatel-kontakty :/

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

JJ, taky jsem pri prohledavani fora narazil, ze resis podobny veci. Me nejde jen o relace, ale v nekterych pripadech i jen o „primitivni“ atribut.

Napadly me zatím dvě věci, ale nelíbí se mi moc ani jedna

  1. V Extension předat ty konkrétní entity zvenčí.
  2. možná by nějak šlo zneužít doctrine eventy listenery, tak nějak funguje třeba gedmo apod.
David Matějka
Moderator | 6445
+
0
-

Resil bych to podedenim ty entity a pridanim fieldu. Je tam ale par problemu.

Vetsinu problemu by melo vyresit targetEntityMappings, zbyva jeden problem a to je, ze bude pravdepobodne nutne z te originalni entity udelat mapped superclass, coz by slo taky pomoci loadClassMetadata listeneru. Ale nektere veci v ClassMetadataFactory jsou zavisle na tom, jestli trida je entita nebo mapped superclass. takze to mozna budes muset nejak premapovat

Tomáš Votruba
Moderator | 1114
+
0
-

Řešil bych to stejně jako @DavidMatějka . Pokud by ses do toho pouštěl, velkou inspiraci můžeš najít u projektu Sylius, který to řeší po svém.

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

Díky, nastuduji a odzkouším

akadlec
Člen | 1326
+
0
-

Zajímavé návrhy. A dá se nějak řešit okamžik kdy tu entitu rozšuřují dva a více modulu? Řekněme že máme modul Uživatelé, kde je entita Uzivatel a tuto entitu rozšiřují další dva moduly: adresáš a eshop. Modul adresáře by rád přidal parametry pro adresy apod a eshop zase objednávky/faktury apod. Je to realizovatelné?

David Matějka
Moderator | 6445
+
0
-

Nejde dynamicky pridavat fieldy/asociace. Doctrina vyuziva reflexi a pro ten field/asociaci musi existovat property, coz v runtime neudelas.
Takze me napadaji dve reseni:

  1. ty rozsirujici parametry v adresar a eshop si das do traity a v aplikaci pak vytvoris entitu, kam das pozadovane traity
  2. user v adresar a eshop nebudou nijak rozsirovat tu puvodni entitu, ale budou to samotne entity s asociaci na tu orig.
akadlec
Člen | 1326
+
0
-
  1. To by přineslo závislosti které by byly nežádoucí.
  2. To je řešení které aktuálně mám. No funguje, ale není to úplně ono no :(
akadlec
Člen | 1326
+
0
-

Takže jsem něco málo zkoušel a asociace jdou docela jendoduše udělat dynamicky pomocí eventů, má to akorát jeden háček, v entitě musí být definována property oné asociace :( Takže asi víc než ty dvě zmíněné možnosti nelze vymyslet.

Tomáš Votruba
Moderator | 1114
+
0
-

@akadlec Přesně tak to řeší DoctrineBehaviors, traity + subscriber.

Tedy forma decoratoru.

akadlec
Člen | 1326
+
0
-

@TomášVotruba jo traity sou ok, ale pak už by ty moduly nebyly na sobě nezávislé. Já chtěl nějak vyřešit že bodou třeba 3 moduly co jsem zmínil a bude moct fungovat libovolná kombinace, třeba uživatele + adresář, nebo uživatelé + adresář + eshop a nebo se použije jiný modul adresáře co bude fungovat jinak. Ideou bylo je mít nezávislé na sobě a třeba pomocí těch událostí aby se vzájemně dokázaly propojit.

Tomáš Votruba
Moderator | 1114
+
0
-

@akadlec To mi přijde jako jiný případ než řešený na začátku. Pokud potřebuješ libovolné kombinace určitých prvků, tak na to používáme value objects, v Doctrine embeddables.

akadlec
Člen | 1326
+
0
-

@TomášVotruba ee to myslím že není ono. Doctrine embeddables pokud jsem dobře pochopil udělají to že si entitu rozšíříš o další property. Já to asi zkusím těma traitama a vymyslet nějaký generátor co by mě to spojil.

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

Ok, tak jsem se k tomu dostal a zkouším zatím targetEntityMappings, využívám z Kdyby\Doctrine ITargetEntityProvider, kde to nadefinuji, co chci nahradit a v pohodě.

Problém je ale v tom, že jsem si myslel, že si při zavolání generovaní schematu z konzole doctrine sama podívá, co nahrazuje ten interface a vygeneruje potřebnou tabulku. Ale to neudělá… Uniká mi něco? Nebo musím ještě standardně nadefinovat, že chci aby se vygenerovala tato entita? Jdde případně nějak definovat, že chci dát mapování jenom těhle konkrétních entit, nebo musím definovat celou složku, ať si pročte doctrine?

Mám to plánované tak, že si nastavím, zda jde o jendodušší nebo složitější projekt. A podle toho se potom rozhoduji, zda interface bude mít tuhle nebo tuhle entitu…

Editoval Jiří Nápravník (7. 11. 2015 14:33)

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

Ok, tak jsem došel k tomuto.

Problém:
Chci udělat databázi štítků. Jednou to bude jen název štítku, podruhé tam budou i nějaké další atributy. Když pak ten modul nastavím určím si, zda chci tohle nebo tohle.

Jak to zatím řeším:

interface ITag
{

}

/**
 * @ORM\MappedSuperclass
 */
class BaseTag extends BaseEntity implements ITag
{

	/**
	 * @ORM\Id
	 * @ORM\GeneratedValue
	 * @ORM\Column(type = "integer")
	 */
	protected $id;

	/**
	 * @ORM\Column(type = "string", unique = TRUE)
	 */
	protected $name;
}

/**
 * @ORM\Entity
 * @ORM\Table(name = "tag")
 */
class StandardTag extends BaseTag
{

}

/**
 * @ORM\Entity
 * @ORM\Table(name = "tag")
 */
class ExtendedTag extends BaseTag
{
	//nejake pridavne atrbity
}

v extension pak něco takového:

	public function getEntityMappings()
	{
		if(..){ //podminka, podle toho jako entitu tam chci soupnout
			return [
				'TagModule' => __DIR__ . '/../Model/TargetEntities/Extended/',
			];
		} else {
			return [
				'TagModule' => __DIR__ . '/../Model/TargetEntities/Standard/',
			];
		}
	}

	public function getTargetEntityMappings()
	{
		//zase if, zda chci slozity nebo lehci, stejny jako vyse
		return [
			'TagModule\Model\Entities\ITag' => 'TagModule\Model\TargetEntities\Standard\Tag',
		];
	}

Řešili byste to takto, nebo jinak? Asi běžnější proces je, že si tu konkrétní entitu nastavím v jiném modulu. Ale mě přijde lepší v tomhle případě, když jich bude nějaký konečný počet, mít to vše pod jedním modulem a nastavím si jen kterou použiju…

Druhá věc pak směřuje jak byste řešili vytvoření nové entity, abych si nějak vytáhl tu entity, co je resolved. Našel jsem tohle, ale příliš mi nesedí, není něco lepšího?

	$entityName = $this->tagRepository->getClassName();
	$tag = new $entityName();
Oli
Člen | 1215
+
0
-

@akadlec myslím, že mám řešení co hledáš (Možná by to mohlo pomoct i @JiříNápravník). Ptal jsem se na to na kdyby help, ale ve skutečnosti jsem to potřeboval až teď. Funguje to tak, že máš entitu, která implementuje interface a ty si jako závislost předáš tu interface. V neonu si potom namapuješ pro konkrétní projekt jestli se má s modulem propojit nebo ne…

Mám to takhle a zatím mě to funguje:

doctrine:
	targetEntityMappings:
		Entity\IArticleGallery: Entity\FakeGallery
// Article

/**
 * @ORM\ManyToOne(targetEntity="Entity\IArticleGallery", inversedBy="articles")
 * @ORM\JoinColumn(name="gallery_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
 */
protected $gallery;
/**
 * @ORM\Entity(repositoryClass="FakeGalleryRepository")
 * @ORM\Table(name="galleries")
 */
class FakeGallery extends BaseEntity implements Entity\IArticleGallery
{

	use Identifier;

	public function getMainPhoto()
	{
		return NULL;
	}

}
fizzy
Backer | 49
+
0
-

ako potom niekde v module vytvorit novu entitu typu Entity\IArticleGallery?

Oli
Člen | 1215
+
0
-

Vytvoříš to co potřebuješ. V projektu nechceš používat Gallery. Tak vytvoříš a předáš FakeGallery. Chceš jinou galerii? Předáš AnotherGallery. Možná funguje i to, že když IArticleGallery implementuje jen jedna třída, tak stačí vytvořit tu interface. Nezkoušel jsem to…

fizzy
Backer | 49
+
0
-

A ked niekde v inom projekte budem potrebovat specificku verziu FakeGalleryEx tak to budem prepisovat vsade? :D

ako prve ma napadlo nieco taketo:

$className = $entityManager->getRepository(Entity\IArticleGallery::class)->getClassName();
$article = new $className();

napriklad spominany Sylius to riesi sposobom:

class EntityRepository extends BaseEntityRepository implements RepositoryInterface
{
    public function createNew()
    {
        $className = $this->getClassName();
        return new $className();
    }

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

TO jsem se ptal o uroven vyse, jak to nejlepe resit. A zatim to mam jako ty ->getRepository()->getClassName() jen se mi to moc nelibi, i kdyz vzhledem k tomu, ze too ostatne podobne resi i ten sylius, tak asi nic lepsiho nebude:)

Zax
Člen | 370
+
0
-

Já si obecně na entity (nejen pro Doctrinu) rád píšu továrničky, defaultně využívám generované z Nette, protože se občas může stát, že je potřeba po vytvoření každou entitu nějak nakonfigurovat trochu víc, než na co stačí defaultní hodnoty, a pak je člověk rád když nemusí hledat kde všude v kódu se používá new Entity.

V tomhle případě bych si napsal jen obecnou továrničku vracející interface, kterou bych pak ručně implementoval pro každou konkrétní entitu.

interface IArticleGalleryFactory {
  /** @return IArticleGallery */
  function create();
}
class FakeGalleryFactory implements IArticleGalleryFactory {

  function create() {
    return new FakeGallery;
  }

}

Sice to pak bude v configu na dva řádky a ne jeden a je potřeba o jednu závislost víc v kódu (je to špatně? ono je možná fajn vidět hned v konstruktoru, že tahle třída někde vyrábí novou entitu…), ale to snad není moc problém ;-)

David Matějka
Moderator | 6445
+
+1
-

@Zax neni nutne je implementovat:

services:
	-
		implement: IArticleGalleryFactory
		class: FakeGallery
Zax
Člen | 370
+
0
-

@DavidMatějka tyjo díky! Tohle mě nějak ani nenapadlo zkusit :-D

EDIT: ale ještě mě tak napadá, že zrovna v týhle situaci je stejně lepší si tu implementaci napsat, protože je to součást modelu. Mimo Nette to nebude fungovat.

Editoval Zax (15. 11. 2015 13:44)

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

Tak jsem vyvíjel a asi před třemi dny nasadil do provozu a začali těžké výkonostní problémy. Web padal, přehlcoval a nakonec jsme zjistil, že problém je právě v EntityResolveru. Musel jsem se vrátit k pevně definovaným třídám, a jak použiju interfaces, tak mluvíme o vykonání skriptech často v sekundách, ted jsme na nějakých 0.2s. Nasazoval jste to nekdo v reálné, celkem navštěvované aplikaci? Mluvíme asi o 8000 lidí denně, chtěl bych to nasadit i v aplikaci, která má i 100tisic lidí, a tam to nemá šanci asi vůbec.

Když jsem googlil, tak jsem nic nenašel. Nevím tedy jestli je to běžné chování nebo je problém v Doctrine, Kdyby\Doctrine, či Kdyby\Events… Četl jsem jen někde, že je výkonostní problém, dkyž se používá dědění v doctrine, napadá mě jestli nemůže být stejný problém i v případě interface. Je pravda, že jsem jej používalna hodně místech v anotacích… Nějaké tipy?

enumag
Člen | 2118
+
0
-

@JiříNápravník Jsem trochu zmaten o čem přesně mluvíš. Co je EntityResolver a jak funguje?

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

@enumag sorry, ten nazev si asi nikdy nezapamtuju poradne:-) ResolveTargetEntityListener mam na mysli. Přijde mi divné, že při každém refreshi se vystřeluje event: „Doctrine\ORM\Event::loadClassMetadata“ myslel jsem, že se to při kompilaci popáruje a pak už doctrinu moc nezajímá, co za interface tam je. Ale asi ne

enumag
Člen | 2118
+
0
-

@JiříNápravník Ten event by se myslím neměl vystřelit pokaždé. Ovšem co jsem teď zkoušel v mé aplikaci tak pro některé entity mi vyletí vždy a pro některé nikdy (pokud nesmažu cache). Tzn. možná je kešování nějak zabugované… Pěkně děkuju, nasadils mi brouka do hlavy a teď abych strávil den zjišťováním jak funguje keš Doctrine. :-D

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

@enumag Já tam mám v podstatě tři interface a pak nasledně třídy. A pro každou mi vylétává ten event. A když mrknu do cache, tak přitom tam už jsou ty targetEntity v pořádku nastavené, či-li ten event vylétává zbytečně dle mě, protože v cache to je již uložené. Jenže já nějak nejsem schopný vykoumat, kde vylétává. Kdybys vyzjistil něco, dej pls vědět, i bych si to natvrdo opravil dočasně ve vendoru. Ani nevím mco jestli hledat chybu v Doctrine nebo v Kdyby. Osobně bych tipoval spíše Kdyby, když na tomhle běží i ten Sylius (viz výše), ale zase v Kdyby není skoro nic k RTEL

enumag
Člen | 2118
+
0
-

@JiříNápravník Pokud jsem to správně pochopil tak ten event vyletí když cache driver vrátí false. Následně by se ty chybějící data měly do cache uložit což se buď nestane anebo stane ale uloží se z nějakého důvodu jinam než kde se pak hledají… A spíše bych řekl že to nebude mít co do činění s RTEL, ale půjde o něco obecnějšího – respektive pokud ne tak to nenajdu protože RTEL nepoužívám.

enumag
Člen | 2118
+
0
-

@JiříNápravník Netuším zda to je i tvůj problém, ale v mém případě jde alespoň částečně o tohle. Zítra zkusím nějakej jinej cache driver než filesystem abych zjistil jestli je to jediný problém nebo ne.

EDIT: Ok, můj problém jsem vyřešil a zatím se zdá, že to byl jediný problém. Tvůj problém ale bude asi jinde.

Editoval enumag (19. 12. 2015 0:50)

akadlec
Člen | 1326
+
0
-

@DavidMatějka: hele teď jsem se k tomu zase dostal. Pokud jsem pochopil správně tvou myšlenu v bodu 2, tak by např. v adresářovém modulu, byla entita User která by dělala propojení mezi moduly je tak? Taková asociační tabulka.

class AddressBookUser
{
	// Vazba na entitu User z modulu Users
	private $user;

	// Vazba na entitu AddressBook z modulu AddressBook
	private $addressBook;
}

Nemohl bych pak samozřejmě udělat $userModuleEntity->getAddressBook() ale musel bych si najít AddressBookUsera a až na něm to volat. Což by snad ani nevadilo.

Dá se to tedy považovat za čisté řešení? Čeho se snažím dosáhnout je odstranění závislostí mezi moduly, minimálně obousměrné, tedy UserModul nebude nic vědět o AddressBookModul a bude mu jedno zda bude instalován nebo nebude, ale AddressBookModul bude mít závislost na UserModul, protože bude chtít rozšířit jeho funkcionalitu, resp. vzít si jeho data pro práci.

@Oli: dík za řešení, ale myslím že toto můj problém neřeší, pokud bych chtěl jeden modul rozšířit jen jedním modulem tak by to asi bylo dostačující, ale já právě potřebuju jeden rozšířit vícero moduly.

Př: Mám modul uživatel. Ten má entitu Uživatel a ta má v sobě nějaké základní data co tomu modulu stačí a pro běh úplně easy verze aplikace je dostačující. Jenže pak tam přibude adresář a eshop, takže adresář bude chtít rozšířit entitu uživatele od záznamy adres a eshop bude zase chtít rozšířit uživatele o jeho faktury a objednávky. Takže mě napadlo buď dělat nějaký PHPWriter co veme konfiguraci co by daly dohromady jednotlivé moduly a vygeneruje třídu která bude extendovat uživatele a bude implementovat traity daných modulů, něco jako generuje proxy doctrina. Jenže pak by byl problém že by to bylo generované a IDEčko by se z toho mohlo posrat :D ale asi by to fungovalo. A nebo teda řešení jak jsem popsal vyše, resp. jak uvedl David Matějka

A @JiříNápravník se omlouvám že mu tu okupuju téma které je trošku jiné :D