Doctrine rekurze nefunkcni matching
- kleinpetr
- Člen | 480
Zdravim,
narazil na takovy zvlastni problem, ktery bych si rad ujasnil.
Mam tabulky
country
locality (id, id_parent_locality, name)
city (id, id_locality)
a vysledek je treba v cr
cz(country) -> Ustecky kraj(lokalita) -> Okres teplice(lokaita) -> teplice(mesto)
A ted v localityEntity mam
/**
* @var CountryEntity
* @ORM\ManyToOne(targetEntity="CountryEntity", inversedBy="localities")
* @ORM\JoinColumn(name="id_country", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
private $country;
/**
* @var LocalityEntity
* @ORM\ManyToOne(targetEntity="LocalityEntity", inversedBy="sub_localities")
* @ORM\JoinColumn(name="id_locality", referencedColumnName="id", onDelete="CASCADE", nullable=true)
*/
public $parent_locality;
/**
* @var Collection
* @ORM\OneToMany(targetEntity="LocalityEntity", mappedBy="parent_locality")
*/
private $sub_localities;
/**
* @var Collection
* @ORM\OneToMany(targetEntity="CityEntity", mappedBy="locality", cascade={"persist"})
*/
private $cities;
a v CountryEntity mam
/**
* @var Collection
* @ORM\OneToMany(targetEntity="LocalityEntity", mappedBy="country")
*/
private $localities;
Ted mi slo o to udelat si v CountryEntity metodu, ktera mi bude vracet nejake pocty. Takze napr. metodu, ktera vrati pocet lokalit v dane zemi, ktere uz nemaji zadny parent a pote pocet vsech mest v dane zemi pro vsechny lokality. Tak jsem napsal nasledujici v CountryEntity
/**
* @return array
*/
public function getLocalities($all = false)
{
if ($all) {
return $this->localities->toArray();
}
$criteria = Criteria::create()->where(Criteria::expr()->isNull('parent_locality'));
return $this->localities->matching($criteria)->toArray();
}
public function getCitiesCount()
{
$count = 0;
/** @var LocalityEntity $localityEntity */
foreach ($this->getLocalities(true) as $localityEntity) {
$count += count($localityEntity->getCities());
}
return $count;
}
Pri tomhle pouziti, vznikne problem kdyz v template iteruji zeme a nad kazdou si zavolam
{foreach $countries as $item}
{foreach $item->getLocalities() as $locality}
.....
.....
vyhodi
Cannot access private property App\Entity\LocalityEntity::$parent_locality
Co jsem pochopil, tak on si v matching hleda getter, a muj getter
je getParent()
Takze matching upravim na
$criteria = Criteria::create()->where(Criteria::expr()->isNull('parent'));
return $this->localities->matching($criteria)->toArray();
V tu chvily funguje spravne, ale jakmile si nactu detail nejake zeme a zavolam si v sablone
{foreach $country->getLocalities() as $item}
Vyhodi Unrecognized field: parent
Reseni jsou 2
- budto nastavit $parent_locality jako public
- prejmenovat getter na getParentLocality() a v matching nechat ‚parent_locality‘
Prejmenovat getter je to nejmensi, jen mi nejde do hlavy, co je to za chovani, to musi byt getter striktne pojmenovany jako nazev promenne ? Kdybych tam getter vubec nemel, tak to padne taky.
Diky za vysvetleni.
Editoval kleinpetr (7. 2. 2018 21:59)
- Martk
- Člen | 661
Podle mě to funguje takhle:
První případ Kolekce již byla načtena z databáze, nyní se bude filtrovat na „úrovni PHP“, takže se budou procházet všechny entity a porovnávat hodnoty, k tomu potřebuješ getter nebo public proměnnou.
Druhý případ Kolekce není načtena z databáze, takže
pro optimalizaci se vytvoří SQL pro vytažení z databáze. Doctrine
potřebuje vytáhnout z metadat informace o položce „parent“ (aby mohl
filtrovat podle reálného názvu sloupce), ale ta neexistuje, proto hláška
Unrecognized field: parent
, kterou generuje výjimka ze třídy
Metadata.
EDIT: V dokumentaci jsem našel tohle
The Criteria has a limited matching language that works both on the SQL and on the PHP collection level. This means you can use collection matching interchangeably, independent of in-memory or sql-backed collections
Editoval Martk (8. 2. 2018 9:38)
- kleinpetr
- Člen | 480
@Martk aha, diky za odpoved. Slo mi o to, ze kdyby bylo jasne dano, ze se to bere na urovni php, tak bych naopak mohl vytvorit nejake custom gettery, a mel bych mnohem rozmanitejsi filtraci. Nicmene takto jsem omezen na to vzdy dat stejny nazev getteru jako promenne, prijde mi to lepsi nez davat public promennou.
Kazdopadne s doctrinou teprve zacinam a tak me napadlo, nedal by se nejak optimalizovat ten getter na citiesCount ? Abych nemusel jednotlive prochazet lokality a pripocitavat jejich mesta.
Diky
- kleinpetr
- Člen | 480
@Martk Myslel jsem si to.. Premyslel jsem nad tim uz vicekrat, ze pokud bych nemel brat entitu pouze jako obalku na data, ale mel bych v ni resit i nejake operace, tak bych tam musel nacpat entity managera nebo spis nejake repository.. Jak to resis ty ?
Editoval kleinpetr (8. 2. 2018 11:15)
- Martk
- Člen | 661
Používám jen entitu jako obálku na data, složitější dotazy / optimalizaci řeším přes repozitář. Napadají mě 2 způsoby jak by se to dalo ještě udělat.
První je použít navrhový vzor strategie, předáš entitu a entity managera do nové třídy a změníš jen algoritmus pro výpočet metody getCitiesCount (nezkoušel jsem to v praxi, je to jen teoretická úvaha a není to nejčistší řešení, protože to porušuje dry)
Druhá je použít DTO, jednoduché třídy na přepravu dat s public proměnnýma. Někdo to používá a diskuze o tom tady určitě budou.
Raději bych počkal na radu od zkušenějších…
- kleinpetr
- Člen | 480
@Martk Jeste kdyz jsme u te Doctrine, ted jsem zjistil, ze kdyz si udelam DQL dotaz s joinem a mam napriklad knihu a autora, dejme tomu, ze hledam nazev knihy a k ni nazev autora. Pokud to udelam takhle:
->createQueryBuilder()
->select('b')
->from(BookEntity::class, 'b')
->leftJoin('b.author', 'a')
->where('b.name LIKE :name')
->setParameter('name', $name)
->getQuery()
->getResult();
Tak kdyz si pak dumpnu $book->getAuthor(), tak mi vrati instanci
Kdyby/GeneratedProxy/__CG__...... , ale pokud udelam pridam do
->select('b, a')
tak uz to vraci klasicky AuthorEntity. Jedna se
lazyloading ?
Mohl bys me navest vcem je presne rozdil, popripade kde bych si na to mel dat pozor a mohlo by to delat neplechu. Diky
Editoval kleinpetr (10. 2. 2018 20:51)
- Mysteria
- Člen | 797
Přesně tak. Pokud uvedeš do SELECTu, že chceš knihu i autora, tak se udělá jeden JOIN a vytáhne se všechno dohromady. Pokud dáš že chceš jenom knihu, tak autor se ti vytáhne, až když u nějaké knihy zavoláš $book->getAuthor().
Pozor je potřeba dát na to, že když použiješ druhou variantu, tak v případě, že ji nijak nezoptimalizuješ, tak když budeš procházet foreachem třeba 500 knih, a v něm budeš volat $book->getAuthor(), tak budeš mít 1 + 500 dotazů do databáze.
Případně se podívej na: https://tideways.io/…should-avoid
- kleinpetr
- Člen | 480
@Mysteria Diky ! to je presne to co jsem potreboval, je tedy lepsi misto findAll() napsat rovnou querybuilder a vyselectovat vsechny asociace, potom je to otazka jednoho query a myslim, ze ikdyz bych nejakou asociaci ani zrovna v nejakou chvily nepotreboval, tak je to globalne lepsi nez lazyloading.
Diky :)