Nextras\ORM – ORM nad Nextras\Dbal
- Mart78
- Člen | 31
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
{
}
- Mart78
- Člen | 31
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 | 1838
@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
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
@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
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
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
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
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
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
- Skřetík
- Člen | 11
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 | 1838
@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
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
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
Ahoj, zkouším orm podle dokumentace a nějak jsem se seknul na tomto:
https://nextras.org/…l-definition#…
- Trošku mě zarazilo, že tam už není definovaný model u nextras.orm, ale asi tam má být, ne?
- 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
@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
@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
@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
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)
- vladimir.biro
- Člen | 163
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
@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
- stajo
- Člen | 8
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
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
- David Matějka
- Moderator | 6445
@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 :)
- EchoZulu
- Člen | 4
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
@EchoZulu zkus na files (coz je OneHasMany relationship) zavolat
explicitne ->get()
, coz vrati kolekci
- romiix.org
- Člen | 343
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)
- Martin
- Člen | 171
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)
- Martin
- Člen | 171
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
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:
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)
- Martin
- Člen | 171
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)
- w3r0
- Člen | 4
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';