Nextras\ORM – ORM nad Nextras\Dbal
- goood
- Člen | 26
@hrach máš pravdu, opravil jsem to. Díky
Ještě jedna technická. Zkoumal jsem ještě dokumentaci k dbal a narazil jsem na špatné odkazy na stránce http://nextras.org/…s/2.0/mapper odkazy místo na dbal vedou na orm
Editoval goood (22. 3. 2016 22:51)
- pfilipek
- Člen | 25
Zdravím,
chtěl jsem se zeptat jak to je s Nextras/ORM. Někde jsem se dočetl, že
pracuje nad Nette/Database, ale v praxi mi to s ním nefunguje (je to tedy
možné zprovoznit s NDtb?). Zjistil jsem že to funguje s Nextras/Dbal, ale
vyhazuje mi to vyjímku Unknown or incorrect time zone:
‚Europe/Prague‘. V tomto vlákně jsem se dočetl, že se to má
řešit importem názvů časových pásem do databáze, ale na produkci to
bohužel nemůžeme ovlivni, jelikož máme externí správu databázového
serveru. Lze to řešit i nějak jinak?
- Felix
- Nette Core | 1245
pfilipek napsal(a):
Zdravím,
chtěl jsem se zeptat jak to je s Nextras/ORM. Někde jsem se dočetl, že pracuje nad Nette/Database, ale v praxi mi to s ním nefunguje (je to tedy možné zprovoznit s NDtb?). Zjistil jsem že to funguje s Nextras/Dbal, ale vyhazuje mi to vyjímku Unknown or incorrect time zone: ‚Europe/Prague‘. V tomto vlákně jsem se dočetl, že se to má řešit importem názvů časových pásem do databáze, ale na produkci to bohužel nemůžeme ovlivni, jelikož máme externí správu databázového serveru. Lze to řešit i nějak jinak?
Jde to vyresit napevno pres konfigurace.
applicationTz: time zone for returned DateTime objects
connectionTz: time zone for connection
simpleStorageTz: time zone for simple time stamp type
Problematika casovy zon a dalsich je popsana zde: https://nextras.org/…2.0/datetime
Muzes mrknout na Componette, kde je vyresene napevno: https://github.com/…xt/dbal.neon#L10
- hrach
- Člen | 1838
@pfilipek Jinak idealne nastavit auto-offset, a jeste idealni si naimportovat jmenne zony. https://nextras.org/…ysql-support
- pfilipek
- Člen | 25
Zdravím, chtěl jsem se zeptat na to když mám u entity property například @property array $test, tak jestli ORM umí a případně jak ukládat do databáze (např pomocí serializace) a při dotázání z DB zase vrátit pole. Pokud ne tak posílám řešení které mě zatím funguje:
<?php
namespace app\model\orm\entities;
use pf\datatypes\JSON;
/**
* Třída TestEntity.
*
* @property JSON $test
*/
class TestEntity extends Entity
{
/**
* Nastaví test property na objekt JSON.
*
* @param mixed $data
*/
protected function setterUserSettings($data) {
if (!is_a($data, JSON::class)) {
$data = JSON::create($data);
}
return $data;
}
}
?>
objekt JSON je třída, která je potomkem ArrayObject, takže se s ní pracuje jako s polem a má právě magickou metodu __toString(), která zajistí že při ukládání do databáze se pole uloží v JSONu a při načtení z DB se z něj stane opět JSON object.
Více viz. zdrojový kód:
<?php
namespace prosys\datatypes;
use ArrayObject;
use Tracy\Debugger;
/**
* Objekt reprezentujici datovy typ JSON.
*/
class JSON extends ArrayObject implements \JsonSerializable{
private $_json;
public function __construct($json = NULL, $flags = 0, $iterator_class = 'ArrayIterator') {
if (!is_array($json)) {
$json = ((($decoded = @json_decode($json, TRUE))) ? $decoded : array());
}
$this->_json = (($json) ? $json : array());
parent::__construct($json, $flags, $iterator_class);
}
public function jsonSerialize() {
return $this->_json;
}
public function offsetExists($offset) {
return array_key_exists($offset, $this->_json);
}
public function offsetGet($offset) {
return $this->_json[$offset];
}
public function offsetSet($offset, $value) {
$this->_json[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->_json[$offset]);
}
public function append($value) {
$this->_json[] = $value;
}
public function count($mode = 'COUNT_NORMAL') {
return count($this->_json, $mode);
}
public function getIterator() {
return new \ArrayIterator($this->_json);
}
public function __toString() {
return json_encode($this->_json, JSON_PRETTY_PRINT);
}
public static function create($json) {
return new static($json);
}
}
?>
Editoval pfilipek (7. 5. 2016 14:53)
- Felix
- Nette Core | 1245
pfilipek napsal(a):
Zdravím, chtěl jsem se zeptat na to když mám u entity property například @property array $test, tak jestli ORM umí a případně jak ukládat do databáze (např pomocí serializace) a při dotázání z DB zase vrátit pole. Pokud ne tak posílám řešení které mě zatím funguje:
Mrkni do dokumentace: https://nextras.org/…/conventions#…
Mohlo by se ti to hodit, presne na co potrebujes.
Pripadne muzes mrknout jeste na tohle rozsireni, je tam par zajimavych feature.
https://github.com/…xtras-ormext
Jenom takova rada, priste zkus vkladat zdrojaky bez tech phpDocu, usetrime tim hodne mista. Treba na mobilu to skoro neslo precist. xD
- pfilipek
- Člen | 25
pfilipek napsal(a):
@Felix: Děkuji za radu. Je to funkční.
Ještě jsem se chtěl zeptat, zda se nedá náhodou řešit přes config nastavení prefixu tabulek nebo jestli to někdo neřešil nějakým způsobem. Díky za každou radu ;)
Zdravím, tak jsem implementoval použití prefixu tabulek definovaného v config.neon. V první řadě jsem si vytvořil MyMapper, který dědí od \Nextras\Orm\Mapper\Mapper a prefix jsem si ošetřil v něm viz následující ukázka:
<?php
namespace app\orm;
use Nette\Caching\Cache;
use Nextras\Dbal\Connection;
use Nextras\Dbal\InvalidArgumentException;
use Nextras\Orm\Mapper\Mapper;
use pf\common\Functions;
/**
* Hlavní mapper ORM systému.
*/
abstract class MyMapper extends Mapper
{
private $prefix = NULL;
protected $tableName = NULL;
public function __construct(Connection $connection, Cache $cache) {
parent::__construct($connection, $cache);
$this->prefix = Functions::item($connection->getConfig(), 'prefix');
}
/**
* Vrátí název tabulky.
*
* @return string
*/
public function getTableName() {
if (is_null($this->tableName) || !$this->tableName) {
throw new InvalidArgumentException('Musí být zadán název tabulky pro mapper: ' . get_called_class());
}
return $this->prefix . $this->tableName;
}
}
?>
Třída Functions sdružuje užitečné funkce a zde
použitá funkce item jen ošetřuje to, že pokud se požadovaný klíč
v předaném poli nenachází, tak vrátí výchozí hodnotu (v default NULL,
ale jinak se dá předa jako třetí parametr funkce).
V config.neon potom použijete klíč prefix v sekci
dbal.
Editoval pfilipek (7. 5. 2016 14:53)
- Paradiso
- Člen | 101
Ahoj, chtěl bych se zeptat, jaká je best practice, v případě že mám entitu user a potřeboval bych jí považovat na customer a seller
v entitě order chci odkazovat na customer a seller.
oboje se bere ze stejné tabulky
napadlo mě podětit si user a udělat entity customer a seller, ale to mi stejně zařvalo, že mám duplicitní entitu
doufám, že jsem to nějak tak rozumně popsal, děkuji předem za odpověď
- pfilipek
- Člen | 25
Zdravím,
chtěl jsem se zeptat, jak vyřešit tento problém:
Mám entitu Country a CountryTranslation.
Problém je v tom, že potřebuju nyní řadit entitu Country podle property
name z entity CountryTranslation. Entity mám
nadefinované následovně:
Country:
/**
* @property-read int $id {primary}
* @property string $isoCode
* @property string $isoCode2
* @property JSON $addressFormat
* @property bool $mandatoryZipcode
* @property bool $disabled
*
* @property ManyHasOne|CountryTranslation[] $translations {1:m CountryTranslation::$country}
*/
class Country extends TranslationEntity
{
}
CountryTranslation:
/**
* @property-read int $id {primary}
* @property Language $language {m:1 Language, oneSided=TRUE}
* @property Country $country {m:1 Country::$translations}
*/
class CountryTranslation extends MainEntity
{
}
Nevím si už rady jak docílit toho řazení. Někde jsem četl že přes Mapper ale netuším zatím jak na to. Můžete někdo aspoň trochu naznačit řešení? Díky moc.
- Jan Tvrdík
- Nette guru | 2595
Rád bych ti pomohl, ale bohužel nechápu kde a podle čeho to chceš řadit.
potřebuju nyní řadit entitu Country podle property name z entity CountryTranslation.
Přečetl jsem to 10× a pořád to nechápu. Každá země má několik překladů a ty chceš seřadit ty země podle názvu (jednoho?) jejich překladu? Myslíš, že bys mi to mohl vysvětlit na příkladu?
- pfilipek
- Člen | 25
chceš seřadit ty země podle názvu (jednoho?) jejich překladu?
Ahoj,
pochopil jsi to správně, každá země má n překladů podle jazyků v DB.
Takže moje otázka zní když budu mít aktivní jazyk čeština tak potřebuji
řadit podle názvu země pro jazyk čeština a když slovenština, tak zase
řadit podle názvu překladu slovenština.
Díky moc
- Jan Tvrdík
- Nette guru | 2595
Teď jsem to teprve (snad) pochopil – ty chceš řadit názvy zemí podle jejich jména v aktivním jazyce, tj. např. seřadit země podle jejich českého nebo slovenského názvu.
Pak by mělo stačit
$countries = [];
foreach ($countryTranslations->findBy(['language' => $activeLanguage])->orderBy('name') as $ct) {
$countries[] = $ct->country;
}
- pfilipek
- Člen | 25
a jak to zakomponovat do Nextras/Datagrid, protože tam to dělám tak, že poskytuji data jako kolekci entit Country a ty vypisuji v šabloně, kde se odkazuji na překlad dle aktuálního jazyka. Sloupec mám pojmenovaný name, takže když mi přijde do sourceCallbacku order s nazvem sloupce name, tak to musim poskytnout jinym zpusobem ta data než když to dělám obvykle že? A ještě mi teď došlo co když budu chtít řadit podle názvu z překladu (CountryTranslation) a zároveň podle isoCode (Country) to už nebude tak easy že? Mě napadlo to nechat na mapperu a vyřešit to nějak přes join ale zatím v tom tápu a nevím jak a co předat.
- Jan Tvrdík
- Nette guru | 2595
kolekci entit Country
Můžeš to pole obalit objektem ArrayCollection
. Nebo (pokud
bys to potřeboval třeba dynamicky stránkovat, tak) to můžeš samozřejmě
přepsat do SQL a dát do mapperu.
A ještě mi teď došlo co když budu chtít řadit podle názvu z překladu (CountryTranslation) a zároveň podle isoCode (Country)
Nechápu, to ty přeložené názvy nejsou unikátní? Slovo „zároveň“ používáš ve významu „sekundárně“? Mělo by snad fungovat i něco jako
$countryTranslations
->findBy(['language' => $activeLanguage])
->orderBy(['name', 'this->country->isoCode']);
- Jan Tvrdík
- Nette guru | 2595
@hrach To funguje i když ta země má těch překladů n? Tj. ORM pak nad tím udělá DISTINCT?
- pfilipek
- Člen | 25
Zdravim,
tak jsem zkoušel to co napsal @hrach
pripadne:
$countries = $orm->countries->findBy(['this->translation->lang' => $activeLanguage]) ->orderBy('this->translation->name');
a nefunguje. Píše to následující chybu Cannot order by ‚this->translations->name‘ expression, includes has many relationship, což je logické, protože ORM neví podle kterého překladu to řadit.
to @Jan Tvrdík:
no ty překlady nemusí být unikátní, tady u překladu státu nejspíš ano,
ale co když se to bude týkat překladu tabulky výrobků tam už jeden
výrobek může být pro dva jazyky přeložen stejně, například čeština a
slovenština mají některá slova totožná.
Editoval pfilipek (23. 5. 2016 8:46)
- pfilipek
- Člen | 25
Tak jsme to teďka ještě s kolegou konzultovali a mě napadlo po přečtení příspěvku od @Jan Tvrdík, že i když chci vypisovat na výstup země, tak bude lepší k tomu přistupovat ze strany překladů zemí (CountryTranslation), protože každý překlad vidí právě na svoji konkrétní zemi a přes ni můžu jak filtrovat, tak i řadit pokud se nepletu:
$countryTranslations = $orm->countryTranslations->findBy(['language' => $activeLanguage, 'this->country->isoCode' => $filter['isoCode']])
->orderBy('name')
->orderBy('this->country->isoCode', ICollection::DESC);
Editoval pfilipek (23. 5. 2016 9:13)
- pfilipek
- Člen | 25
pfilipek napsal(a):
Tak jsme to teďka ještě s kolegou konzultovali a mě napadlo po přečtení příspěvku od @Jan Tvrdík, že i když chci vypisovat na výstup země, tak bude lepší k tomu přistupovat ze strany překladů zemí (CountryTranslation), protože každý překlad vidí právě na svoji konkrétní zemi a přes ni můžu jak filtrovat, tak i řadit pokud se nepletu:
$countryTranslations = $orm->countryTranslations->findBy(['language' => $activeLanguage, 'this->country->isoCode' => $filter['isoCode']]) ->orderBy('name') ->orderBy('this->country->isoCode', ICollection::DESC);
problém u tohoto řešení je, že pokud některá země nebude mít překlad pro aktuální jazyk tak se mi nezobrazí. Jak na to? Chtěl bych, aby se to chovalo následovně:
Výchozí parametry:
- Výchozí jazyk systému je: čeština
- Aktuální jazyk je: sloveština
- Země mají pro výchozí jazyk překlady
Měly by se zobrazovat všechny země tak, že pokud nebude existovat překlad pro aktuální jazyk vezme se překlad pro výchozí jazyk.
Editoval pfilipek (23. 5. 2016 9:53)
- Jan Tvrdík
- Nette guru | 2595
@pfilipek Myslím, že to co chceš, je na natolik složité, že buď musíš načíst zároveň překlady pro výchozí i aktivní jazyk a v PHP si to pak proiterovat a vzít si z toho překlad pro aktivní jazyk, pokud je k dispozici, a jinak vzít překlad pro výchozí jazyk.
$defaultLanguage = 'cs';
$activeLanguage = 'sk';
$countryTranslations = $orm->countryTranslations
->findBy([
'language' => [$activeLanguage, $defaultLanguage],
'this->country->isoCode' => $filter['isoCode']]
)
->orderBy('name')
->orderBy('this->country->isoCode', ICollection::DESC);
Nebo bys musel položit dotazy dva a načíst v prvním překlady pro aktivní jazyk a v druhém překlady pro výchozí jazyk, které jsi ještě nenačetl v prvním dotazu.
Nebo, což mi připadá asi jako nejlepší možnost, je napsat si to v SQL v mapperu, kde to jde udělat jedním (byť netriviálním) dotazem.
- pfilipek
- Člen | 25
Díky moc za návod, tvůj návod
$defaultLanguage = 'cs'; $activeLanguage = 'sk'; $countryTranslations = $orm->countryTranslations ->findBy([ 'language' => [$activeLanguage, $defaultLanguage], 'this->country->isoCode' => $filter['isoCode']] ) ->orderBy('name') ->orderBy('this->country->isoCode', ICollection::DESC);
je super, akorát se mi dublují záznamy což je logické když udělám language IN (‚cs‘, ‚sk‘), ale potřeboval bych tím pádem udělat nějké GROUP BY pro seskupení těch záznamů.
SQL dotaz v Mapperu není problém, ale nevím co má mapper vracet, tak aby se mi to chovalo korektně a hlavně když budu chtít použít nějaké aliasy, tak se s tím už bude hůře pracovat v řazení atd. Nebude to vůbec použitelné pro napojení na Nextras\Datagrid.
Ještě dotaz na metody v mapperu:
Někde jsem četl, že když udělám metodu v Mapperu, tak stačí potom
v repozitáři dát metodu do anotace třídy (předpokládám, že jen kvůli
nápovědě) a mělo by být vše funkční, ale u mě není → vyhodí to
chybu
Call to undefined method prosys\model\orm\CountryRepository::test()
.
- Jan Tvrdík
- Nette guru | 2595
Nebude to vůbec použitelné pro napojení na Nextras\Datagrid.
Nesmysl, z pohledu datagridu nejde poznat, jestli se pro sestavené té kolekce použil dotaz z mapperu nebo ne. Pokud ho dobře napíšeš.
stačí potom v repozitáři dát metodu do anotace třídy
Mám dojem, že musí začínat ne get
nebo
find
.
- goood
- Člen | 26
pfilipek napsal(a):
@JanTvrdík Vyzkoušel jsem tvoji radu
Mám dojem, že musí začínat ne
get
nebofind
.ale bohužel je nefunkční. Zkusil jsem si pojmenovat vlastni metodu getMy i findMy a pokaždé mi to zahlásilo chybu, že volám neznámou metodu na repozitář.
Tady je to popsano https://nextras.org/…0/repository . Me to teda funguje dobre
<?php
/**
* @method ICollection|Book[] findBooksWithEvenId()
*/
final class BooksRepository extends Repository
{
// ...
}
final class BooksMapper extends Mapper
{
public function findBooksWithEvenId()
{
return $this->builder()->where('id % 2 = 0');
}
}
?>
- pfilipek
- Člen | 25
@goood Díky, tak už jsem pochopil použití a funguje.
Akorát pořád stojím na tom mém problému s překlady. Nevím si s tím
rady. To co mi poradil @JanTvrdík (přistupovat ze strany překladů) je
nepoužitelné, pokud daná entita nemá překlad pro aktuální jazyk. Když
totiž použiji podmínku
...->findBy(['language' => [$activeLanguage, $defaultLanguage]);
tak se mi vrátí překlady pro oba jazyky – logicky ;). Já bych ovšem
potřeboval vrátit vždycky všechny entity, a ke každé
entitě mít překlad aktuálního jazyka a pokud
neexistuje, tak se vezme výchozí překlad
(ten musí vždycky existovat).
Zkuste mě aspoň někdo nakopnout :) Díky moc.
Editoval pfilipek (24. 5. 2016 13:44)
- Jan Tvrdík
- Nette guru | 2595
@pfilipek Řešíš tady X dní jak napsat velmi složitý dotaz pomocí ORM místo toho, abys ho během 15 minut napsal v SQL.
- goood
- Člen | 26
pfilipek napsal(a):
@goood Díky, tak už jsem pochopil použití a funguje.
Akorát pořád stojím na tom mém problému s překlady. Nevím si s tím rady. To co mi poradil @JanTvrdík (přistupovat ze strany překladů) je nepoužitelné, pokud daná entita nemá překlad pro aktuální jazyk. Když totiž použiji podmínku
...->findBy(['language' => [$activeLanguage, $defaultLanguage]);
tak se mi vrátí překlady pro oba jazyky – logicky ;). Já bych ovšem potřeboval vrátit vždycky všechny entity, a ke každé entitě mít překlad aktuálního jazyka a pokud neexistuje, tak se vezme výchozí překlad (ten musí vždycky existovat).Zkuste mě aspoň někdo nakopnout :) Díky moc.
Tak si to napiš přímo v SQL, to asi bude nejjednodušší
- pfilipek
- Člen | 25
@JanTvrdík Díky za názor, ale pro mě v tu chvíli nemá význam asi používat ORM, protože v systému budu mít cca 70% entit, které mají k sobě překladové entity. Chtěl jsem využívat ORM, protože mi to přišlo lepší než obyčejné SQL, nebo Nette Database, ale jak se tak dívám, tak mi asi nic jiného nezbývá, protože jak vidno můj problém je v ORM neřešitelný.
- hrach
- Člen | 1838
@pfilipek
Díky za názor, ale pro mě v tu chvíli nemá význam asi používat ORM
zpusob vytazeni dat prece nic nerika o tom, ze nemuzes vyuzivat benefitu
orm;
to ale porad nemeni nic na tom, ze zrejme muzes vyuzit QueryBuilder, ktery
poskytuje dostatecnou abstrakci a posleze se porad da prevest na klasicke
ICollection.
- Gappa
- Nette Blogger | 208
Ahoj,
mám dotaz ohledně jazykových mutací. Aktuálně je mám řešené obyčejným dotazem pomocí dvou tabulek – jedna obsahuje „společné věci“, druhá pouze věci, které se musí překládat a přes ID z „nejazykové“ tabulky jsou oba záznamy provázané.
Ideální by bylo, kdyby se pak celek choval jako jedna entita – tedy
např. aby $article->image
byl z nejazykové tabulky,
$article->title
z jazykové.
- Je to vůbec v ORM možné udělat? Protože nějak by se musel předávat jazyk a na jeho základě automaticky vytáhnout z druhé tabulky překlad (pokud v požadovaném jazyku existuje) a „jedna entita byla ze dvou tabulek“.
- Pokud to možné je – je to vůbec dobrý nápad? :)
- Existuje nějaký lepší způsob, jak řešit v DB překlady?
Díky za jakékoliv postřehy :)
(Debatu výše jsem sledoval, ale přijde mi, že se řešilo trošku něco jiného.)
- pfilipek
- Člen | 25
@Gappa Něco co potřebuješ mám již vyřešeno. Udělal jsem si obecnou TranslationEntity, od které dědí všechny entity, které mají překladovou entitu více napoví níže uvedený kód:
namespace prosys\model\orm\entities;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\InvalidStateException;
use prosys\services\LanguageService;
abstract class TranslationEntity extends MyEntity
{
/** @var LanguageService */
protected $languageService;
/**
* Injectnutí LanguageService.
* @param LanguageService $languageService
*/
public function injectTranslator(LanguageService $languageService) {
$this->languageService = $languageService;
}
/**
* Vrátí překlad podle jazyka.<br />
* Pokud jazyk není předán, vezme se výchozí.
*
* @param string $code
* @return ProSYSEntity
*/
public function getTranslation($code = NULL) {
$language = NULL;
if (!isset($this->translations)) {
throw new InvalidArgumentException('Entita ' . get_called_class() . ' nemá definovanou property translations s vazbou na překladovou tabulku.');
}
if (!is_null($code)) {
try {
$language = $this->languageService->getLanguageByCode($code);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException($e->getMessage());
}
}
if (is_null($language)) {
$language = $this->languageService->getCurrentLanguage();
}
$translation = $this->translations->get()->findBy(['language' => $language->id])->fetch();
if (!$translation) {
throw new InvalidArgumentException('<b>' . get_called_class() . ' (id: ' . $this->id . ')</b>', 'Následující entity nemají překlad pro jazyk: <b>' . $language->name . '</b>');
$translation = $this->translations->get()->findBy(['language' => $this->languageService->getDefaultLanguage()])->fetch();
}
return $translation;
}
/**
* Vyzkouší získat property způsobem, který definuje rodič, v případě výjimky vyzkouší, zda se nejedná o překladovou property v aktuálním jazyku a jinak vyhodí obě výjimky.
*
* @param $name
* @return mixed
*/
public function &__get($name) {
$translate = function($name, $msgs = []) {
try {
return $this->getTranslation()->$name;
} catch (InvalidArgumentException $e) {
$msgs[] = $e->getMessage();
throw new InvalidArgumentException(implode(' --> ', $msgs));
}
};
try {
$value = parent::__get($name);
} catch (InvalidArgumentException $e) { // neni-li uvedena v anotaci entity
$value = $translate($name, [$e->getMessage()]);
} catch (InvalidStateException $e) { // je-li v anotaci entity jako virtual
$value = $translate($name, [$e->getMessage()]);
}
return $value;
}
A tady je použití v překladatelné entitě:
namespace prosys\model\orm\entities;
use prosys\datatypes\JSON;
/**
* Třída CountryEntity - objektová reprezentace země systému.
*
* @property-read int $id {primary}
* @property string $isoCode
* @property string $isoCode2
* @property JSON $addressFormat
* @property bool $mandatoryZipcode
* @property bool $disabled
*
* @property ManyHasOne|CountryTranslationEntity[] $translations {1:m CountryTranslationEntity::$country}
*
* @property-read string $name {virtual}
* @property-read string $description {virtual}
* @property-read string $help {virtual}
*/
class CountryEntity extends ProSYSTranslationEntity
{
}
LanguageService je služba, která se mi stará o poskytování jazyků.
Funguje to tak, že překladatelná entita musí mít property
$translations
a potom se s tím pracuje následovně:
$country = $orm->countries->getById(2);
$country->getTrasnlation()->name; // vrátí název pro aktuální jazyk
$country->getTrasnlation('en')->name; // vrátí název pro anglický jazyk
Editoval pfilipek (25. 5. 2016 8:31)
- hrach
- Člen | 1838
@Gappa :
- ano da, napriklad jak psal pfilipek; nebo tak, ze si napises gettery, ktere budou slouzit jako proxy na subentitu.
- imo tezko rict, sam s preklady tak velke zkusenosti nemam, ale nevidim tam zadny zasadni problem.
- treba si muzes udelat jen „readonly“ entity, ktere budes tahat z view, ktere ti sestavi db.
@pfilipek urcite neni dobre ridit beznou funkcionalitu pomoci vyjimek
- pfilipek
- Člen | 25
@hrach Tak jsem si udělal dotaz do DB v maperru:
public function findByLanguage(LanguageEntity $language) {
$query = $this->builder()->from($this->getTableName(), 'c')
->addSelect('c.*')
->addSelect('CASE WHEN t.`name` IS NULL THEN d.`name` ELSE t.`name` END AS name')
->leftJoin('c', 'country_translations', 't', 't.country_id = c.id AND t.language_id = %i', $language->id)
->leftJoin('c', 'country_translations', 'd', "d.country_id = c.id AND d.language_id = (SELECT id FROM languages WHERE is_default = 1)");
return $query;
}
vrátí mi to ICollection, ale problém nastane když chci na to zavolat
orderBy('name')
(sloupec, který mi vzniká pomocí CASE).
Jak jsem zjistil, tak Dbal automaticky přidá k požadovanému sloupci
alias z from tabulky a to vyhodí vyjímku Unknown
column ‚c.name‘ in ‚order clause‘ což chápu, protože takový
sloupec v tabulce countries c opravdu neexistuje.
Ještě se zeptám, kdybych to chtěl řešit pomocí DB view (pohledu), tak jak to mám udělat abych mohl i ukládat data, protože nad view samozřejmě nemůžu provádět INSERT, UPDATE a DELETE?
Editoval pfilipek (25. 5. 2016 12:33)
- hrach
- Člen | 1838
@pfilipek
- case & name: no a nemuzes tam dat as c.name? ICollection umi pracovat jen se strukturou entity, tzn. jen diky tomu, ze tam je takovy sloupec ti to funguje, ale tim hackem by to mohlo fungovat.
- view: tam je to prave ten vtip, ze by to bylo jen read-only. byla to jen idea, nevim, jestli je to dobry napad.
- rizeni flow – pres navratove hodnoty, nebo argumenty pasnuty jako reference. uplne presne jsem nezkoumal tvuj priklad, ale urcite to nejak pujde.
- Jan Tvrdík
- Nette guru | 2595
@pfilipek Nepomohlo by to obalit dalším selectem, tj. něco jako
public function findByLanguage(LanguageEntity $language)
{
$query = $this->builder()->from($this->getTableName(), 'c')
->addSelect('c.*')
->addSelect('CASE WHEN t.`name` IS NULL THEN d.`name` ELSE t.`name` END AS name')
->leftJoin('c', 'country_translations', 't', 't.country_id = c.id AND t.language_id = %i', $language->id)
->leftJoin('c', 'country_translations', 'd', "d.country_id = c.id AND d.language_id = (SELECT id FROM languages WHERE is_default = 1)");
return $this->builder()->from($query->getQuerySql(), 'x', ...$query->getQueryParameters());
}
- pfilipek
- Člen | 25
@hrach
- case & name: Tak jsem vyzkoušel doporučený hack ale stále to nefunguje. Stále to vyhazuje stejnou chybu.
- view: a můžu se zeptat jestli máš nápad? protože mě přijde blbost dělat kvůli tomu dvě entity, dva repositáře a dva mappery (jede pro obsluhu view – SELECT a druhý pro zbylé operace INSERT, UPDATE a DELETE)
- pfilipek
- Člen | 25
@JanTvrdík tak to taky bohužel nefunguje. Vyhazuje to Error: Object of class Nextras\Dbal\QueryBuilder\QueryBuilder could not be converted to string. což chápu → asi není implementována metoda __toString(). Nicméně jsem zkusil i toto
$this->builder()->from($query->getQuerySql(), 'x');
a vyhodí to že není předán parametr pro zástupný znak %i
Missing query parameter for modifier %i. search
.
Edit: jo díky jsem si nevšim toho třetího parametru.
Editoval pfilipek (25. 5. 2016 13:04)
- pfilipek
- Člen | 25
@Gappa
Ideální stav by byl, že by entita řešila jak načítání, tak ukládání dat, aby nebylo poznat, že se reálně pracuje se dvěma tabulkami.
No úplně ideální to není, protože je lepší mít pro překlady zvlášť entitu, protože překladů máš většinou víc (podle počtu jazyků). Je to lepší z toho důvodu, že přes vazbu ManyHasOne si to pohodlně napáruješ a potom už můžeš jednoduše vypisovat v šabloně např:
<div id="someEntity_{$someEntity->id}">
ID: {$someEntity->id}
Překlady:
<ul n:inner-foreach="$someEntity->translations as $translation">
<li>Jazyk: {$translation->language->name}: {$translation->name}</li>
</ul>
</div>
při definici entit:
/**
* @property string $id {primary}
* @property string $version
* @property bool $installed
*
* @property ManyHasOne|SomeTranslationEntity[] $translations {1:m SomeTranslationEntity::$some, cascade=[persist, remove]}
*
* @property-read string $name {virtual}
* @property-read string $description {virtual}
* @property-read string $help {virtual}
*/
class SomeEntity extends Entity
{
/**
* @param string $id
* @param string $version
* @param bool $installed
*
* @return SomeEntity
* @throws InvalidArgumentException
*/
public static function create(string $id = '', string $version = '', bool $installed = FALSE): Entity {
if (!$id || !$version) {
throw new InvalidArgumentException('id a verze jsou povinné parametry');
}
$entity = new SomeEntity();
$entity->id = $id;
$entity->version = $version;
$entity->installed = $installed;
return $entity;
}
}
/**
* @property-read int $id {primary}
* @property ExtensionEntity $extension {m:1 SomeEntity::$translations}
* @property LanguageEntity $language {m:1 LanguageEntity, oneSided=TRUE}
* @property string $name
*/
class SomeTranslationEntity extends TranslationEntity
{
/**
* @param LanguageEntity $code
* @param string $name
*
* @return SomeTranslationEntity
* @throws InvalidArgumentException
*/
public static function create(LanguageEntity $language = NULL, string $name = ''): Entity {
if (!$language || !$name) {
throw new InvalidArgumentException('Entita jazyka a překlad názvu rozšíření jsou povinné parametry');
}
$entity = new SomeTranslationEntity();
$entity->language = $language;
$entity->name = $name;
return $entity;
}
}
V entitách mám vytvořené i továrničky, aby se to lépe používalo potom při ukládání atd …
a naopak zase při ukládání je to taky triviální a ještě navíc uzavřené v transakci, takže když by se něco pokazilo, tak se defacto nic nestane více příklad
$someEntity = new SomeEntity();
$someEntity->version = '1.0';
$someEntity->installed = TRUE;
$someEntity->translations->add(SomeTranslationEntity::create($currentLanguageEntity, 'Název nějaké entity'));
// ulozeni hlavni entity vč. překladu
$orm->someRepository->persistAndFlush($someEntity);
- Hanz25
- Člen | 38
Ahoj,
zkouším 2.0.2. a zatím to vypadá dobře. Jen mi skáče exception
Nextras\Dbal\InvalidArgumentException
Modifier %dts does not allow NULL value, use modifier %?dts
instead.
data vybírám takto
$this->findBy(["published!=" => NULL, "published<" => new \DateTime]);
Definice entity
/**
* Blog
* @property int $id {primary}
* @property string $title
* @property string $nameClean
* @property string $text
* @property int $views
* @property User|NULL $author {m:1 User::$blogs}
* @property DateTime|NULL $published
*/
class Blog extends Entity
{
}
cache smazaná, když si dumpnu výsledek, a proklikám se k informacím o propertě published, tak je tam nullable ⇒ true
Editoval Hanz25 (9. 6. 2016 12:31)