Nextras\ORM – ORM nad Nextras\Dbal

David Matějka
Moderator | 6445
+
0
-

@w3r0 doporucil bych ti udelat samostatny id i pro CSkillLocale

manwe
Člen | 44
+
0
-

@DavidMatějka Az ted jsem si vsiml ze se mi pri refaktorizaci tridy v PHPStormu prejmenoval v TripRepository getEntityClassNames() a vracel TripTerm::class misto Trip::class…

ach jo… no nic, uz jsem zabil hodinu sveho zivota asi i na horsich vecech

petr.pavel
Člen | 535
+
0
-

@w3r0 Nebude to tím, že máš v entitě „int nebo null“, přičemž null to být nesmí, když je to primární klíč? V db to je správně NOT NULL.

@property int|null $id {primary}

Asi se tam snažíš vložit null.

P.S. Založ si, prosím, samostatné vlákno, tohle je kilometr dlouhé, míchá se tu kde co.

stajo
Člen | 8
+
0
-

caute,

v entite mam nullable property nastavenu na hodnotu. ako sa da vratit na null?

@property \DateInterval|null     $start

$entity->start = null; vygeneruje sql:

UPDATE tab SET start = '' …

dik

David Matějka
Moderator | 6445
+
0
-

@raddy668 ahoj,

  • v doc je chyba. od verze 3.0 je potreba ten query builder prohnat skrz toCollection, stejne jako to delas s tim resultem
  • s tema properties je to divne, nejsou treba jejich hodnoty null?
manwe
Člen | 44
+
0
-

@DavidMatějka
Jo, uz jsem to poresil :) Ma tam nekdo pristup to opravit? Ja totiz dost dlouho stravil nad tim ze jsem faral co mam blbe proti tomu examplu :D

Promin, nevsiml jsem si zes odpovedel driv nez jsem to smazal.

Bylo to zpusobeno tim, ze jsem mel v anotacich property v snake_case i v camelCase a Nextras je v defaultnim modu automaticky predelava z camelCase do snake_case a timpadem ty property, ktere byly ve snake_case tak se nenaplnovaly.
Takze muj brainfart, predelaval jsem to z YetORM do Nextrasu.

Kazdopadne diky za odpoved :)

David Matějka
Moderator | 6445
+
0
-

@raddy668 dokumentace je na githubu, takze kdokoliv muze poslat PR s opravou :) (krome stranky s mapperem je chyba i v repository kapitole) poslal bys PR nebo to mam opravit ja? :)

manwe
Člen | 44
+
0
-

@DavidMatějka nom, bych si musel precist co je Pull Request a jak se ho dela :D

Taky nevim co presne bych tam teda mel napsat krome toho ze je treba dat to toCollection?

d3tr1tus
Člen | 52
+
0
-

Ahoj začínám s ORM a jeho implementací píše mi to zatím jen

Class App\Model\Orm does not exist

můj config

extensions:
	nextras.orm: Nextras\Orm\Bridges\NetteDI\OrmExtension


nextras.orm:
	model: App\Model\Orm

strom modelu

-model
	-orm
		-Order
			-OrderRepository.php
			-atd.
		-Orm.php

	-SomeService.php

co ještě mám přidat? Díky moc za rady :)
David Matějka
Moderator | 6445
+
0
-

ukaz, co mas v tom souboru Orm.php

d3tr1tus
Člen | 52
+
0
-

@DavidMatějka můj Orm.php vypadá takto

namespace App\Model;

use App\Model\Order\OrderRepository;
use Nextras\Orm\Model\Model;

/**
 * Model
 *
 * @property-read OrderRepository                   $order
 */
class Orm extends Model {

}
d3tr1tus
Člen | 52
+
0
-

Můžete mi poradit co tam mám špatně prosím? Pořád se mi to nedaří rozchodit a fakt netuším kde je chyba :/

hrach
Člen | 1838
+
0
-

@d3tr1tus mas spatne asi autoloading, bud musis ho mit pres composer nebo pres robotloader, to asi s orm nema moc spolecneho, tak se k tomu neupinej a spis hledej jak spravne nastavit ten autoloading :)

d3tr1tus
Člen | 52
+
0
-

@hrach autoloading mam nastaveny pres composer takto

"autoload": {
                "psr-4": {
                    "Nextras\\Dbal\\": "src/"
                },
                "classmap": [
                    "src/exceptions.php"
                ]
            },

ale netuším kam to je nasměrované :/

David Matějka
Moderator | 6445
+
0
-

@d3tr1tus tohle je autoloading nextras/dbalu, ne? jde o tvuj autoloading tveho projektu. predpokladam, ze pouzivas robot loader. mas tu slozku model ve slozce app?

d3tr1tus
Člen | 52
+
0
-

@DavidMatějka ano omlouvam se omylem jsem poslal dbal, ale na orm to mám stejně

"autoload": {
                "psr-4": {
                    "Nextras\\Orm\\": "src/"
                },
                "classmap": [
                    "src/exceptions.php"
                ]
            },

ano mám složku model ve složce app a pouzivam pro nacitani robot loader.

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

ty si ale musíš hodit do autoloadu třídy co máš ve složce app, takže musíš mít v bootstrapu něco takového

máš?

$configurator->createRobotLoader()
	->addDirectory(__DIR__)
	->register();

nebo tu tvoji třídu máš uplně někde mimo?

d3tr1tus
Člen | 52
+
0
-

@OndřejKubíček ano mám tam přesně to samé

$configurator->createRobotLoader()
	->addDirectory(__DIR__)
	->register();

mám to normálně ve složce app

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

a proč máš v composeru ten autoload vůbec?

d3tr1tus
Člen | 52
+
0
-

@OndřejKubíček ten se tam přidal sám po nainstalování ORM přes composer. Může se to tlouct? Může být v tomhle chyba?

David Matějka
Moderator | 6445
+
0
-

ten je predpokladam ve vendoru, ten te vubec nemusi zajimat. zkontrol ji, ze mas ten soubor Orm.php spravne – ze ma opravdu spravnou koncovku .php, ze je nekde ve slozce app a ze ma spravnou oteviraci znacku <?php

d3tr1tus
Člen | 52
+
0
-

@DavidMatějka mám vše správně jak by mělo být ale stále to píše

Class App\Model\Orm does not exist

Orm mám v

-app
	-model
		-orm
			-Orm.php

tak netuším :/

David Matějka
Moderator | 6445
+
0
-

a s autoloadingem jinych trid problem nemas? zkus treba ten soubor smazat a znovu vytvorit, treba i pod jinym nazvem

hrach
Člen | 1838
+
0
-

@d3tr1tus pls dojdi na nejaky lepsi komunikacni nastroj, idealne slack Pehapkari, tady ta diskuze nedava vubec smysl. Diky :)

d3tr1tus
Člen | 52
+
0
-

@DavidMatějka dobře zkusím

@hrach okej sory :)

Zax
Člen | 370
+
0
-

Ahoj,

používám Nextras ORM spolu s pluginem pro PhpStorm.

Všiml jsem si, že se mi v repozitářích nabízí tlačítka „Navigate to mapper method“, které bohužel nic nedělají. Ono se to řídí nějakou konvencí? Dá se to nějak nastavit, aby to fungovalo i u mě? Repozitáře mám pod NS Modul\Submodul\Repository a mappery mám pod Modul\Submodul\Mapper.

Dále se chci zeptat – plánujete přidat také tlačítka „Navigate to getter/setter method“ do entit?

Díky za případnou odpověď a hlavně za toto fantastické ORM!

hrach
Člen | 1838
+
+1
-

@Zax v plugingu toto dela tato trida: https://github.com/…rProvider.kt Ktera aktualne stripuje Repository suffix a pridava Mapper suffix. Tvuj zpusob namingu tedy uplne fungovat nebude, asi by bylo dobre ten plugin upravit, muzes otevrit issue/pull request :)

ony2
Člen | 9
+
0
-

Zdravim, chcem sa spytat ci je nejako mozne vo findBy filtrovat podla hodnoty ineho stlpca.
Napriklad:

$myRepository->findBy([
  'stlpec1!=' => 'stlpec2'
])

Dakujem

hrach
Člen | 1838
+
+1
-

@ony2 jedine pres custom collection (filtering) function.

ony2
Člen | 9
+
0
-

hrach napsal(a):

@ony2 jedine pres custom collection (filtering) function.

Dakujem, spravil som to cez filter function, len pre istotu som chcel vediet ci nie je aj lahsia cesta.

mskocik
Člen | 62
+
0
-

Ahoj, zasekol som sa na nasledovnej veci:
Tabulka User

Field Type Null Key Default Extra
id int(11) NO PRI NULL auto_increment
username varchar(255) NO UNI    

Tabulka Contact

Field Type Null Key Default Extra
id int(10) unsigned NO PRI NULL auto_increment
user_id int(10) unsigned NO PRI NULL  
type enum(‚billing‘,‚personal‘) NO PRI NULL  
name varchar(100) NO   NULL  

Neviem, ako spravne definovat vztah {1:1} tak, aby bolo mozne pouzit

$user->contact

Je to vobec mozne, ked User nema referenciu na Contact?

David Matějka
Moderator | 6445
+
+1
-

@mskocik ahoj, mrkni do doc, „Book“ je v tvem pripade ten „Contact“, jelikoz ten drzi referenci..

mskocik
Člen | 62
+
0
-

@DavidMatějka vdaka za reakciu. Mam to takto:

/**
 * @property int            		$id {primary}
 * @property Contact				$contact {1:1 Contact::$user}	// v DB nie je stlpec contact_id
 */
final class User extends Entity {}

/**
 * @property int 		$id {primary}
 * @property User		$user {1:1 User::$id, isMain=true} // v DB stlpec user_id
 */
final class Contact extends Entity

Predpokladam, ze je to takto spravne, pretoze vazba Contact na Usera, je cez user_id. Dava mi to takto zmysel ale vysledkom je:

Nextras\Orm\InvalidStateException
Model\User\User::$contact relationship with Model\User\Contact::$user is not symetric.

Co mi tu unika? Tabulka User nema definovany contact_id (z historickych dovodov) – predpokladam, ze kebyze je to tam definovane, tak by to fungovalo bez problemov. Je mozne nieco taketo vobec? V Doctrine1 s tym neboli problemy nikdy :)

David Matějka
Moderator | 6445
+
0
-

@mskocik
u toho mapovani Contact::$user musis mit {1:1 User::$contact, isMain=true}, uvadi se tam inversed strana te relace

mskocik
Člen | 62
+
0
-

@DavidMatějka v takejto forme mi to nefungovalo – vynimka:

Nextras\Dbal\QueryException
Unknown column 'Contact.user' in 'where clause'

SELECT `Contact`.* FROM `Contact` AS `Contact` WHERE `Contact`.`user` IN (3393) // existuje `Contact`.`user_id`

Musel som este zmenil * @property Contact $contact {1:1 Contact::$user} na * @property Contact $contact {1:1 Contact::$userId} zacalo to fungovat. Dakujem moc!
/

Václav Kraus
Člen | 77
+
0
-

Ahoj, je možné se jednoduše připojit k více databázím?

V configu mám

nextras.dbal:
	db1:
		driver: mysqli
		host: db
		username: root
		password: root
		database: db1
	db2:
		driver: mysqli
		host: db
		username: root
		password: root
		database: db2

Netuším ale, jak jednotlivá připojení předat ORM. Předem díky za tip :)

mskocik
Člen | 62
+
+1
-

@VáclavKraus Ahoj, k databazi sa pripaja mapper, takze v configu si mozes pre dany mapper definovat, ktore Dbal\Connection pouzijes:

services:
	- UserMapper # toto pouzije dane connection, ktore mas definovane autowired
    - ContactMapper(@nextras.dbal.db2.connection)

nextras.dbal:
    db1:
        driver: mysqli
        host: db
        username: root
        password: root
        database: db1
    db2:
        driver: mysqli
        host: db
        username: root
        password: root
        database: db2
		autowired: false
hrach
Člen | 1838
+
+1
-

@VáclavKraus je to castecne jak pise @mskocik, ze predas instanci, ale nejde takto mit vic instanci connection, ty udelas tak, ze Dbal extension zaregistrujes 2×.

Václav Kraus
Člen | 77
+
0
-

Děkuju vám za rady. Nicméně jsem se zasekl na tom, že je stejně předána connection, která má nastavený autowire.

Mám v konfigu:

extensions:
    nextras.orm: Nextras\Orm\Bridges\NetteDI\OrmExtension
    nextras.dbal.gui: Nextras\Dbal\Bridges\NetteDI\DbalExtension
    nextras.dbal.ldap: Nextras\Dbal\Bridges\NetteDI\DbalExtension

nextras.dbal.gui:
	driver: mysqli
	host: db
	username: root
	password: root
	database: gui
	autowired: false

nextras.dbal.ldap:
	driver: mysqli
	host: db
	username: root
	password: root
	database: ldap

services:
	- App\Orm\UserMapper(@nextras.dbal.gui.connection)

V cache se správně vytvoří:

public function createService__42_App_Orm_UserMapper(): App\Orm\UserMapper
{
	$service = new App\Orm\UserMapper(
		$this->getService('nextras.dbal.gui.connection'),
		$this->getService('nextras.orm.mapperCoordinator'),
		$this->getService('41_Nette_Caching_Cache')
	);
	return $service;
}

Nicméně UserMapper stále používá jinou connection. Nevíte, v čem by mohl být zádrhel? :)

hrach
Člen | 1838
+
0
-

@VáclavKraus pokud pouzivas phpdoc finder (default), tak ten, pokud chces predefinovat sluzby, vyzaduje, aby mely presne jmeno v di. https://github.com/…ryFinder.php#L89 tzn. to jmeno cca nextras.orm.mappers.userRepository (asi, podivej se, co to presne vygenerovalo)

pitr82
Člen | 121
+
0
-

Ahoj @VáclavKraus můžeš zde postnout funkční řešení, pro připojení ke dvěma DB.
Díky

mskocik
Člen | 62
+
0
-

@pitr82 Ja som to spravil tak, že som si napísal vlastný bridge pre Dbal nejako takto:

<?php declare(strict_types=1);

namespace Base\Bridges\DI;

use Tracy\Debugger;
use Nextras\Dbal\Connection;
use Nette\DI\CompilerExtension;
use Nextras\Dbal\Bridges\NetteTracy\ConnectionPanel;
use Nextras\Dbal\Bridges\NetteTracy\BluescreenQueryPanel;

/**
 * DBAL Extension enabling multiple connections and specific connection factory class
 */
class DbalExtension extends CompilerExtension
{
	public function loadConfiguration()
	{
		$configs = $this->getConfig();

		foreach ($configs as $name => $config) {
			if (is_scalar($config)) continue;
			$this->setupConnection($config, $name);
		}
	}

	protected function setupConnection(array $config, string $name)
	{
		$builder = $this->getContainerBuilder();
		$className = $config['factory'] ?? Connection::class;

		unset($config['factory']);

		$definition = $builder->addDefinition($this->prefix("$name.connection"))
			->setClass($className)
			->setArguments([
				'config' => $config,
			])
			->setAutowired(isset($config['autowired']) ? $config['autowired'] : true);

		if (isset($config['debugger'])) {
			$debugger = $config['debugger'];
		} else {
			$debugger = class_exists('Tracy\Debugger', false) && Debugger::$productionMode === Debugger::DEVELOPMENT;
		}

		if ($debugger) {
			$definition->addSetup('@Tracy\BlueScreen::addPanel', [BluescreenQueryPanel::class . '::renderBluescreenPanel']);
			$definition->addSetup(ConnectionPanel::class . '::install', ['@self', $config['panelQueryExplain'] ?? true]);
		}
	}
}

A config.neon

extensions:
	dbal: Base\Bridges\DI\DbalExtension

dbal:
	default:
		driver: mysqli
		host: 127.0.0.1
		database: db
		username:
		password:

	logger:
		# create: Base\Ext\Dbal\Connection # possible to define own connection factory
		driver: mysqli
		host: 127.0.0.1
		database: dt_log
		username: root
		password:
romiix.org
Člen | 343
+
0
-

Zdravím, riešilo sa to tu už 100×, ale aj tam mi nič nepomáha…

V php nastavím:

$d = new \DateTime('2018-09-21 13:37:09');

// DateTime
//     date => "2018-09-21 13:37:09.000000"
//     timezone_type => 3
//     timezone => "Europe/Prague"

$e = new E;
$e->date = $d;

Do DB sa zapíše do stĺpca date (datetime) – 2018–09–21 13:37:09. Zatiaľ všeko ok.

Dám dump $e->date a dostanem:

DateTimeImmutable
   date => "2018-09-21 15:37:09.000000"
   timezone_type => 1
   timezone => "+02:00"

Config:

dbal:
	connectionTz: auto
	driver: mysqli
	host: 127.0.0.1
	database: db
	username:
	password:

Ako prosím zabezpečiť, aby som dostal aj naspäť zhodný čas ako som zapísal do DB?

Vďaka

hrach
Člen | 1838
+
+1
-

@romiix.org

Jaky pouzivas v db datovy typ? Predpokladam, ze pouzivas datetime – ten neumi rozeznavat connection timezone, tzn. je mu jedno, v jake jsi to ulozi a v jake to ctes. Takze uz je take spatne zapsano 13:37, to uz je jiny udaj od toho posledniho. Koukam se a je to dobre. Ten simple typ se chape v dbalu tak, ze ignoruje zonu, protoze i databaze neumi pracovat se zonou. Tzn. asi je tam chyba fakt v tom cteni, ale jsou na to testy a tam je to ok, tak bych to potreboval nejak vic oddebugovat.

Pripadne prosim zaloz issue na githubu dbal. On tam je asi mozna opravdu bug.

Editoval hrach (24. 9. 2018 20:54)

TonnyVlcek
Člen | 31
+
0
-

@romiix.org @hrach
Teď jsem narazil na něco podobného, ale vypadá to spíš na chybu (změnu chování) při zapisování do db. Nemůžu to říct se 100% jistotou, ale zdá se, že problém (změna chování) se projevil po updatu na nextras/orm 3.0.

V databázi jsem měl sloupec start_time datového typu DATETIME. V aplikaci mi uživatel vybral datum a čas a k tomu zvolil časovou zónu. Při zpracování formuláře jsem si to přebral a vytvořil z toho objekt DateTime se se správnou časovou zónou. Např.:

date => "2018-09-27 09:00:00.000000" (26)
timezone_type => 3
timezone => "Europe/Madrid" (13)

Do databáze se uložilo 2018-09-27 09:00:00.000000 místo 2018-09-27 07:00:00.000000. (Čas serveru, php, mysql i connection je nastavený na UTC).

Řešením bylo změnit datový typ sloupečku v db z DATETIME na TIMESTAMP a tím donutit nextras používat modifikátor %dt místo %dts.

Nemyslím si, že tohle je nutně špatné chování, přijde mi, že to odpovídá i dokumentaci (https://nextras.org/…3.0/datetime#…). Zaskočilo mě ale to, že to vypadá, že se chování změnilo mezi verzemi.

Dá se Nextras Orm nějakým způsobem donutit aby používal %dt i pro slupce typu DATETIME?

hrach
Člen | 1838
+
0
-

@TonnyVlcek ahoj, v 3.0 opravdu doslo ke zmene chovani. Je to zminene tady a odkazuje se to sem, kde se pise:

Notable BC breaks:
Date-time changes: %dts does not do any timezone modifications (removed simpleStorageTz), read more in documentation and see the usage matrix.

Takze jednoduse receno a vysvetleno, sloupce, ktere nepodporuji zony – DATETIME – nyni DBAL taky chape tak, ze proste zadny zony nemaj. Datetimy do nich zapisuje nehlede jejich zon. A cte je nehlede jejich zony. To plati zejmena pro MySQL.

Coz je ten @romiix.org priklad – proste se vezme co je v DateTimu a neresi se, v jaky zone to je a zapise to. Pri cteni se to stejne tak precte z db a skonci to s php default timezonou.

V tvem pripade ti drivejsi chovani nahodou vic vyvhovalo, ale jak sam pises, pro tvuj usecase je spravne pouzit datovy typ timestamp. Mas nejaky duvod chtit pouzit DATETIME?

hrach
Člen | 1838
+
0
-

@TonnyVlcek ahoj, dnes už je tu vidět má odpověď 🙂

Barbarossa
Člen | 74
+
0
-

Ahoj,

nemůžu se prodrat přes jednu dbal/RelationshipMapperOneHasMany exception:
Column 'id' in field list is ambiguous

Mám entitu ve které mám metodu:

public function getMembers()
{
	return $this->userMembers->get()
	->findBy([
		'status' => 1,
		'this->users->deleted' => FALSE,
	])
	->orderBy('priority', ICollection::DESC)
	->limitBy(7);
}

tuto metodu volám v různých místech aplikace. Funguje všude až na jednu ajax komponentu (to, že je ajax na to ale asi vliv nemá), ve které se díky nějaké kombinaci (práci se stejnou tabulkou) vytvoří sql dotaz, který vyhazuje výše zmíněnou chybu. Kritické sql pak vypadá takto:

(
SELECT `id`, `teams_id` FROM `teams_access` AS `teams_access`
  LEFT JOIN `users` AS `users` ON (`teams_access`.`users_id` = `users`.`id`)
  WHERE ((teams_access.deleted = 0) AND ((`teams_access`.`status` = 1) AND (`users`.`deleted` = 0)))
   AND (`teams_id` = 14)
   ORDER BY `teams_access`.`teams_role_id` DESC LIMIT 7
)UNION ALL (SELECT `id`, `teams_id` FROM `teams_access` AS `teams_access` LEFT JOIN `users` AS `users` ON (`teams_access`.`users_id` = `users`.`id`) WHERE ((teams_access.deleted = 0) AND ((`teams_access`.`status` = 1) AND (`users`.`deleted` = 0))) AND (`teams_id` = 8359) ORDER BY `teams_access`.`teams_role_id` DESC LIMIT 7) UNION ALL (SELECT `id`, `teams_id` FROM `teams_access` AS `teams_access` LEFT JOIN `users` AS `users` ON (`teams_access`.`users_id` = `users`.`id`) WHERE ((teams_access.deleted = 0) AND ((`teams_access`.`status` = 1) AND (`users`.`deleted` = 0))) AND (`teams_id` = 8360) ORDER BY `teams_access`.`teams_role_id` DESC LIMIT 7) UNION ALL (SELECT `id`, `teams_id` FROM `teams_access` AS `teams_access` LEFT JOIN `users` AS `users` ON (`teams_access`.`users_id` = `users`.`id`) WHERE ((teams_access.deleted = 0) AND ((`teams_access`.`status` = 1) AND (`users`.`deleted` = 0))) AND (`teams_id` = 8361) ORDER BY `teams_access`.`teams_role_id` DESC LIMIT 7)

debug info:
RelationshipMapperOneHasMany->fetchByTwoPassStrategy()
RelationshipMapperOneHasMany->processMultiResult()

…vypadá to, že do metody fetchByTwoPassStrategy to chodí jen v té jedné ajax komponentě, protože ostatní vygenerují vždy něco jako:

SELECT DISTINCT `teams_access`.* FROM `teams_access` AS `teams_access`
 LEFT JOIN `users` AS `users` ON (`teams_access`.`users_id` = `users`.`id`)
 WHERE ((teams_access.deleted = 0) AND ((`teams_access`.`status` = 1) AND (`users`.`deleted` = 0))) AND (`teams_access`.`teams_id` IN (14))
ORDER BY `teams_access`.`teams_role_id` DESC LIMIT 7

+ pokud z findBy odeberu 'this->users->deleted' => FALSE, tak je vše v pořádku. Dokonce aji když odeberu ten limitBy
++ v mapperu nepoužívám žádná rozšíření
+++ db driver: mysqli

Díky za jakoukoliv pomoc.

Editoval Barbarossa (15. 10. 2018 14:37)

hrach
Člen | 1838
+
+1
-

@Barbarossa

  • vytvoril jsem issue, prosim pokracujme v reseni tam.
  • zkusil jsem to fixnout, zkus pls branch, a dej vedet v issue (ktera branch – viz issue).

Editoval hrach (15. 10. 2018 21:41)

hrach
Člen | 1838
+
+6
-

Ahoj, na Nette foru bych jiz rad nespamoval tematem Nextras Orm. Proto prosim, pokud mas otazku, problem, navrh, cokoliv:

Diky, hrach