Bezbolestná integrace Doctrine 2 ORM do Nette – Kdyby/Doctrine
- Filip Procházka
- Moderator | 4668
akadlec napsal(a):
pepakriz, Filip Procházka ale jž to používám na kolekci…
Ehm… Když si přečteš dokumentaci doctrine, tak zjistíš že best
practise je, aby kolekce nebyly pristupne z venku entity. Tedy
BaseEntity
se presne takto chova. Výchozí chování ti na
$group->getUsers()
nevrátí kolekci ale pole těch uživatelů. Tedy máš dvě možnosti, vystavit kolekci ven (nedoporučuje se, pokud to není nutné)
public function getUsers()
{
return $this->users; // vystaví ven surovou kolekci
}
Nebo si napsat tuhle metodu do groupy
public function getBirthdayUsers()
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq("birthday", "1982-02-17"))
->orderBy(array("username" => Criteria::ASC))
->setFirstResult(0)
->setMaxResults(20)
;
return $this->users->matching($criteria);
}
- Jiří Nápravník
- Člen | 710
Mám nějaký balík slov, které chci nahrát do databáze, ale nad jedním sloupcem mám UNIQUE index. Myslel jsem, že nejlepší řešení bude vzít a poslat to všechno postupně na $dao->save($entity); a odchytím jen DuplicateEntryException a vypíšu, co nebylo uloženo. Jenže ejhle dostávám v tomhle případě exception u dalšího, že EntityManager was closed.
Či-li je nějaká metoda, jak tohle řešit, než nejprve kontrolovat všechny, jestli už náhodou v db nejsou a pokud nejsou tak pak teprve nahrávat?
- Filip Procházka
- Moderator | 4668
Doctrine to vůbec neumí, ale Kdyby/Doctrine ano ;) Slouží k tomu metoda EntityManager::safePersist(), jen dávej pozor, instance co se ti vrátí nebude identita té co vložíš (tu původní musíš zahodit).
- Jiří Nápravník
- Člen | 710
@Filip: wow, super práce. Čím dál více mě kdyby komponenty překvapují:-)
Jen pro info, kdyby bylo někdy v budoucnu třeba nastartovat nový EntityManager, jak to v tomhle případě udělat?
- Filip Procházka
- Moderator | 4668
Symfony to dělá tak, že si vytvoří „registr“ a v něm má entity manager. Když se zamkne, může snadno EM z registru smazat a zase ho v něm vytvořit, protože se počítá s tím, že služby používají registr, nikoliv EM. Blbý je, že to přidává další vrstvu a zhoršuje použití.
Dřív jsem to v Kdyby měl, ale jednak jsem nikdy nepotřeboval EM odemykat a druhak to bylo fakt nahouby. Lepší je zamknutí předcházet.
A kdyby ti někdo radit hacknout EntityManager tak aby se neuměl zamknout (což jde celkem snadno) tak to nedělej, protože se zamyká z dobrého důvodu. UnitOfWork se při výjimce dostane do nekonzistentního stavu a mohl by sis poškodit data hodně náhodným způsobem.
- Jiří Nápravník
- Člen | 710
Jj, právě jsem narážel na nějaký registr, když jsem googlil a říkal si, že ho v Kdyby nikde nevidím. Ale souhlasím, že je lepší tomu zamknutí předcházet, když ta možnost je.
- jedelex
- Člen | 16
Zdravim,
chcel by som si oddelit metody ktore patria k DAO konkretnej entity (find, findBy , save ..) od metod pomocou ktorych robim DQL,Query Builder alebo nativne SQL (createQuery, createQueryBuilder,createNativeQuery ) a nevim si s tym rady ?
Pre lepsie porozumnie mam v configu napr. takuto service :
categorie: App\CategoryDao(@doctrine.dao(Entity\Category))
Potom v presenteri si napojim dany model :
<?php
public function injectCategoryDao(App\CategoryDao $categoryDao)
{
$this->categoryDao = $categoryDao;
}
?>
A nasledne uz s nim mozem pohodlne pracovat :
<?php
$cateogry = $this->categoryDao->find($catID);
?>
Ale co mi vadi a co chcem oddelit su spominane metody pre DQL,Query Builder alebo nativne SQL aby som nerobil v presenteri nieco taketo :
<?php
$dql = "SELECT a, c FROM Article a JOIN a.category c"
$result = $this->categoryDao->createQuery($dql);
?>
kedze logicky DQL ci nativne SQL sa nerobi len nad jednou konkretnou entitou a potom ten zapis je matouci , chcel by som docilit neco takoveho, kde property napr. em predstavuje instanciu tridy v ktorej mam ulozene vsetky spominane metody pre pracu s DQL,Query Builder a SQL:
<?php
$this->em->createQuery($dql);
?>
Dufam ze som to napisal zrozumitenlne a dakujem za rady :)
- akadlec
- Člen | 1326
Mužeš si buď udělat model ve kterém budeš mít konkrétní DAO a tam si uděláš konkrétní metody třeba i s tím nativní DQL a nebo k tomu můžeš použít QueryObject
- akadlec
- Člen | 1326
@jedelex: tipuju že Filip Procházka ma fasády jako já modely. Takže vytvoříš si službu, které předá DAO pro konkrétní entitu a tvoříš si tam jednotlivé metody:
class UserModel extends \Nette\Object
{
/**
* @var \Kdyby\Doctrine\EntityDao
*/
protected $dao;
public function __construct(\Kdyby\Doctrine\EntityDao $dao)
{
$this->dao = $dao;
}
/**
* @param $email
* @return object
*/
public function getByEmail($email)
{
return $this->dao->findOneBy(array('email' => $email));
}
}
samo že je lepší si udělat abstraktní třídu pro model/fasádu která bude mít ten konstruktor a bude obsahovat základní uni metody jako getOne, save, atd.
- jedelex
- Člen | 16
@akadlec : takto to mam aj ja spravene , ze mam abstraktni tridu, ktora obsahuje zakladni uni metody getOne, find , findAll atd. a od tejto triedy extenduju uz konkretne modely, ktore mma registrovane ako service a v konstruktoru im predavam dao pre konkretnu entitu. Mne skor islo o to aby som si vytvoril service, ktory sa nebude viazat na dao konkretnej entity ale bude len obsahovat uni metody pre DQL, pripadne nativne SQL. Dospel som k takemuto zaveru :
config.neon
services:
emRepository: App\EmRepository
<?php
namespace App;
use Nette;
final class EmRepository extends \Nette\Object
{
/**
* @var Kdyby\Doctrine\EntityManager
*/
protected $em;
function __construct(\Doctrine\ORM\EntityManager $em )
{
$this->em = $em;
}
/**
*
*/
public function createQuery($dql){
return $this->em->createQuery($dql);
}
?>
cili v konstruktoru mu nepredavam DAO ale klasicky entityManager, kedze dao by sa muselo viazat ku konkretnej entite. Teraz mam pekne oddelene zakladne uni. metody ktore sa budu viazat uz k nejakej konkretnej entite od metod pomcou ktorych budem robit DQL. Takto sa mi to zda logicke a ciste :)
- Jiří Nápravník
- Člen | 710
akadlec napsal(a):
samo že je lepší si udělat abstraktní třídu pro model/fasádu která bude mít ten konstruktor a bude obsahovat základní uni metody jako getOne, save, atd.
Tak já si teda nejsem úplně jistý, ale podle mě abstraktní „nadfasáda“ s těmahle metodama save, getOne apod. úplně ideální není. Podle mě tohle jsou metody do repozitare/dao (což ostatne tak EntityDao i má) a ve fasádě už by měly být konkrétnější saveArticle, atd. Protože často bývá fasáda nad několika dao, a když pak zavolám $orderFacade->save(), tak co ukládám? Ukládám jenom objednávku od uživatele (která ji jen vloží do databáze), nebo ukládám objednávku ze skladu (který má pak na starost, ponížit skladové zásoby a odeslat maily). To z těhle obecných názvů nezjistím.
To, že patří do repositáře, ostatně potvrzuje i jedelex, který pak abstraktní třídu pro fasádu pojmenoval suffixem Repository:-))
Rád bych připomenul jedno Filipovo moudro: „Pokud se dědí jen abych si usnadnil práci, tak to dědění zneužívám“.
- Filip Procházka
- Moderator | 4668
- Entity nemá model (třídu), model (vrstva) má entity.
- Omezovat se na 1 model (třída) = 1 entita je blbost, není důvod se bránit vztahu 200 modelů (tříd) na 20 entit.
- Jiří Nápravník
- Člen | 710
U Kdyby\Doctrine „neexistuje“ repositář. Uvozovky tam jsou schválně, protože DAO objekty jsou repositáře obohacené o metodu save apod.
DAO se nemá pak dále dědit, ale využívat přes kompozici. A tam kde to DAO využíváš tak to většinou je pak nějaká fasáda/servisní třída, či jak si to nazveš.
- akadlec
- Člen | 1326
No řek bych že se tady společně mícháme v názvosloví. DAO samozřejmě již nikde nedědím ale jen předávám fasádě která s ním pracuje (u mě to má suffix Model)
class UserModel extends \Nette\Object
{
/**
* @var \Kdyby\Doctrine\EntityDao
*/
protected $dao;
public function __construct(\Kdyby\Doctrine\EntityDao $dao)
{
$this->dao = $dao;
}
}
Tato fasáda/model pak obsahuje další metody pro vyhledání konkrétních entit na základě nějakých vstupních parametrů, např.: getAllActivated(), getInRole(Role $role) atd.
Mimojiné tato fasáda/model má také metodu save(Entity $entity) která provede to že jen zavolá DAO a provede jeho save. Je to prostě proto abych si nemusel předávat DAO a fasádu/model do presenterů či komponent.
A asi tedy přejmenuju Model na Facade abych tady nezaváděl nesrovnalosti ;)
Nicméně včera jsem narazil na jeden problém při ukládní dat. Mám entitu Item a ta má v sobě reali OneToMany na entity Attributes. V relaci je definováno cascade={persist, remove} Pokud přidám nový atribut pomocí addAtribut(Attribute $attribute) metody a tento atribut je pro entitu nový tak se korektně provede jeho vytvoření při save entity Item. Pokud ale dojde ke změně již existujícího atributu tak se to samozřejmě neprovede pokud se neflushne celý EntityManager. Akorát že celý EM flushnout nějak nemůžu protože by mě to do DB poslalo změny i co nechci aby tam prošly, resp dochází mi v aplikaci někde ke změně hodnot v array collection (zatím nevím kde).
Takže jak by jste řešili uložení takové entity aby se uložily i její realce a nepoužil se obecný EM? Vytvořit ve fasádě metodu save která zavolá save na DAO Item entity, pak si vytáhne DAO Attribute entiy a projde všechny attributy a provede save na nich?
- Skippous
- Člen | 21
Ahoj, chtěl bych se zeptat jaká je v Kdyby\Doctrine best practice pro práci s typem ENUM. Vytvořil jsem si vlastní typ a nakonfiguroval mu metodu getSqlDeclaration dle dokumentace doctrine.
namespace Types;
use Kdyby\Doctrine\Types\Enum;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class MyEnum extends Enum {
protected $name = 'myenum';
protected $values = array('one', 'two');
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform) {
$values = array_map(function($val) {
return "'" . $val . "'";
}, $this->values);
return "ENUM(" . implode(", ", $values) . ") COMMENT '(DC2Type:" . $this->name . ")'";
}
}
Entitě nastavil pomocí:
/**
* @ORM\Column(type="myenum")
* @var MyEnum
* @property MyEnum $myEnum
*/
protected $myEnum;
Není mi ale jasné, jak ho nejlépe zaregistrovat. Zatím to mám v bootstrapu takto:
\Doctrine\DBAL\Types\Type::addType('myenum', 'Types\MyEnum');
Dá se to vyřešit elegantněji v neonu? Díky za radu.
- David Matějka
- Moderator | 6445
neon:
doctrine:
types:
myenum: types\MyEnum
nebo muze tvuj extension (pokud pouzivas) implementovat https://github.com/…Provider.php
- Tomáš Votruba
- Moderator | 1114
Jak je vhodné řešit vícejazyčné záznamy v rámci tohoto balíčku?
Našel jsem zatím DoctrineBehaviors
a DoctrineExtensions
Co používáte vy?
- one-two
- Člen | 80
Mám menší problém. Používám detekci jazyku z hlavičky
Accept-Language, kterou obstarává
Kdyby\Translation\LocaleResolver\AcceptHeaderResolver
a která je
závislá na Nette\Http\Request
. Problém je, že http request
dědím a používám vlastní, čimž daná služba nejde vytvořit. Můžu si
sice fixnout parametr v konstruktoru, aby požadoval interface
Nette\Http\IRequest
, jenomže to je podle mě trochu dirty hack,
protože ten resolver potřebuje metodu detectLanguage
, kterou ale
IRequest
vlastně nepožaduje…
Co s tim? :)
EDIT: problém jinde, vyřešeno
Editoval one-two (6. 3. 2014 16:33)
- tivvit
- Člen | 36
Ahoj,
postupoval jsem podle:
https://github.com/…/en/index.md#…
entitu Article.php jsem uložil do app/model a config.neon mám takhle:
services:
- App\UserManager
- App\RouterFactory
router: @App\RouterFactory::createRouter
articles: App\Articles(@doctrine.dao(App\Article))
a dostávám tohle –
Class App\Articles used in service 'articles' has not been found or is not instantiable
a pořád nemůžu přijít na to kde je chyba.
- Jiří Nápravník
- Člen | 710
Protože nejspíše nemáš App\Articles – tj v podstate facade/servisni trida, ktera nad tou entitou pracuje a budes tam pak psat queries, ziskavat data atd.
Třída Articles, pak vytvoř nějak takto a mělo by to jít:
namespace App;
final class Articles
{
private $articleDao;
public function __construct(EntityDao $articleDao)
{
$this->articleDao = $articleDao;
}
//tady pak pises dalsi metody tovjiu business logiky
}
- tivvit
- Člen | 36
Myslel jsem, že k tomu slouží v Kdyby/Doctrine entity.
Moje aktuální konfigurace:
doctrine:
#host: 127.0.0.1
user: root
password: ****
dbname: db
metadata:
App: %appDir%/model
extensions:
console: Kdyby\Console\DI\ConsoleExtension
events: Kdyby\Events\DI\EventsExtension
annotations: Kdyby\Annotations\DI\AnnotationsExtension
doctrine: Kdyby\Doctrine\DI\OrmExtension
services:
- App\UserManager
- App\RouterFactory
router: @App\RouterFactory::createRouter
articles: App\Articles(@doctrine.dao(App\Article))
model/Article.php
<?php
namespace App;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Article extends \Kdyby\Doctrine\Entities\IdentifiedEntity
{
/**
* @ORM\Column(type="string")
*/
protected $title;
}
?>
model/Articles.php
<?php
namespace App;
final class Articles
{
private $articleDao;
public function __construct(EntityDao $articleDao)
{
$this->articleDao = $articleDao;
}
//tady pak pises dalsi metody tovjiu business logiky
}
?>
presenters/HomepagePresenter.php
<?php
private $em;
public function __construct(Doctrine\ORM\EntityManager $em)
{
$this->em = $em;
}
public function renderDefault()
{
// $articles = $this->context->articles;
$articles = $this->em->getDao(App\Article::getClassName());
$article = new Article();
$article->title = "The Tigger Movie";
$articles->save($article);
$article = $articles->find(1);
echo $article->title; // "The Tigger Movie"
}
?>
composer.json
"require": {
"php": ">= 5.3.7",
"nette/nette": "~2.1.1",
"dg/adminer-custom": "1.*",
"kdyby/doctrine": "~1.1.0"
A chyba je pořád stejná
Class App\Articles used in service 'articles' has not been found or is not instantiable.
- Filip Procházka
- Moderator | 4668
Tuhle chybovou hlášku
Class App\Articles used in service 'articles' has not been found or is not instantiable
háže Nette a nijak nesouvisí s Kdyby/Doctrine, vůbec. Nette neumí
z nějakého důvodu třídu najít (zkontroluj že jde opravdu načíst už
v boostrapu před kompilací) nebo je abstraktní/má privátní konstruktor,
zkrátka není nad ní možné zavolat new App\Articles();
Jeden z těchto problémů to je a ty musíš zjistit který. Abstraktní jak tak koukám není, takže si oprav autoloading. Začal bych smazáním cache.
- tivvit
- Člen | 36
@Hosiplan:
Bezva moc díky, autoload po smazání cache zafungoval.
Nyní ale nemohu najít EM:
No service of type App\Presenters\Doctrine\ORM\EntityManager found. Make sure the type hint in App\Presenters\HomepagePresenter::__construct() is written correctly and service of this type is registered
a ani
services:
entityManager: @Kdyby\Doctrine\EntityManager
tomu napomůže, mám Doctrine dobře nainstalované?
- Jiří Nápravník
- Člen | 710
Říkají ti něco namespaces? Pokud ne, tak si o nich něco nastuduj, protože dál se nejspíše nepohneš. Tohle hlavně nemám nic společného s Kdyby\Doctrine, ale to jsou základy PHP.
Tohle hledá EntityManager zde: App\Presenters\Doctrine\ORM\EntityManager , ale ty ho máš zde: Doctrine\ORM\EntityManager .
Takže bud takhle:
public function __construct(\Doctrine\ORM\EntityManager $em)
{
$this->em = $em;
}
nebo takhle:
use Doctrine\ORM\EntityManager;
class HomepagePresenter{
public function __construct(EntityManager $em)
{
$this->em = $em;
}
}
- tivvit
- Člen | 36
Ahoj,
narazil jsem na problém na produkci.
nejříve jsem chtěl použít:
v config.local.neon
parameters:
database:
user: root
...
v config.neon to samé v parameters a
doctrine:
%database.user%
to na produkci nejelo tak jsem to přepsal že mám v obou souborech přímo sekci doctrine, ale stejně to na produkci nejde. Co z toho je vhodnější zápis? To že mi to na produkci preferuje ten lokální config bude asi špatnou detekcí (mám VPS), ale laděnku tam nemám tak tomu moc nerozumím.
- akadlec
- Člen | 1326
Já to takhle předávám a funguje mi to i na produkčním serveru. Co ti to vyhazuje za chybu? Taky to může být problém s generováním proxyn co má kdyby dle mě špatně ošetřeno. Zkus opravit OrmExtension.php
řádek 305:
->addSetup('setAutoGenerateProxyClasses', array($config['autoGenerateProxyClasses']))
nahraď za:
->addSetup('setAutoGenerateProxyClasses', array($config['autoGenerateProxyClasses'] ? 1 : 2))
- Filip Procházka
- Moderator | 4668
a co takhle tu sekci doctrine nadefinovat jinak?
doctrine: dbname : %database.dbname% host : %database.host% user : %database.user% password : %database.password%
Tohle je tak hrozná zbytečnost, proč to prostě nenapíšete přímo do té sekce?
doctrine:
dbname: foo
host: localhost
user: root
password: heslo
Taky to může být problém s generováním proxyn co má kdyby dle mě špatně ošetřeno. Zkus opravit OrmExtension.php
Proč jsi to nereportovat? Proč se to dozvídám náhodou z fóra? Opraveno.
Změnil jsem to co píšeš a pořád to bere špatný config.
Máš špatně heslo, nebo máš blbě nastavená oprávnění pro MySQL server. Tohle jsou úplně základy práce s databází, neuškodilo by trochu googlit, existuje na to asi půl internetu návodů.
- akadlec
- Člen | 1326
@Filip Procházka: Filipe já to reportoval pravda měl sem to hodit spíše na git, ale když to tady bylo bez odezvy tak sem to bral jako korektní chování ;)
Ad definice, tak ja to tak mám abych mohl zadefinovat jedny přístupy na dev a druhé na prod.
- tivvit
- Člen | 36
Dump configuratoru
<?php
Nette\Configurator #b0e8
onCompile => NULL
defaultExtensions => array (4)
php => "Nette\DI\Extensions\PhpExtension" (32)
constants => "Nette\DI\Extensions\ConstantsExtension" (38)
nette => "Nette\DI\Extensions\NetteExtension" (34)
extensions => "Nette\DI\Extensions\ExtensionsExtension" (39)
parameters protected => array (8)
appDir => "/var/www/newlode/app" (20)
wwwDir => "/var/www/newlode/www" (20)
debugMode => FALSE
productionMode => TRUE
environment => "production" (10)
consoleMode => FALSE
container => array (2)
class => "SystemContainer" (15)
parent => "Nette\DI\Container" (18)
tempDir => "/var/www/newlode/app/../temp" (28)
files protected => array (2)
0 => array (2)
0 => "/var/www/newlode/app/config/config.neon" (39)
1 => NULL
1 => array (2)
0 => "/var/www/newlode/app/config/config.local.neon" (45)
1 => NULL
?>
@Hosiplan to si nemyslím, když konfigurační sekci doctrine z config.local.neon smažu tak web jede a podle toho dumpu to vypadá, že to detekuje jako produkci.
Přepsal jsem to na verzi bez parameters, stačí předefinovat jen to co je jinak (user, password)?
Jak se dostanu k proměnným v configu?
- Filip Procházka
- Moderator | 4668
akadlec napsal(a):
@Filip Procházka: Filipe já to reportoval pravda měl sem to hodit spíše na git, ale když to tady bylo bez odezvy tak sem to bral jako korektní chování ;)
Nenapadlo tě že jsem si toho nevšiml? :)
- Filip Procházka
- Moderator | 4668
@tivvit vlož na pastebin všechny configy co jsi jakkoliv upravoval a názvy jejich souborů (a sem vlož odkazy). Jinak se nikam nedostaneme. Nezapomeň z nich předtím vyhvězdičkovat hesla :)