Nextras\ORM – ORM nad Nextras\Dbal

hrach
Člen | 1834
+
0
-

@Hanz25 promin, ze to zustalo bez reakce, super, ze si to objevil. Vytvoril jsem issue pro lepsi check.

svezij
Člen | 69
+
0
-

@hrach
Ahoj, chtěl bych si rozšířit QueryBuilder o možnost CREATE TABLE a další. Když se podívám na kód QueryBuilderu, tak vypadá, že je určen k dalšímu rozšiřování, ale bohužel to nejde, protože tam máš vše private. Nemohu tedy využít metodu getQuerySql(), protože property $generatedSql je také private atd. Můžu si napsat své vlastní, ale to pak budu vytvářet jeden typ QueryBuilderu pro dotazy typu SELECT, druhý typ QueryBuilderu pro dotazy typu CREATE TABLE atd. Bylo by možné, abys po zvážení dal některé části protected místo private?

Také mě napadlo, že by obecně nebylo špatné mít přes config možnost vložit jinou třídu pro Connection, než tvoji Nextras\Dbal\Connection. Domnívám se, že by stačilo jednoduše upravit řádek 32 v souboru DbalExtension.php, kde se nachází definice připojení a konkrétně metoda setClass (ale tím si nejsem jistý, protože nevím, zda jsem v Nette natolik zběhlý, abych to mohl tvrdit). Nicméně, kdybych chtěl podědit třídu Nextras\Dbal\Connection, tak bych dopadl obdobně, jako u QueryBuilderu, také tam máš mnohé private.

Samozřejmě budu rád za tvůj názor. Díky, ahoj.

hrach
Člen | 1834
+
0
-

@svezij

  • query builder: ohledne create table – to asi neni moc pro aktualni query builder, jeho api je spis pro selectovani, a do budoucna urcite insertovani a updatovani a mazani. vytvareni tabulek imo bude vyzadovat o dost jiny interface a uplne nevim, co se ti z aktualniho query builderu tolik hodi. pripadne navrhi ktere property a pak muzes udelat pull-request :)
  • dedeni connection – co presne chces delat? ktere property potrebujes?
  • extension pro jinou connection class – neni problem, otazka je, jestli ti to bez predchoziho bodu k necemu bude :-)
svezij
Člen | 69
+
0
-

@hrach

No, oba mé návrhy spolu souvisí. Je možné, jak píšeš, že není aktuální query builder ideální pro create table, ale každopádně je dobré používat jeden query builder pro „buildování queries“. Nehledě na to, že Connection::createQueryBuilder() vytváří právě tento konkrétní QueryBuilder. Z tohoto QueryBuilderu se mi hodí minimálně property $generatedSql, kterou využívá metoda getQuerySql(). Vše ostatní se týká pouze SELECTu, který máš vyřešený.

Nijak dopodrobna jsem Connection zatím nezkoumal, ale souvisí to právě s metodou createQueryBuilder(), ve které bych potřeboval vrátit jiný, poděděný QueryBuilder. S tím tedy souvisí i extension pro jinou Connection, která by byla potomkem té tvojí.

Situace není těžká – potřeboval jsem CREATE TABLE, díval jsem se, jak řešíš dotazování a zjistil jsem, že existuje QueryBuilder. Vytvořil jsem si tedy potomka, kterého jsem rozšířil o funkčnost CREATE TABLE a zjistil jsem, že není viditelná property $generatedSql, takže nemůžu využít rodičovskou getQuerySql(). Zjistil jsem také, že jej vytváříš pomocí Connection::createQueryBuilder(), tak jsem si říkal, že přetížím createQueryBuilder() ve vlastním Connection a zjistil jsem, že v Extension není možnost, jak nastavit svou Connection namísto tvé. To je vše. Každopádně zkusím víc projít tuto a související třídy.

igor.pocta
Člen | 100
+
0
-

Ahoj,

začal jsem přepisovat aplikaci na nextras/orm ale setkal jsem se s jedním problémem. Potřebuji použít nějaký datagrid ale nelze mu dodat data z orm :( Tak jsem si v presenteru přímo předal

/** @var Context $context */
   public $context;

   public function __construct(Context $context)
   {
       parent::__construct();
       $this->context = $context;
   }

a když chci použít klasický $this->context->table(„article“) tak mi to vypisuje chybu, Cannot read an undeclared column ‚Level‘. To je ale divný, protože to je v úplně jiném modulu a ten funguje dobře.

Nevíte co s tím?


https://ctrlv.cz/nKRf

Editoval igor.pocta (18. 6. 2016 14:39)

Jan Tvrdík
Nette guru | 2595
+
0
-

To nechápu, proč by datagridu nešlo dodat data z ORM? Každopádně z toho screenshotu nejde poznat imho vůbec nic poznat. Zkus smazat cache, víc poradit neumím.

igor.pocta
Člen | 100
+
0
-

@JanTvrdík Ahoj, a můžeš mi prosím poradit, jak z Nextras\Orm dodám data do Nextras\Datagrid, tak, abych tam mohl uplatňovat filtry apod.? Zkoušel jsem to a také to házelo chybu :)

&EDIT: Tak se podařilo, předtím tam byla chybička :) Díky

Editoval igor.pocta (18. 6. 2016 17:00)

igor.pocta
Člen | 100
+
+1
-

Ahoj,

vytvořil jsem si vlastní metodu v mapperu u ORM a mám problém s tím, že mi vyskakuje chyba – tabulka s názvem databáze neexistuje

$month = new DateTime("-1 month");
$builder = $this->builder();
$builder->from($this->getTableName());
$builder->where('[created_by] = %s', $employee->id);
$builder->andWhere('[termin] > %s', $month->format("Y-m-d"));
return $builder;

mi vyhodí chybu

Nextras\Dbal\QueryException
Unknown table ‚wag.‘
SQL

SELECT ``.* FROM task_item WHERE (`created_by` = '805963') AND (`termin` > '2016-05-20')

Chyba je právě v ``.* a když dám cokoliv do $builder->select() nebo $builder->addSelect(), tak mi to ignoruje a vždy mi to postaví stejný SQL příkaz.

Lze to nějak obejít?

Editoval igor.pocta (20. 6. 2016 16:37)

Jan Tvrdík
Nette guru | 2595
+
+2
-

@igor.pocta Zkus smazat ten řádek $builder->from($this->getTableName());, from nastavuje už volání builder(), akorát to na rozdíl od tebe dělá správně.

Felix
Nette Core | 1189
+
+4
-

@JanTvrdík Mozna to pusobi jenom na me, ale veta ‚..akorat to narozdil od tebe dela spravne‘ je podle me zbytecne drsna.

Jde to prece napsat i taktne. Rozhodne tenhle pristup propagaci Nextras/Orm neprospeje. A my vsichni chceme, aby se to pouzivalo co nejvice a tim i odladilo a zapojilo se vice lidi.

Jestli to myslim spatne, tak me prosim opravte. :)

igor.pocta
Člen | 100
+
0
-

Jan Tvrdík napsal(a):

@igor.pocta Zkus smazat ten řádek $builder->from($this->getTableName());, from nastavuje už volání builder(), akorát to na rozdíl od tebe dělá správně.

@JanTvrdík Tak to pomohlo. Děkuji.
Mimochodem, já tam nejdřív zkoušel samotný název tabulky (což v podstatě vrací i ta metoda) ale to vyhazovalo stejnou chybu, i když v dokumentaci je napsáno:
The second argument is optional table alias, other arguments are arguments for used modifiers.
A až když jsem tam ten alias přidal, fungovalo to.

Jan Tvrdík
Nette guru | 2595
+
+2
-

@igor.pocta Ono je to optional pouze pro obecné použití QueryBuilderu mimo ORM, ORM ten alias potřebuje.

pfilipek
Člen | 25
+
0
-

@JanTvrdík @hrach Zdravím, chtěl jsem se zeptat, jestli jste už někdy řešili a nebo zda to ORM umožňuje, abych přidal do modelu dynamicky repozitáře místo do anotace. Děláme modulární systém a potřebujeme dynamicky přidat repozitář pro dané extension. Zatím nás napadlo přidat to nějakým způsobem do anotací, protože nám s kolegou @svezij přijde, že jiným způsobem to ORM neumožňuje. Děkuji mnohokrát za odpověď.

hrach
Člen | 1834
+
0
-

@pfilipek neresil. Hlavní podstatná věc je, že DI extension pro Model určitě věci predzpracovává. Pak je tu ještě SimpleModel, ale ten je spíš pro testování. Imo potřebuješ něco úplně nového, co bude implementovat IModel.

hrach
Člen | 1834
+
0
-

Respektive teď je tam repository loader, tak bude možná stačit reimplementovat jen ten.

Vastlik
Člen | 58
+
0
-

Ahoj, je nějaká možnost jak v Repository vytvořit query která obsahuje OR? Například

Select * from `table` where `status` = 1 and `number` = 1 and `created_at` > 1469526914 OR `number` = 2 and `status` = 1

Nějaký takovýhle php kód

return $this->findBy([
            'this->account->id' => $accountId,
			'createdAt>=' => $createdAt,
			'status' => 1,
            'or status' => 2,
			'this->account->id' => $accountId
        ])->limitBy($perPage)->orderBy('id');

Díky!

Editoval Vastlik (26. 7. 2016 13:37)

batko
Člen | 219
+
0
-

Ahoj,

jde nějak elegantně udělat dvojí řazení?

Mám

<?php
* @property OneHasMany|Time[] $times {1:m Time::$ticket, orderBy=[user, DESC]}
?>

Chci

<?php
* @property OneHasMany|Time[] $times {1:m Time::$ticket, orderBy=[user, DESC] A JEŠTĚ DLE DATA}
?>

Díky

Spectator
Člen | 48
+
0
-

Ahoj, mám takový problém. Používám vazební tabulku a v určitých případech funguje správně v určitých vytváří nesmyslná SQL – -konkrétně countStored()

Entity mám definovány takto:

/**
 * Class User
 * @property int $id {primary}
 * @property string $status {enum self::STATUS_*}
 * @property \DateTime $created {default now}
 * @property string $email
 * @property string $password
 * @property string $role {enum self::ROLE_*}
 * @property string $name
 * @property ManyHasMany|Code[] $codes {m:n Code::$users, isMain=true, orderBy=[order, DESC]}
 * @property OneHasMany|UserHasCode[] $userHasCode {1:m UserHasCode::$user}
 */

class User extends Entity
{
    const STATUS_ACTIVE = 'active';
    const STATUS_INACTIVE = 'inactive';
    const ROLE_USER = 'user';
    const ROLE_ADMIN = 'admin';
}

/**
 * Class Code
 *
 * @property int $id {primary}
 * @property bool $active
 * @property \DateTime $created {default now}
 * @property string $name
 * @property int $order
 * @property ManyHasMany|User[] $users {m:n User::$codes}
 * @property OneHasMany|UserHasCode[] $userHasCode {1:m UserHasCode::$code}
 */
class Code extends Entity
{
}

/**
 * Class UserHasCode
 * @property mixed $id {primary-proxy}
 * @property int $userId {primary}
 * @property int $codeId {primary}
 * @property \DateTime $created {default now}
 * @property Code $code  {m:1 Code::$userHasCode}
 * @property User $user {m:1 User::$userHasCode}
 */
class UserHasCode extends Entity
{
}

Mapování mám změněno z %s_x_%s na %s_has_%s.

A zde problém:

$codes = $this->loggedUser->codes;
foreach ($codes as $code) {
    dump($code->id); // 1, 2, (OK)
}
dump($codes->count()); // 2 (OK)
dump($codes->countStored()); // Column 'code_id' in field list is ambiguous
/*
SELECT `user_has_code`.`user_id`, COUNT(`code_id`) as count FROM `code` AS `code` LEFT JOIN `user_has_code` AS `user_has_code` ON (`user_has_code`.`code_id` = `code`.`code_id`) WHERE `user_id` IN (1) GROUP BY `user_id`
*/

Jde mi tedy o toto:
Mám právně nastavenou vazební tabulku a příslušně i navázané entity? Popř. jak na to?
A dále – jestli je to nastaveno správně, tak proč nedoplňuje názvy tabulek v SQL?

EDIT:

Tak ještě jedna věc mne překvapuje. V té vazební tabulce je při jejím dumpu vidět, že user i code jsou něco (čísla ID :-/), ale codeId a userId jsou null, včetně id.

Catchme\Orm\UserHasCode #0ca3
metadata protected => Nextras\Orm\Entity\Reflection\EntityMetadata #dd99
className private => "Catchme\Orm\UserHasCode" (23)
primaryKey private => array (2)
0 => "userId" (6)
1 => "codeId" (6)
properties private => array (6)
id => Nextras\Orm\Entity\Reflection\PropertyMetadata #d13b { ... }
userId => Nextras\Orm\Entity\Reflection\PropertyMetadata #16f5 { ... }
codeId => Nextras\Orm\Entity\Reflection\PropertyMetadata #b6a5 { ... }
created => Nextras\Orm\Entity\Reflection\PropertyMetadata #ee94 { ... }
code => Nextras\Orm\Entity\Reflection\PropertyMetadata #5bba { ... }
user => Nextras\Orm\Entity\Reflection\PropertyMetadata #cb44 { ... }
repository private => Catchme\Orm\UserHasCodeRepository #ca59
data private => array (6)
created => Nextras\Dbal\Utils\DateTime #1915
date => "2016-08-05 14:09:16.000000" (26)
timezone_type => 3
timezone => "Europe/Prague" (13)
code => 1
user => 1
id => NULL
userId => NULL
codeId => NULL
validated private => array (3)
id => TRUE
userId => TRUE
codeId => TRUE
modified private => array ()
persistedId private => array (2)
0 => NULL
1 => NULL
preloadContainer private => Nextras\Orm\Collection\EntityIterator #e34c
position private => 0
data private => array (3)
0 => Catchme\Orm\UserHasCode #0ca3 { RECURSION }
1 => Catchme\Orm\UserHasCode #0ca3 { RECURSION }
2 => Catchme\Orm\UserHasCode #0ca3 { RECURSION }
iteratable private => array (3)
0 => Catchme\Orm\UserHasCode #0ca3 { RECURSION }
1 => Catchme\Orm\UserHasCode #0ca3 { RECURSION }
2 => Catchme\Orm\UserHasCode #0ca3 { RECURSION }
hasSubarray private => FALSE
preloadCache private => NULL

Editoval Spectator (7. 8. 2016 20:20)

jukie
Člen | 5
+
0
-

Zdravím,

řeším logování updatů ve spojení s kdyby events, chtěl jsem nějak elegantně zajistit ve vyšší třídě načtení starých hodnot entity a udělat diff, ale narážím na problémy:

  1. nejsem schopen načíst hlouběji než ve vlastním presenteru staré hodnoty entity, zřejmě proto, že se dále posílají už jen entity s modifikovanými parametry, bohužel je v projektu nemožné upravovat každý presenter a v něm zajistit logování
  2. pořád jsem si myslel že toto někde musí být implementováno, že se přeci neprovádí update celé entity, ale jenom modifikovaných sloupců, ale on se celý update provádí

Dělám něco špatně? Předem děkuji za rady

Jan Tvrdík
Nette guru | 2595
+
0
-

@jukie ORM ví, které sloupce se změnily, viz https://github.com/…075a606c0396

jukie
Člen | 5
+
0
-

@JanTvrdík přijde mi že isModified funguje jen na nastavené hodnoty (viz. setValue ⇒ internalSetValue), čili je na uživateli zjistit, kde je změna a entitě nastavit jen tyto změny?

hitzoR
Člen | 51
+
0
-

pfilipek napsal(a):

@JanTvrdík @hrach Zdravím, chtěl jsem se zeptat, jestli jste už někdy řešili a nebo zda to ORM umožňuje, abych přidal do modelu dynamicky repozitáře místo do anotace. Děláme modulární systém a potřebujeme dynamicky přidat repozitář pro dané extension. Zatím nás napadlo přidat to nějakým způsobem do anotací, protože nám s kolegou @svezij přijde, že jiným způsobem to ORM neumožňuje. Děkuji mnohokrát za odpověď.

Řešil jsem teď stejný problém a musel jsem přepsat metodu getRepositoryList v Extension. Místo projíždění anotace jsem si tam v bootstrapu přidat RobotLoader a natáhl všechny třídy implementující IRepository

bootstrap.php

$configurator->onCompile[] = function ($_, \Nette\DI\Compiler $compiler) use ($loader) {
	...
	$compiler->addExtension('orm', new \PgWay\Model\Orm\CustomOrmExtension($loader));
};

Tady by mě od zkušenějších zajímalo, jestli jde předání RobotLoaderu řešit nějak jinak, čístěji, třeba klasicky přes config?

CustomOrmExtension.php

<?php

namespace App\Model\Orm;

use Nette;
use Nette\PhpGenerator;
use Nette\Reflection\ClassType;
use Nextras\Orm\Bridges\NetteDI\OrmExtension;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\Repository\IRepository;


class CustomOrmExtension extends OrmExtension
{
	private $robotLoader;

	public function __construct(Nette\Loaders\RobotLoader $robotLoader)
	{
		$this->robotLoader = $robotLoader;
	}

	protected function getRepositoryList($modelClass)
	{
		$modelReflection = new ClassType($modelClass);

		$builder = $this->getContainerBuilder();
		$builder->addDependency($modelReflection->getFileName());

		$repositories = [];
		foreach ($this->robotLoader->getIndexedClasses() as $class => $file) {
			$reflection = Nette\Reflection\ClassType::from($class);
			if ($reflection->implementsInterface(IRepository::class)) {
				/* úprava jména z např. 'ArticleCommentRepository' na 'articleComments',
				   což bude vpodstatě název property modelu pro konkrétní repozitář */
				preg_match('/([A-Z][a-zA-Z]*)Repository$/', $reflection->name, $nameMatch);
				$name = lcfirst($nameMatch[1]) . 's';

				if (!array_key_exists($name, $repositories))
					$repositories[$name] = $class;
				else
					throw new InvalidStateException("There are multiple repositories with name $nameMatch[1]Repository."); // TODO custom exception
			}
		}

		return $repositories;
	}
}

Je to takové trochu dirty řešení, ale svůj účel to plní. Ještě jsem upravoval MetadataParser a přidal si do argumentů u relationships volitelný modifikátor optional, abych mohl do entit psát propojení s jinýma entitama, které jsou v modulech, které nejsou nainstalované. Pří používání takových property se to musí ale dobře ošetřit. Já mám v databázi seznam nainstalovaných modulů a podle toho kontroluju, zda to můžu použít nebo ne, ale tohle už je implementační detail, který si každý může vyřešit po svém.

Anotace entity

/*
 * @property NotExistingEntity|NULL  $null     {1:1 NotExistingEntity::$.., optional} {default NULL}
 * @property NotExistingEntity       $notNull  {1:1 NotExistingEntity::$.., optional}
 */

Při zavolání $entity->null se vrací NULL, při $entity->notNull se vyhodí InvalidStateException("Property ..\XxEntity::$notNull is not set.")

Takhle vypadají všechny čtyři metody na parsování relationships:

protected function parseXHasX(PropertyMetadata $property, array &$args)
{
	$pos = strpos($args[0], '::');
	if ($pos !== FALSE)
		$class = substr($args[0], 0, $pos);
	else
		$class = $args[0];
	$entity = $this->makeFQN($class);

	$optional = in_array('optional', $args, TRUE);
	if ($optional === TRUE)
		unset($args[array_search('optional', $args, TRUE)]);

	if (!$optional || isset($this->entityClassesMap[$entity])) {
		/* původní kód metody, spustí se když není nastaven argument optional nebo když entita existuje */
	} else
		$args = []; /* $args se musí vracet z této metody prázdné, normálně se o to starají metody,
					   které se volají v posledním if, ale pokud jej přeskočíme, tak musíme takhle
					   ručně pole vymazat, jinak nám processPropertyModifier(..) vyhodí
					   InvalidModifierDefinitionException */
}

Funkční to všechno je (zatím, pořádně jsem to ještě neotestoval), ale pořád mi to přijde takové nečisté a neelegantní. Zajímal by mě názor zkušenějších a hlavně @JanTvrdík a @hrach na to, jak stabilní to bude a případně co a kde by se dalo udělat jinak, líp.

Editoval hitzoR (18. 8. 2016 9:45)

hrach
Člen | 1834
+
0
-

Odpovim až budu v září v CR. Díky za pochopení.

hrach
Člen | 1834
+
0
-

@hitzoR vypada to docela dobre a zajimave!

  • ad repository z ruznych modulu, ktere nejsou v anotaci: pro aktualni kod je to urcite dobre reseni na neni tam nic zasadne spatne. Tim, ze se to deje v compile time tak je to v podstate v pohode. Slo by to asi optimalizovat, ze treba reflection classy nevytvaris, pokud ve jmenu tridy neni repository.
  • ad optional property – tak tady to vypada zajimave a nevim jestli to uplne chapu, reseni je to pekne, ale ale zrejme je treba, aby databaze akceptovala na strane toho, kdo drzi klic, i null, protoze kdyz ten modul neni nainstalovanej… :-) tak orm vygeneruje insert bez toho no :-) Ale jo, to muze fungovat pekne, navic vetsinou jsou ty moduly z druhy strany asi – hodne zajimavej napad!
Vastlik
Člen | 58
+
0
-

Ahoj,
jak je to s implementací many to many? Mám 3 tabulky. Users, Tags, users_tags. Jak udělám vazbu? Napadá mě řešení vytvořit tabulku users_tags a pak one to many k users a tags.
Ale určitě to jde nějak za pomocí vazby ManyHasMany, ale nevím jak. Není někde example i s návrhem nebo popisem DB?
Další otázkou je určování která strana je isMain? Jak vybrat nebo, co se tím určuje?
Moc díky za odpověď.

Editoval Vastlik (7. 9. 2016 21:51)

hrach
Člen | 1834
+
0
-

https://nextras.org/…elationships v doc by to mělo být dost popsaný. Is main prostě řídí trn vztah, je k tomu něco i v jiných kapitolách.

Vastlik
Člen | 58
+
0
-

Udělal jsem dle docu, ale dostávám Undefined property: Nextras\Orm\Mapper\Dbal\DbalCollection::$users když se snažím získat u tagů users.

Editoval Vastlik (7. 9. 2016 23:19)

hitzoR
Člen | 51
+
+1
-

Vastlik napsal(a):

Udělal jsem dle docu, ale dostávám Undefined property: Nextras\Orm\Mapper\Dbal\DbalCollection::$users když se snažím získat u tagů users.

Hádám, že to bude tím, že se snažíš číst property entity z kolekce. Pokud sis třeba vytáhl jeden tag pomocí findById, tak to je špatně a měl bys to dělat pomocí getById. Pokud si dával nad repositářem findAll nebo findBy, aby ti to vrátilo víc záznamů, tak si to musíš buď fetchnout nebo projít foreachem a až pak číst $users.

Editoval hitzoR (8. 9. 2016 3:34)

Vastlik
Člen | 58
+
0
-

hitzoR napsal(a):

Vastlik napsal(a):

Udělal jsem dle docu, ale dostávám Undefined property: Nextras\Orm\Mapper\Dbal\DbalCollection::$users když se snažím získat u tagů users.

Hádám, že to bude tím, že se snažíš číst property entity z kolekce. Pokud sis třeba vytáhl jeden tag pomocí findById, tak to je špatně a měl bys to dělat pomocí getById. Pokud si dával nad repositářem findAll nebo findBy, aby ti to vrátilo víc záznamů, tak si to musíš buď fetchnout nebo projít foreachem a až pak číst $users.

Moc díky, pomohlo. Je nějaká možnost jak definovat spojovací tabulku? Tzn. aby ta tabulka nebyl users_x_tags ale users_tags? Díky

hrach
Člen | 1834
+
0
-

@Vastlik https://nextras.org/…/conventions dole

Asi by ti pomohlo procist celou dokumentaci ;)

Vastlik
Člen | 58
+
0
-

@hrach promiň, moje chyba :) jinak díky za odpověď :)

Spectator
Člen | 48
+
0
-

Zdarec, mohl bych poprosit o objasnění mého problému popisovaném výše? Dík moc

hrach
Člen | 1834
+
0
-

@Spectator zkus toto, a sorry za přehlédnutí.

/**
 * Class UserHasCode
 * @property mixed $id {primary-proxy}
 * @property \DateTime $created {default now}
 * @property Code $code  {m:1 Code::$userHasCode} {primary}
 * @property User $user {m:1 User::$userHasCode} {primary}
 */ class UserHasCode extends Entity { }
stpnkcrk
Generous Backer | 189
+
0
-

Když mám následující entitu (výřez)

/**
 * @property int                $id        {primary}
 * @property OneHasMany|Child[] $children  {1:m Child::$litter, orderBy=gender}
 */
final class Litter extends Nextras\Orm\Entity\Entity
{}

a používám možnost výchozího řazení orderBy=gender, dá se v tomhle místě nějak řadit podle dvou sloupců?

Zkoušel jsem ... orderBy=gender, orderBy=name, ale nefunguje (řadí podle posledního orderBy).

Jan Tvrdík
Nette guru | 2595
+
0
-

@skocourek Myslím, že to nejde momentálně zapsat anotací, ale pokud ručně upravíš odpovídající instanci PropertyRelationshipMetadata, tak by to mělo fungovat. Nejjednodušeji třeba v konstruktoru entity.

/**
 * @property int                $id        {primary}
 * @property OneHasMany|Child[] $children  {1:m Child::$litter}
 */
final class Litter extends Nextras\Orm\Entity\Entity
{
	public function __construct()
	{
		parent::__construct();
		$this->metadata->getProperty('children')->relationship->order = [
			['gender' => 'ASC', 'name' => 'DESC'],
			NULL
		];
	}
}
hrach
Člen | 1834
+
0
-

@skocourek na tvuj pozadavek urcite mrknu, asi to fakt nejde, kdyztak dodelame.

@JanTvrdík to nebude fungovat dobre, protoze entity z db vytazene nevolaji constructor. Asi by to chtelo pouzit event, ikdyz ted z docky to nevypada, ze by nejaky byl pro oba usecasy volany.

Ja to driv bez property resil tak, ze si clovek vytvori vlastni tridu pro drzeni relationship a pretizi https://github.com/…/HasMany.php#… metodu applyDefaultOrder, a pak nastavi pomoci {container} anotace tuto tridu danymu relationship. Protoze to bylo tezkopadne, vznikla podpora pres anotaci, ktera ocividne neni dokonala.

stpnkcrk
Generous Backer | 189
+
0
-

@JanTvrdík Díky, ale Tvoje řešení se mi bohužel nepodařilo rozchodit.

@hrach Podpora pro default řazení dle vícero sloupců by se určitě hodila, každopádně děkuju, zatím použiju řešení s přetížením applyDefaultOrder.

Děkuji moc oběma!

hrach
Člen | 1834
+
0
-

@skocourek podpora pro defaultni razeni vice sloupci implementovana v masteru, viz. https://nextras.org/…elationships

stpnkcrk
Generous Backer | 189
+
0
-

@hrach Díky, perfektní, vyzkouším! Ještě jsem dneska objevil jednu věc, se kterou si nevím rady…

Mějme vztah m:n, ale kvůli potřebě extra dat ve vazební tabulce to řeším jako 2×1:m (výřez entit)

/**
 * @property int                            $id                             {primary}
 * @property OneHasMany|AnimalCertificate[] $certificates                   {1:m AnimalCertificate::$animal, orderBy=order}
 */
final class Animal extends Nextras\Orm\Entity\Entity
{}

/**
 * @property int         $id                {primary}
 * @property string      $name
 */
final class Certificate extends Nextras\Orm\Entity\Entity
{}

/**
 * @property int         $id            {primary-proxy}
 * @property Animal      $animal        {m:1 Animal::$certificates} {primary}
 * @property Certificate $certificate   {m:1 Certificate, oneSided=true} {primary}
 * @property int         $order         {default 0}
 */
final class AnimalCertificate extends Nextras\Orm\Entity\Entity
{
}

Dá se nějak udělat tohle? Níže zkoušený zápis nefunguje, ale třeba to jen zkouším blbě…

$animal->certificates->get()->fetchPairs(null, 'certificate->name')
hrach
Člen | 1834
+
0
-

@skocourek neda :( ale uz je na to dlouho issue :D https://github.com/…m/issues/147

Vastlik
Člen | 58
+
0
-

Ahoj, mám DB a v ní tabulku m:n. Je nějaká možnost přistupovat dalším sloupcum, které jsou tam? Je tam např. created nebo state. Napadlo mě to udělat si tu tabulku jako entitiu s vazbami 1:n a 1:m, ale mám problém v tom, že v té tabulce není primární klíč.
Je nějaké mozne řešeni krom přesunutí techto sloupců do jiné tabulky nebo přidání primárního klíče? Díky za radu.

hitzoR
Člen | 51
+
+2
-

Vastlik napsal(a):

Ahoj, mám DB a v ní tabulku m:n. Je nějaká možnost přistupovat dalším sloupcum, které jsou tam? Je tam např. created nebo state. Napadlo mě to udělat si tu tabulku jako entitiu s vazbami 1:n a 1:m, ale mám problém v tom, že v té tabulce není primární klíč.
Je nějaké mozne řešeni krom přesunutí techto sloupců do jiné tabulky nebo přidání primárního klíče? Díky za radu.

Já ten vztah m:n s parametrem používám jako normální entitu se dvěma 1:m vztahy, kde je kompozitní primární klíč z IDček obou stran vztahu.

/**
 * @property int                     $id          {primary-proxy}
 * @property ManyHasOne|Competition  $competition {primary} {m:1 Competition::$admins}
 * @property ManyHasOne|User         $user        {primary} {m:1 User::$admining}
 * @property string                  $position
 */
class CompetitionAdmin extends Entity
{
}
Vastlik
Člen | 58
+
0
-

@hitzoR díky moc za pomoc, funguje :)

Petr Kubeš
Člen | 2
+
0
-

Ahoj,
mám entity:

	/**
 * User
 *
 * @property int 						$id {primary}
 * @property string 					$email
 * @property OneHasMany|Friendship[]  	$myFriendships 		{1:m Friendship::$user}
 * @property OneHasMany|Friendship[]  	$otherFriendships 	{1:m Friendship::$friend}
 */
class User extends Entity
{
}

/**
 * Friendship
 *
 * @property int                $id 		{primary}
 * @property User  				$user     	{m:1 User::$myFriendships}
 * @property User  				$friend		{m:1 User::$otherFriendships}
 * @property DateTime			$createdAt 	{default now}
 */
class Friendship extends Entity
{

}

CREATE TABLE IF NOT EXISTS  `users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `email` varchar(255) NOT NULL UNIQUE ,
) ENGINE='InnoDB';

CREATE TABLE IF NOT EXISTS `friendships` (
  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  `user_id` int(11) unsigned NOT NULL,
  `friend_id` int(11) unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT `friendships_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
  CONSTRAINT `friendships_ibfk_2` FOREIGN KEY (`friend_id`) REFERENCES `users` (`id`)
) ENGINE='InnoDB' DEFAULT CHARSET=utf8;

Když spustím následující kód:

$friendship = new Friendship();
$friendship->user = $this->orm->users->getById(1);
$friendship->friend = $this->orm->users->getById(2);
$this->orm->friendships->persistAndFlush($friendship);

tak to vygeneruje dotaz:

INSERT INTO `friendships` (`user`, `friend`, `created_at`) VALUES (1, 2, '2016-10-14 16:59:52')

čekal bych, že to bude user_id a friend_id. Má to tak být, nebo mám někde chybu? Děkuji za odpověď.

Jan Tvrdík
Nette guru | 2595
+
0
-

@PetrKubeš Zkus smazat cache.

Petr Kubeš
Člen | 2
+
0
-

@JanTvrdík Díky, na to vždycky zapomenu. :D

hrach
Člen | 1834
+
+1
-

Verze 2.2.0 je venku. Vsichni, co pouzivate verzi 2.1.0 prosim zkuste co nejdriv upgradovat, ve 2.1.0 jsou vazne problemy s nacitanim relationship & perzistenci. https://github.com/…s/tag/v2.2.0

Burrag
Nette Blogger | 3
+
0
-

Ahoj, mam nejaky otazek okolo nextras/orm.
Musi byt kazdy repositori zaregistrovan v modelu?
Muze byt vice modelu? To je hlavne kvuli Extension chci je nezavisly.

Editoval Burrag (24. 11. 2016 19:21)

Hanz25
Člen | 38
+
0
-

Ahoj,

upgradoval jsem na 2.2.0 a zatím je všechno super. Funguje jak má – dobrá práce. Jen bych se chtěl více doptat na mazání entit, protože to mi pořád už od předchozích verzí trochu blbne.

Dejme tomu, že mám entitu projekt, poté projekt má určité tasky tzn. jeden Pojekt má eN Tasků a na ty tasky jsou navázány všechny možné další entity (soubory, komentáře, tagy apod.) takže další vztahy 1:m i m:n.

/**
 * Project
 * @property int $id {primary}
 * @property string $name
 * @property Tasks[]|NULL $tasks {1:m Task::$project}
 */

/**
 * Task
 * @property int $id {primary}
 * @property Project $project {n:1 Project::$tasks}
 * @property string $name
 * @property Topic|NULL $topic {1:1 Topic::$task, isMain=true, cascade=[remove]}
 * @property Attachment[]|NULL $attachments {1:m Attachment::$task, cascade=[remove]}
...
 */

A když se pokouším smazat Task kódem

$task = $this->orm->tasks->getById($taskId);
$this->orm->tasks->removeAndFlush($task);

tak mi vyskočí
Nextras\Dbal\NotNullConstraintViolationException
Column ‚project_id‘ cannot be null

protože z nějakého důvodu se to snaží před smazáním ještě uložit. Takže tady nevím jestli jsem na něco nezapomněl, nebo jestli se jedná o chybu.

A ještě by mě zajímalo, jak by se měly v Nextras/ORM obecně mazat entity s nějakými dětmi. Protože v dokumentaci je, že stačí dát jako druhý parametr metody remove() true, což se ovšem už nemusí dělat, protože true je tam defaultně. A jaký je případný rozdíl mezi tímto řešením (true v remove metodě) a ještě přidáním cascade=[remove] do entity. Nebo to cascade je nějaká vnitřní podpora pro databázové cascade navázané na cizí klíče? Ta logika může být i v databázi, ale raději bych ji teda měl v ORMku…

Díky :)

hrach
Člen | 1834
+
+2
-

@Hanz25
obecne:

  1. ORM pri odstraneni entity se snazi vynulovat vsechy ukazatele, ktere na ni ukazuji. (Mazu autora, snazi se vynulovat book.author_id)
  2. Pokud je dany vztah oznacen jako cascade=[remove], tak se misto nulovani rovnou odstrani i entita, ktera dany ukazel ma. Tj. pri smazani autora se odstrani i kniha, pokud v phpdoc u autora je nastavena kaskada.
  3. Pokud vztah neni kaskadovy a zaroven dany ukazatel nema povoleno byt null → je vyhozena vyjimka.
  • parametr u metody remove zapina, jestli se ma resit kaskadove mazani. Pokud je false, Orm nebude mazat a ukladat zadne jine entity, pouze vynuluje property u ukazatelu a je tvou odpovednosti, abys je persistnul – tyto zmeny. Toto je dost specialni chovani, ktere muzes chtit pouzit v extremnich pripadech, proto neni vychozi. A dokumentace neni prilis dobra a aktualni – upravim.

K tvemu kodu:

  • mel bys upravit relationship u projektu, aby tasky smazal kaskadou, tj. * @property Tasks[]|NULL $tasks {1:m Task::$project, cascade=[persist, remove]}
  • mas definovanou kaskadu jen na remove, ale dost pravdepodobne ji chces zachovat i pro persist, coz je vychozi chovani! Viz https://nextras.org/…elationships#…

Editoval hrach (27. 12. 2016 9:17)