Nextras\ORM – ORM nad Nextras\Dbal

Mart78
Člen | 31
+
0
-

Mohl by mi někdo prosím poradit jak do orderBy zakomponovat virtuální property? Mám tabulku ve které si data seřazuji podle sloupce. Jeden sloupec odpovídá virtuální property, a nevím jak dle něj provést order by, ostatní sloupce nejsou problém protože properties v entity odpovídají reálnému sloupci. Jedná se o property $couponsCount v entity Category

Category.php

/**
 * @property		int			$id {primary}
 * @property		string		$name
 * @property		int			$priority
 * @property-read	int			$couponsCount {virtual}
 * @property		Coupon[]	$coupons {1:m Coupon::$category}
 */
class Category extends \Nextras\Orm\Entity\Entity
{
	protected function getterCouponsCount()
	{
		return count($this->coupons);
	}
}

Coupon.php

/**
 * @property		int			$id {primary}
 * @property		Category	$category {m:1 Category::$coupons}
 */
class Coupon extends \Nextras\Orm\Entity\Entity
{

}
hrach
Člen | 1834
+
0
-

@Mart78 moc nechapu co chces a jak chces radit? Jinak ta implementace copounsCount je takova, ze vytahne z databaze uplne vsechny entity a az pak je spocita. Doporucuji volat spis $this->copouns->countStored().

Editoval hrach (13. 10. 2017 15:27)

Mart78
Člen | 31
+
0
-

Díky za tip ohledně countu.
Mám tabulku (takový svůj jednoduchý datagrid) se stránkováním a sortováním. Data v ní tahám takto:

$this->orm->categories
	->findFiltered($filterQuery)
	->orderBy($this->orderBy, $this->orderDirection)
	->limitBy($this->itemsPerPage, $this->currentPage * $this->itemsPerPage);

kde $this->orderBy je např. ‚name‘ nebo ‚priority‘,
kde findFiltered mám v CategoriesMapper takto:

	public function findFiltered($query)
	{
		return $this->builder()->where('name LIKE %s', '%'.$query.'%');
	}

Seřazení podle např. name jde, protože je to vlastně reálný sloupec v DB, ale nenapadá mě jak mám provést orderBy dle toho počtu kupónů. Jdu na to celé špatně?

hrach
Člen | 1834
+
0
-

@Mart78 jasny, uz mi to cele doslo. Bohuzel virtualni property jsou jen pro cteni, ale neni mozno s nimi pracovat pri filtrovani. Razeni podle poctu prispevku pak nezbyva nez udelat na dbal vrstve, v Nextras Orm 3 pomoci custom function by to treba slo, nebo proste prepsat to do mapperu.

vymak
Člen | 92
+
0
-

Ahoj,
mám dotaz ohledně použití mapperu, se který si nevím rady.
Mám následující kód:

UserMapper

declare(strict_types = 1);

namespace App\Model;

use Nextras\Dbal\QueryBuilder\QueryBuilder;

final class UserMapper extends \Nextras\Orm\Mapper\Mapper
{

	public function findByEmail(string $email): QueryBuilder
	{
		return $this->builder()
			->where('email = %s', $email)
			->orWhere('secondaryEmail = %s', $email);
	}

}

UserRepository

declare(strict_types = 1);

namespace App\Model;

use Nextras\Orm\Collection\ICollection;

/**
 * @method ICollection|User[] findByEmail(string $email)
 */
final class UserRepository extends \App\Model\BaseRepository
{

	/**
	 * @return string[]
	 */
	public static function getEntityClassNames(): array
	{
		return [User::class];
	}

}

UserFacade

declare(strict_types = 1);

namespace App\Model\Facade;

use App\Model\User;

class UserFacade extends \App\Model\Facade\BaseFacade
{
	/** @var \App\Model\ORM */
	private $orm;

	/**
	 * @param string $email
	 *
	 * @return \App\Model\User[]|\Nextras\Orm\Collection\ICollection
	 */
	public function findByEmail(string $email)
	{
		return $this->orm->userRepository->findByEmail($email);
	}
}

Problém je, že když z UserFacade zavolám metodu findByEmail('nejaky@email.cz'); tak mi to vždy vrátí QueryBuilder, nicméně já potřebuji vrátit ICollection|User.

Dokázal by jste mi někdo poradit?

Nextras ORM mám ve verzi 3.0.0 b1 a PHP 7.1.10.

Editoval vymak (19. 10. 2017 10:45)

David Matějka
Moderator | 6445
+
0
-

@vymak ahoj, jednou zmenou ve verzi 3 je, ze se z mapperu vraci rovnou vysledek, tedy kolekce. ten return v mapperu tak v tomhle pripade staci obalit volanim $this->toCollection($qb)

vymak
Člen | 92
+
0
-

Super díky.
Nikde v dokumentaci jsem to právě nenašel, ale když jsem se teď díval na Migrate to 3.0 tak tam to je.

David Matějka napsal(a):

@vymak ahoj, jednou zmenou ve verzi 3 je, ze se z mapperu vraci rovnou vysledek, tedy kolekce. ten return v mapperu tak v tomhle pripade staci obalit volanim $this->toCollection($qb)

vymak
Člen | 92
+
0
-

Ahoj,
ještě bych se rád zeptal, zdali je možné se nějak dostat k datům, které nejsou nadefinované v Entitě.

Kupříkladu mám entitu:

ProjectForm

/**
 * @property int                    $id                         {primary}
 * @property string                 $name
 * @property string                 $nameClient
 * @property string                 $nameShopper
 * @property string                 $idNumber
 * @property string|null            $idNumberSk
 * @property int|null               $status                     {enum self::STATUS_*}
 * @property DateTimeImmutable      $created
 */
final class ProjectForm extends \App\Model\BaseEntity
{

	public const STATUS_NONE = 0,
		STATUS_NEW = 1,
		STATUS_CURRENT = 2,
		STATUS_FINISHED = 3;

}

Bez problému se tedy dostanu ke všem vlastnostem, co jsou u entity deklarované. U jedné velké aplikace mám však problém, že data uložená z aplikace jsou ukládána vždy do samostatné tabulky (například _project_id1, _project_id2), několik prvních vlastností je vždy stejné (pro ty jsem vytvořil entitu a v mapperu mám pouze metodu, kterou nastavuji název tabulky)

declare(strict_types = 1);

namespace App\Model;

class ProjectFormMapper extends \Nextras\Orm\Mapper\Mapper
{

	public function setProjectTableName(string $tableName): void
	{
		$this->tableName = $tableName;
	}

}

Problém ale nastává u vlastností, které nejsou definované v entitě a nemůžu se k nim žádným způsobem dostat. Je na to nějaký trik, nebo budu muset všechny data získávat klasicky přes Nextras\Dbal\Connection?

$entity->nameShopper; // OK
$entity->jinaVlastnostCoNeniVEntite; // Exception

Těším se na brzké odpovědi.

Libor.

Editoval vymak (23. 10. 2017 15:32)

David Matějka
Moderator | 6445
+
0
-

to ten navrh vypada velmi divoce :) nextras potrebuje znat metadata, takze by to asi slo nejak nahackovat a metadata upravit.

nebo by mohlo asi jit pouzit (respektive zneuzit) single table inheritance? ze bys mel X entit, ktere dedi od base a maji navic ty properties. priklad jak pouzit STI mozna v doc nebude, ale v testech by mel byt (klicove jsou metody getEntityClassNames a getEntityClassName

ale nejdriv bych se opravdu spise zamyslel nad tim navrhem databaze…

vymak
Člen | 92
+
0
-

S návrhem databáze nic udělat nejde, je to projekt co běží již přes 10 let a aktuálně obsahuje přes 5000 tabulek. Nakonec se mi podařilo metadata upravit přes

	if (!$entity->getMetadata()->hasProperty($name)) {
		$entity->getMetadata()->setProperty($name, $metadata);
	}

Ještě by mě ale zajímalo, jaký je rozdíl v:

	$entity->setValue('name', 'value');

a

	$entity->setRawValue('name', 'value');

Lze nějak docílit toho, aby data v $entity->getValue() a $entity->getRawValue() byla odlišná? (např. timestamp a DateTime object?)

Libor.

David Matějka napsal(a):

to ten navrh vypada velmi divoce :) nextras potrebuje znat metadata, takze by to asi slo nejak nahackovat a metadata upravit.

nebo by mohlo asi jit pouzit (respektive zneuzit) single table inheritance? ze bys mel X entit, ktere dedi od base a maji navic ty properties. priklad jak pouzit STI mozna v doc nebude, ale v testech by mel byt (klicove jsou metody getEntityClassNames a getEntityClassName

ale nejdriv bych se opravdu spise zamyslel nad tim navrhem databaze…

David Matějka
Moderator | 6445
+
0
-

asi by ti mohlo pomoci property container, jak to treba muze vypadat pro enum: https://gist.github.com/…bbfc947a64e4 (jen to teda bude potreba trochu upravit v novem nextras/orm, nektere nove metody tam nejsou implementovane)


jo a jeste vic low level reseni by mohly byt modifikatory pro ty sloupecky na urovni storage reflection https://github.com/…flection.php#L287

pak bys v dbal registroval custom modifier, co ti prevede datatime na timestamp https://github.com/…rocessor.php#L69

EchoZulu
Člen | 4
+
0
-

Mám tabulky category a item (n:m) a vazební tabulku category_X_item.
Jak získat pro danou kategorii polozky seřazené podle category_X_item.sort?

vymak
Člen | 92
+
0
-

Ahoj,
lze nějak jednoduše zapsat NOT IN v findBy?

	// IN funguje
	$this->orm->userRepository->findBy([
		'id' => [1, 2, 3, 4, ...],
	]);

	// jak zapsat NOT IN?
	// zkoušel jsem 'id!=' ale to nejde

Díky.

hrach
Člen | 1834
+
0
-

@vymak melo by jit, co znamena nejde? a jaka verze?

vymak
Člen | 92
+
0
-

hrach napsal(a):

@vymak melo by jit, co znamena nejde? a jaka verze?

Zkoušel jsem to znovu a už mi to funguje.
Jedná se o verzi 3.0 b1.

Skřetík
Člen | 11
+
0
-

Ahoj, občas se stane, že věci jsou poukládány různě ve vazebních tabulkách. Řečeno jinak, mám např. tabulku produktů, a k té mám oneHasMany product_price. Když potřebuji „jen“ správnou cenu, tak si ji vytáhnu z vazby přes getterPrice a tam zavolám getBy.

Jenže co když podle ní potřebuji filtrovat nebo řadit? Nacpat tam this->prices->type ⇒ ‚MOC‘ a ‚this->prices->value>=‘ ⇒ 2000, nebo něco podobného? Nebo jak tyto věci „nejlépe“ řešit? Obecně pro „jednoduché“ načítání mi Nextras ORM přijde hrozně intuitivní a super, ale jak se to začne pléct do moc tabulek tak nikdy neznám optimální cestu – jestli to plácat přes vlastní mappery a tak. Nebo vůbec, složitější filtrování u eshopu, tam mě vůbec nenapadá jak to řešit (nebo spíše napadá spousta různých cest jakt o řešit, ale žádná ideální). V tomhle ohledu mi vůbec v dokumentaci chybí trochu příklady.

hrach
Člen | 1834
+
0
-

@Skřetík Jestli to dobře chápu, tak potřebuješ ve vztahu 1:M nějak vyfiltrovat jeden z těch M a podle jeho property nějak řadit.

Asi muzes zkusit neco jako

$prices->findBy(['this->prices->type' => 'something'])
     ->orderBy('this->prices->value');

Pokud by to vyzadovalo slozitejsi SQL dotaz, pak doporucuji mrknout na Custom Funkce. Diky tomu muzes napsat implementaci pro PHP i SQL. A treba to pak volat $prices->applyFunction(OrderBySomethingPrice::class).

vladimir.biro
Člen | 163
+
0
-

wassy napsal(a):

hrach napsal(a):

Entity vytazene z databaze jsou uz attachovane. V demu se k nove vytvarenemu komentu pripojuje post, ktery je uz v db a je attachovany.

Já mám ale ten projekt už taky vytvořený, ve formuláři jen vybírám ze selectu ke kterému projektu se ten task připojí, achjo :D já asi pořád nechápu jak to funguje :D

edit: Tak už to jde :D a dokonce mi to i dává smysl :D

Ahoj. Mohol by si prosim ta prezradit, ako si to nakoniec spravil? Riesim ten isty problem ako ty a asi tiez nechapem ako to funguje :D

Vo formulari vyberam zo selectu z inej tabulky (1:n) a neviem, ako zostavit ten orm prikaz na pridanie noveho znznamu. ALebo mam problem v nastaveni entity. Neviem.

// Generovanie formularu
// ...
$form->addText('dateStart', '')->setRequired('Vyplnte dátum prosím');
$form->addSelect('workShifts', '', $this->orm->workShifts->findWorkShift()->fetchPairs('id', 'name'));
// ...


// Ulozenie fomrularu
public function addWorkDayData($values)
{
	Debugger::barDump($values); // <-- OK, vypise hodnoty
	$workDay = new WorkDay();

	$workDay->dateStart = $values->dateStart;
	$workDay->workShifts = $values->workShifts;

	$this->orm->workDays->persistAndFlush($workDay);
}




// Orm Entity:
/**
 * WorkDay
 * ...
 * @property WorkShift			$workShifts	{m:1 WorkShift::$workDays}
 */



/**
 * WorkShift
 * ...
 * @property OneHasMany|WorkDay[]	$workDays	{1:m WorkDay::$workShifts}
 */

Tracy: Entity is not attached to repository.

Dik za akekolvek info.

Editoval vladimir.biro (22. 12. 2017 8:57)

vladimir.biro
Člen | 163
+
0
-

vladimir.biro napsal(a):

wassy napsal(a):

hrach napsal(a):

Entity vytazene z databaze jsou uz attachovane. V demu se k nove vytvarenemu komentu pripojuje post, ktery je uz v db a je attachovany.

Já mám ale ten projekt už taky vytvořený, ve formuláři jen vybírám ze selectu ke kterému projektu se ten task připojí, achjo :D já asi pořád nechápu jak to funguje :D

edit: Tak už to jde :D a dokonce mi to i dává smysl :D

Ahoj. Mohol by si prosim ta prezradit, ako si to nakoniec spravil? Riesim ten isty problem ako ty a asi tiez nechapem ako to funguje :D

Vo formulari vyberam zo selectu z inej tabulky (1:n) a neviem, ako zostavit ten orm prikaz na pridanie noveho znznamu. ALebo mam problem v nastaveni entity. Neviem.

// Generovanie formularu
// ...
$form->addText('dateStart', '')->setRequired('Vyplnte dátum prosím');
$form->addSelect('workShifts', '', $this->orm->workShifts->findWorkShift()->fetchPairs('id', 'name'));
// ...


// Ulozenie fomrularu
public function addWorkDayData($values)
{
	Debugger::barDump($values); // <-- OK, vypise hodnoty
	$workDay = new WorkDay();

	$workDay->dateStart = $values->dateStart;
	$workDay->workShifts = $values->workShifts;

	$this->orm->workDays->persistAndFlush($workDay);
}




// Orm Entity:
/**
 * WorkDay
 * ...
 * @property WorkShift			$workShifts	{m:1 WorkShift::$workDays}
 */



/**
 * WorkShift
 * ...
 * @property OneHasMany|WorkDay[]	$workDays	{1:m WorkDay::$workShifts}
 */

Tracy: Entity is not attached to repository.

Dik za akekolvek info.

Takze si odpoviem sam :)

// Generovanie formularu
// ...
$form->addText('date_start', '')->setRequired('Vyplnte dátum prosím');
$form->addSelect('work_shifts_id', '', $this->orm->workShifts->findWorkShift()->fetchPairs('id', 'name'));

// ...


// Ulozenie fomrularu
public function addWorkDayData($values)
{
	Debugger::barDump($values); // <-- OK, vypise hodnoty
	$workDay = new WorkDay();

	$workDay->dateStart = $values->date_start;
	$workDay->workShift = $this->orm->workShifts->getById($values-> work_shifts_id);

	$this->orm->workDays->persistAndFlush($workDay);
}




// Orm Entity:
/**
 * WorkDay
 * ...
 * @property WorkShift			$workShift		{m:1 WorkShift::$workDays}
 */



/**
 * WorkShift
 * ...
 * @property OneHasMany|WorkDay[]	$workDays	{1:m WorkDay::$workShift}
 */
nocturne32
Člen | 21
+
0
-

Ahoj, zkouším orm podle dokumentace a nějak jsem se seknul na tomto:
https://nextras.org/…l-definition#…

  1. Trošku mě zarazilo, že tam už není definovaný model u nextras.orm, ale asi tam má být, ne?
  2. Když to teda mám, tak to getRepository(UserRepository::class)->getById($id) mi vyhodí výjimku, že repozitář neexistuje, ale jen v tom případě, když v tom Orm (extends Model) nemám definované @property s tím repozitářem. Když tam je definovaný jako @property UserRepository, tak je to ok. Ale to se opět vracím k tomu způsobu nad tím, ne?

A nebo mi něco hrozně utíká a vůbec netuším co. :D

David Matějka
Moderator | 6445
+
+1
-

@nocturne32 to koukas na druhy zpusob, jak nextras orm pouzivat, respektive jak tam definovat entity. pro bezne pouziti spise koukej na tu sekci nad tim, kde mas prave vlastni „Orm“ tridu, kde mas @property anotace.

nocturne32
Člen | 21
+
0
-

@DavidMatějka to vím, že to byl právě druhý způsob, jen mě zajímalo jak bych to mohl rozchodit tím způsobem (že to dynamicky hledá repozitare), ale dám na tebe a budu tedy používat ten první způsob. Díky :)

David Matějka
Moderator | 6445
+
+1
-

@nocturne32 jo tak, tak to jsem te spatne pochopil.

dle tveho popisu chyby to vypada, ze si mozna nenastavil ten repositoryFinder, jelikoz s tim DIRepositoryFinderem by nemely fungovat @property anotace u Orm tridy.

takze.

  • je potreba nastavit repositoryFinder
  • registrovat ten repository jako sluzbu
majky358
Člen | 37
+
0
-

Na webe problém s certifikátom .. :/

Po update na RC1

Service ‚nextras.orm.mappers.mymapping‘: Service of type Nextras\Orm\Mapper\Dbal\DbalMapperCoordinator needed by Nextras\Orm\Mapper\Dbal\DbalMapper::__construct()

beta1
Nextras\Dbal\Platforms\CachedPlatform::__construct() must implement interface Nextras\Dbal\Platforms\IPlatform, instance of Nextras\Dbal\Connection given

Ktorá Dbal verzia s Orm je funkčná pri jednotlivých verziách ? Dik

Editoval majky358 (25. 12. 2017 18:58)

hrach
Člen | 1834
+
0
-

@majky358 pouzij latest verze obeho, tzn. RC orm a stable dbalu :)

vladimir.biro
Člen | 163
+
0
-

Ahojte. V presenteri mam handle na pridanie zaznamu do DB:

$cycle = new Cycle();
$cycle->report = $report;
$cycle->from = clone $today;
$cycle->to = clone $today;

$this->orm->cycles->persistAndFlush($cycle);

co funguje super a zaznam sa v pohode prida. No problen nastane potom, ze po kazdom refresi stranky, alebo opatovnom navrate na danu podstranku (kludne mozem pomedzi to klikat kade tade po webe) sa mi opat prida zaznam do DB.

Uplne najlepise je, ze sa to nedeje vzdy. Len obcas to zacne robit a trva to nejaky cas. Potom to samo od seba prestane.

Handle sa nespusta, to som otestoval. Dokonca ked uz tento probelm zace a ja jasledne zakomentujem kod na pridavanie do DB, tak aj tak mi tam pridava riadky.

Stretli ste sa s tymto javou uz niekedy prosim? Dakujem za kazde info :)

Ondřej Kubíček
Člen | 494
+
0
-

@vladimir.biro pokud říkáš, že to zakomentuješ a přidávají se záznamy pořád, musíš ten kód mít ještě někde jinde, zkus vyhledat, kde máš použit persistAndFlush

Mart78
Člen | 31
+
0
-

Je možní nějak nechat si vrátit výsledky dotazu jako obyč. ArrayHash/ActiveRow místo vytváření entity? Např když tahám data pro grafy, kde mi entity nesedí. Nebo musím použít např Nette Database?

hrach
Člen | 1834
+
+1
-

@Mart78 na to bude nejlepsi si nawirovat do servisy rovnou Nextras\Dbal\IConnection (nebo Connection v 2.x) a pouzit normalni query() metodu.

stajo
Člen | 8
+
0
-

Zdravim,
ked ukladam DateTime cez ORM, posuva mi ho o hodinu dozadu. Cez Nette Databaze to ulozi dobre. Neviete co mam zle?

Etita:

namespace Model\Entity;
use Nextras\Dbal\Utils\DateTime;

/**
 * @property int                    $id         {primary}
 * @property DateTime               $time
 */
class Scan extends BaseEntity
{

}

pouzitie:

$time = DateTime::createFromFormat(\Utils\DateTime::FORMAT_DATE_TIME, $data->time);  // v $time je spravny cas s timezone Europe/Prague
$scan = new Scan();
$scan->time = $time;
$this->model->persistAndFlush($scan);  // do DB pride o hodinu menej
Ondřej Kubíček
Člen | 494
+
+1
-

Podle dokumentace https://nextras.org/…s/3.0/entity bys měl posílat DateTimeImmutable jinak to na ten object konvertuje a tipnu si, že to bude ten důvod proč tam je hodina posun

stajo
Člen | 8
+
0
-

upravil som to takto:

/**
 * @property int                            $id         {primary}
 * @property \DateTimeImmutable|null        $time       {default now}
 */
class Scan extends BaseEntity
{
}

$scan = new Scan();
$this->model->persistAndFlush($scan);

, aj tak to da o hodinu menej

Ondřej Kubíček
Člen | 494
+
-1
-

tak pak už to budeš muset debugovat, abys zjistil kde se ten datum posune

David Matějka
Moderator | 6445
+
+3
-

@stajo

ahoj, mas spravne nastavenou timezonu pro db spojeni apod? precti si o tom v doc: https://nextras.org/…2.1/datetime (jelikoz nepouzivas DateTimeImmutable, tak predpokladam, ze pouzivas verzi 2.x, kdyztak si nahore prepni na verzi 3, kde se to trochu menilo)

a pridej se kdyztak na slack pehapkaru, kde v mistnosti #nextras ti urcite pomuzeme :)

Hanz25
Člen | 38
+
+1
-

Jojo také jsem s tím měl problém s verzí 2, ale ve 3 už je to v pohodě.

EchoZulu
Člen | 4
+
0
-

Zdravím,

Chtěl bych se zeptat na vynucení opětovného seřazení vazby OneHasMany, potom co provedu aktualizaci řazení.

Mám dvě tabulky

Tree

  • @property TreeFile[] $files {1:m TreeFile::$tree, orderBy=[sort=ASC], cascade=[persist, remove]}

TreeFile

  • @property Tree $tree {m:1 Tree::$files}
  • @property int $sort
		$obj = $this->orm->tree->getById(1);

		$i = 0;
		//zmenim poradi $files (první bude posledni)
		foreach ($obj->files as $item) {
			if ($i == 0) {
				$item->sort = 1;
			} else {
				$item->sort = 0;
			}
			$i++;
		}

		$this->orm->persistAndFlush($obj);


		// u itemu se zmenila hodnota sortu
		// je i spravně uložená v DB
		// ale seřazení $obj->files zustane stejné jako při prvním vypsání
		foreach ($obj->files as $item) {
			dump($item->sort);
		}

Jak zaručit to, aby se znovu seřadilo $obj->files?

Díky za radu

Editoval EchoZulu (16. 1. 2018 13:30)

David Matějka
Moderator | 6445
+
+1
-

@EchoZulu zkus na files (coz je OneHasMany relationship) zavolat explicitne ->get(), coz vrati kolekci

EchoZulu
Člen | 4
+
0
-

@DavidMatějka ano funguje – díky

romiix.org
Člen | 343
+
0
-

Zdravím, nedarí sa mi dohľadať zápis pre nájdenie všetkých report ktoré majú aspoň jeden nezmazaný report_item.

Ako to zapísať?

Dostal som sa k:

// ReportRepository
public function findNonEmptyReport()
{
	return $this->findBy([
		'this->items->id!=' => NULL,
		'this->items->deleted' => NULL,
	]);
}

Nie je to úplne to čo by som chcel:

SELECT DISTINCT `report`.* FROM `report` AS `report`
LEFT JOIN report_item AS `items` ON (`report`.`id` = `items`.`report_id`)
WHERE (`items`.`id` IS NOT NULL) AND (`items`.`deleted` IS NULL)

Vďaka!

Štruktúra

report

  • id

report_item

  • id
  • report_id
  • deleted (datetime|NULL)

Editoval romiix.org (20. 1. 2018 13:41)

hrach
Člen | 1834
+
0
-

@romiix.org obecne na repository vrstve zatim neni mozno delat zadne agregacni funkce (v kontextu orm davaji vyznam jen na relationships, coz je to, kde to chces i ty).

resit se to da nasledovne:

  • custom filter function v orm 3.0
  • na mapper vrstve
Martin
Člen | 171
+
0
-

Ahoj. Narazil jsem na problém. Asi půjde o nějaký překlep v anotacích, nebo moji základní neznalost. Ale už s tím bojuju několik dní a nevím si rady. Ze struktury databáze z Nextras\orm-demo jsem pomocí poloautomatického generátoru získal soubory, které se podle všech mých dosavadních zjištění liší od originálního dema jen v použitých namespace a v množném čísle u názvů entit. Originální i generovaný projekt mi pracují se stejnou databází, přesto ten generovaný vypisuje články i tagy, ale ne komentáře. Přitom je normálně ukládá, v originálním demu jsou pak vidět. Při čtení dávají oba do databáze naprosto shodné dotazy:

SET sql_mode = 'TRADITIONAL'
SET time_zone = 'Europe/Prague'
SELECT `posts`.* FROM `posts` AS `posts` WHERE (`posts`.`id` = '2')
SELECT `posts_x_tags`.`tag_id`, `posts_x_tags`.`post_id` FROM `tags` AS `tags` LEFT JOIN `posts_x_tags` AS `posts_x_tags` ON (`posts_x_tags`.`tag_id` = `tags`.`id`) WHERE `posts_x_tags`.`post_id` IN (2)
SELECT `tags`.* FROM `tags` AS `tags` WHERE (`tags`.`id` IN (4))
SELECT `comments`.* FROM `comments` AS `comments` WHERE `comments`.`post_id` IN (2)
SELECT `tags`.* FROM `tags` AS `tags`

Comments.php

namespace App\Model\Comments;

use App\Model\Entity\AbstractEntity;
use App\Model\Posts\Posts;
use Nextras\Dbal\Utils\DateTimeImmutable;

/**
 * @property int $id {primary}
 * @property Posts $postId {m:1 Posts::$allComments}
 * @property string|NULL $name
 * @property string|NULL $email
 * @property string $content
 * @property DateTimeImmutable $createdAt {default now}
 * @property DateTimeImmutable|NULL $deletedAt
 */
class Comments extends AbstractEntity
{
}

Posts.php:

namespace App\Model\Posts;

use App\Model\Entity\AbstractEntity;
use App\Model\Comments\Comments;
use App\Model\Tags\Tags;
use Nextras\Dbal\Utils\DateTimeImmutable;

/**
 * @property int $id {primary}
 * @property string $title
 * @property string $content
 * @property DateTimeImmutable $createdAt {default now}
 * @property OneHasMany|Comments[] $allComments {1:m Comments::$postId}
 * @property ManyHasMany|Tags[]          $tags        {m:m Tags::$postId, isMain=true}
 */
class Posts extends AbstractEntity
{
}

HomepagePresenter.php:

	/** @var Post */
	private $post;

...

	public function actionDetail($id)
	{
		$post = $this->orm->posts->getById($id);
		if (!$post) {
			$this->error();
		}
		//TADY UŽ JE $post->allComments->fetchAll() PRÁZDNÉ!
		$this->post = $post;
	}


	public function renderDetail()
	{
		$this->template->post = $this->post;
	}

...

Jen pro úplnost ostatní soubory:

detail.latte:

{block content}

<h1>{$post->title}</h1>

<p>Tags: <span n:foreach="$post->tags as $tag">{$tag->name}{sep}, {/sep}</span></p>

<p>{$post->content}</p>

<hr>

<h4>Comments:</h4>

{*foreach $post->comments as $comment*}
{foreach $post->allComments as $comment}
	<div class="comment">
		<span>{$comment->name}, {$comment->email}</span>
		<a n:href="deleteComment! $comment->id">delete comment</a>
		<p>{$comment->content}</p>
	</div>
{/foreach}

{control addCommentForm}

<hr>

{control updateTagsForm}

AbstractEntity.php:

namespace App\Model\Entity;

use Nextras\Orm\Entity\Entity;

abstract class AbstractEntity extends Entity
{
}

AbstractRepository:

namespace App\Model\Repository;

use Nextras\Orm\Repository\Repository;

abstract class AbstractRepository extends Repository
{
}

AbstractMapper.php:

namespace App\Model\Mapper;

use Nextras\Orm\Mapper\Mapper;

abstract class AbstractMapper extends Mapper
{
}

Orm.php:

namespace App\Model;

use Nextras\Orm\Model\Model;


/**
 * Model
 *
 * @property-read Tags\TagsRepository      $tags
 * @property-read Comments\CommentsRepository  $comments
 * @property-read Posts\PostsRepository     $posts
 */
class Orm extends Model
{
}

CommentsRepository:

namespace App\Model\Comments;

use App\Model\Repository\AbstractRepository;

class CommentsRepository extends AbstractRepository
{
	/**
	 * @return array
	 */
	public static function getEntityClassNames(): array
	{
		return [Comments::class];
	}
}

PostsRepository:

namespace App\Model\Posts;

use App\Model\Repository\AbstractRepository;
use Nextras\Orm\Collection\ICollection;

class PostsRepository extends AbstractRepository
{
	public function findHomepageOverview()
	{
		return $this->findAll()->orderBy('createdAt', ICollection::DESC);
	}

	/**
	 * @return array
	 */
	public static function getEntityClassNames(): array
	{
		return [Posts::class];
	}
}

CommentsMapper.php:

namespace App\Model\Comments;

use App\Model\Mapper\AbstractMapper;

class CommentsMapper extends AbstractMapper
{
}

PostsMapper.php:

namespace App\Model\Posts;

use App\Model\Mapper\AbstractMapper;

class PostsMapper extends AbstractMapper
{
}

Editoval Martin (10. 2. 2018 0:38)

hrach
Člen | 1834
+
0
-

@Martin zkontroluj, jestli mas spravne cizi klice, taky by me zajimalo, jestli to spousti spravny dotaz.

Martin
Člen | 171
+
0
-

hrach napsal(a):

@Martin zkontroluj, jestli mas spravne cizi klice, taky by me zajimalo, jestli to spousti spravny dotaz.

Ahoj. Děkuju za odpověď, byl jsi rychlejší, než jsem příspěvek dopsal. Bojuju tady s formátováním, nedaří se mi vhodně vložit SQL kód, ale snad je nahoře vidět, nakonec jsem ho označil jako php. Oproti původnímu demu používají obě aplikace (demo i generovaná) celý výsledek přes allComments(), protože jsem původně myslel, že je to výběrem. Ale jen demo ty komentáře zobrazí.

Když dotaz

SELECT `comments`.* FROM `comments` AS `comments` WHERE `comments`.`post_id` IN (2)

vložím do admineru, dá mi naprosto správný výsledek včetně komentářů.

Struktura databáze by měla být původní z originálního dema, pokud jsem tam něco omylem nepřepsal:

-- Adminer 4.5.0 MySQL dump

SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';

DROP TABLE IF EXISTS `comments`;
CREATE TABLE `comments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `post_id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `content` text NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `deleted_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `post_id` (`post_id`),
  CONSTRAINT `comments_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `migrations`;
CREATE TABLE `migrations` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `group` varchar(100) COLLATE utf8_czech_ci NOT NULL,
  `file` varchar(100) COLLATE utf8_czech_ci NOT NULL,
  `checksum` char(32) COLLATE utf8_czech_ci NOT NULL,
  `executed` datetime NOT NULL,
  `ready` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `type_file` (`group`,`file`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;


DROP TABLE IF EXISTS `posts`;
CREATE TABLE `posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `posts_x_tags`;
CREATE TABLE `posts_x_tags` (
  `post_id` int(11) NOT NULL,
  `tag_id` int(11) NOT NULL,
  PRIMARY KEY (`post_id`,`tag_id`),
  KEY `posts_x_tags_ibfk_2` (`tag_id`),
  CONSTRAINT `posts_x_tags_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`),
  CONSTRAINT `posts_x_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `tags`;
CREATE TABLE `tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- 2018-02-09 22:34:32

Editoval Martin (9. 2. 2018 23:53)

Martin
Člen | 171
+
0
-

Ještě bych se zeptal – kdy a kde se nakonec v dbal pokládá dotaz do databáze a kde se dostanou výsledná data do orm? Programuji to ve VS Code, které mi zatím některé anotace neumí spojit s definicemi a pod debuggerem se mi zatím nepodařilo zachytit okamžik čtení dat z databáze a co přesně se s daty děje dál. Jinak bych už asi přišel na to, kde se přečtené komentáře ztratí. Myslím, že tady:
Čtení z databáze
jsou ještě komentáře načtené správně, počty sloupců i řádků souhlasí.
Zatím mám pocit, že v hydrateEntity(array $data) se mi plní správnými daty správná entita pro komentáře, ale už se to pak nějak nepřiřadí k postu. Na řádku return $this->identityMap->create($data); je v $data pětkrát vždy správně naplněný jeden komentář.

Editoval Martin (10. 2. 2018 5:03)

hrach
Člen | 1834
+
0
-

@Martin podezrele jsou definice * @property Posts $postId {m:1 Posts::$allComments}, zkus to bez toho Id, standardne Orm stripuje ty prefixy, pokud je tam cizi klic, tak proto ti do toho relationshipu asi nenacita zadny data.

Martin
Člen | 171
+
0
-

hrach napsal(a):

@Martin podezrele jsou definice * @property Posts $postId {m:1 Posts::$allComments}, zkus to bez toho Id, standardne Orm stripuje ty prefixy, pokud je tam cizi klic, tak proto ti do toho relationshipu asi nenacita zadny data.

Děkuju, bylo to tím. Zajímavé je, že pro vazbu m:n mezi Tags a Posts ten samý zápis funguje, proto mě nenapadlo hledat chybu zrovna tam.

Předpokládám, že se něco změnilo od prvních verzí, protože generátor vyvíjím jako interaktivní fork několik let starého generátoru contributte\nextras-orm-generator , který tehdy zřejmě fungoval.

Až to budu mít kompletní, dám sem výsledek k vyzkoušení. Mělo by to umět na základě hotové databáze a „klikací“ interakce s uživatelem vytvořit:
1. funkční základní kostru modelu s Nextras\ORM (nebo na přání jen Nette\Database – to už mám skoro hotové, ale s problémy s vazbami m:n a synchronizací, proto to možná nakonec vyhodím a nechám jen ORM),
2. formuláře pro změny entit včetně vazeb,
3. šablony pro základní výpisy,
4. vše s přípravou pro jazykové mutace,
5. přípravu pro uživatelské role a oddělení backendu.

Asi to nebude nic pro zkušené Nette programátory, ale může to pomoci rychle připravit kostru aplikace lidem, kteří se k Nette dostanou jen výjimečně, jako jsem i já. A kteří nemají IDE, ve kterém by něco takového bylo jako plugin.

Editoval Martin (10. 2. 2018 21:52)

batko
Člen | 219
+
0
-

Ahoj,

měl bych dotaz ohledně dofiltrovávání.

Mám autora a ten napsal několik knih během 50ti let. Ale já bych rád vypsal jen knihy od roku 10 do roku 20. Existuje nějaké elegantní řešení?

<?php
foreach($author->books as $book){
echo $book->title;
}
?>
hrach
Člen | 1834
+
+1
-

@batko

$books = $author->books->get()->findBy(['published>' => $from, 'published<' => $to]);
foreach ($books as $book) {}
w3r0
Člen | 4
+
0
-

Zdravim, do teraz mi vsetko fungovalo a podla navodu som spravil skoro cely projekt. Ostal mi vsak problem s ciselnikmi.

Error:

Property App\Model\Codelist\CSkill\CSkillLocale::$id is not nullable.

Entita: CSkill

<?php

namespace App\Model\Codelist\CSkill;

use App\Model\Codelist\CodeBook;
use App\Model\Customer\Customer;
use Nextras\Orm\Entity\ToArrayConverter;
use Nextras\Orm\Relationships\ManyHasOne;
use Nextras\Orm\Relationships\OneHasMany;

/**
 * @property int|null $id {primary}
 * @property string $name
 * @property int $deleted {default "0"}
 * @property ManyHasOne|Customer $customer {m:1 Customer, oneSided=true}
 * @property OneHasMany|CSkillLocale[] $locales {1:m CSkillLocale::$id}
 */
class CSkill extends CodeBook
{
}

Entita: CSkillLocale

<?php

namespace App\Model\Codelist\CSkill;

use App\Model\Codelist\CodeBook;
use Nextras\Orm\Relationships\ManyHasOne;

/**
 * @property ManyHasOne|CSkill $id {m:1 CSkill::$locales} {primary}
 * @property string $lang {default 'sk'} {primary}
 * @property string $name
 */
class CSkillLocale extends CodeBook
{

}

MariaDb Tabulky:

CREATE TABLE `c_skill` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `customer_id` int(10) unsigned DEFAULT NULL,
  `deleted` tinyint(1) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `IX_c_skill_c_skill_category_id` (`c_skill_category_id`),
  KEY `IX_c_skill_customer_id` (`customer_id`),
  CONSTRAINT `FK_c_skill_c_skill_category_id` FOREIGN KEY (`c_skill_category_id`) REFERENCES `c_skill_category` (`id`),
  CONSTRAINT `FK_c_skill_customer_id` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=385 DEFAULT CHARSET=utf8 COMMENT='OK';

CREATE TABLE `c_skill_locale` (
  `id` int(10) unsigned NOT NULL,
  `lang` char(2) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`,`lang`),
  KEY `IX_c_skill_locale_lang` (`lang`) USING BTREE,
  CONSTRAINT `FK_c_skill_locale_id` FOREIGN KEY (`id`) REFERENCES `c_skill` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `FK_c_skill_locale_lang` FOREIGN KEY (`lang`) REFERENCES `c_language` (`shortcut`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='OK';
David Matějka
Moderator | 6445
+
0
-

@raddy668 mas tu entitu registrovanou v modelu ?