DOCTRINE2 – Self-referencing v AclRole

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Joacim
Člen | 229
+
0
-

Zdravím, trošku jsem se v tom zamotal ZDE Doctrine2 Návod.

Mám tabulku acl_role, kde jsou všechny role a rodiče těchto rolí, přičemž role nemusí mít potomka(nebo může mít potomků více) a potomek má vždy jen jednoho rodiče (jako je tomu u dědění rolí v Nette).

DB Schema

CREATE TABLE IF NOT EXISTS `acl_role` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `parent_id` INT(11) NULL,
  PRIMARY KEY (`id`, `parent_id`),
  UNIQUE INDEX `name_UNIQUE` (`name` ASC),
  INDEX `fk_acl_roles_acl_roles1_idx` (`parent_id` ASC),
  CONSTRAINT `fk_acl_roles_acl_roles1`
    FOREIGN KEY (`parent_id`)
    REFERENCES `acl_role` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
COMMENT = 'List of all roles'

A Entitu pro AclRole

<?php

namespace App\Model\Entities;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Doctrine entity for table AclRole.
 * @package App\Model\Entities
 * @ORM\Entity
 * @ORM\Table(name="acl_role")
 *
 * @author
 *
 * Rodič má 0 až více potomků (1:0..N)
 */
class AclRole extends \Kdyby\Doctrine\Entities\BaseEntity {

    /**
     * Sloupec pro ID role.
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     *
     * @ORM\ManyToOne(targetEntity="AclRole", inversedBy="ac_children")
     * @ORM\JoinColumn(name="id", referencedColumnName="parent_id")
     */
    protected $id;

    /**
     * Sloupec pro jméno role.
     * @ORM\Column(type="string")
     */
    protected $name;

    /* ------------------------- Association Mapping ------------------------ */

    /**
     * Sloupec pro ID rodiče (PARENT)
     * @ORM\Column(name="parent_id", type="integer")
     */
    protected $parent;

    /**
     * Všichni potomci současné role (CHILDRENS)
     * Namapovaná vazba role 1:N na seznam všech potomků.
     * @ORM\OneToMany(targetEntity="AclRole", mappedBy="parent_id")
     */
    protected $ac_children;

    /* --------------------------- Entity Methods --------------------------- */

    public function __construct() {
        parent::__construct();
        $this->ac_children = new ArrayCollection();
    }

    public function AddChildren(AclRole $children) {
        $this->ac_children[] = $children;
        $children->user = $this;
    }

}
// Prevzato jako ukázka z https://www.zdrojak.cz/clanky/ukladame-hierarchicka-data-v-databazi-i/
ID	PARENT	TITLE
1	NULL	Jídlo
2	1	Ovoce
3	2	Červené
4	2	Žluté
5	3	Třešeň
6	4	Banán
7	1	Maso
8	7	Hovězí
9	7	Kuřecí
  1. Jen se chci ujistit, že je tato entita a její reference na sebe samu napsaná dobře. A zda se mám u aktuální role dotazovat na jejího potomka nebo rodiče ?
  2. Další věc co by mě zajímala jak řešíte pro ROLE jako je admin zdroje Permission::ALL (Vložíte do db ke zdroji NULL) IAuthorizator?

EDIT
Už to asi mám, ale neodzkoušeno, asi jsem nad tím přemýšlel déle než bylo zdravé.

Každopádně objekt AclRole by měl uchovávat aktuální entitu (ID 1) a poté všechny její potomky v ArrayCollection( tedy parent entity je roven 1(rodici, který je uložen v promenné children)), ale v příkladu to ukládají do atributu children, neměl by být u entity array collection ac_children kde bude pole všech potomků a v children bude uložen jejich rodič a v parent rodič children ? – Možná mi jen něco uniká

/** @Entity */
class AclRole
{
    // ...
    /**
     * @OneToMany(targetEntity="AclRole", mappedBy="parent")
     */
    private $children; // ID

    /**
     * @ManyToOne(targetEntity="AclRole", inversedBy="children")
     * @JoinColumn(name="parent_id", referencedColumnName="id")
     */
    private $parent;
    // ...

    public function __construct() {
        $this->children = new \Doctrine\Common\Collections\ArrayCollection();
    }
}

Díky moc za pomoc

Editoval Joacim (2. 2. 2016 20:04)

F.Vesely
Člen | 369
+
0
-

Proc potrebujes znat v acl children?

Joacim
Člen | 229
+
0
-

F.Vesely napsal(a):

Proc potrebujes znat v acl children?

Spíš potřebuji znát rodiče, jen nevím jak to napsat, po tom co jsem se do toho zamotal, prohledal jsem i stackowerflow a další fora, ale tento vztah se nejspíše zas tak často nepoužívá, našel jsem jen xml zápis.

Potom existuje ještě možnost že si na to napíši metodu ve fasádě(což bych musel stejně, jen jsem to nechtěl prasit).

Dle DB schématu je toto základní entita, u které chybí selfreference

<?php

namespace App\Model\Entities;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Doctrine entity for table AclRole.
 * @package App\Model\Entities
 * @ORM\Entity
 * @ORM\Table(name="acl_role")
 *
 * @author
 *
 * Rodič(ID) má 0 až více potomků(ID) (1:0..N)
 * Rodič potomka(ID) je uložen v parent_id u potomka
 */
class AclRole extends \Kdyby\Doctrine\Entities\BaseEntity {

    /**
     * Sloupec pro ID role.
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     *
     */
    protected $id;

    /**
     * Sloupec pro jméno role.
     * @ORM\Column(type="string")
     */
    protected $name;

    /**
     * Sloupec pro ID rodiče (PARENT)
     * @ORM\Column(name="parent_id", type="integer")
     */
    protected $parent;

    /* ------------------------- Association Mapping ------------------------ */

    /**
     *
     * Namapovaná vazba role 1:N na seznam všech uživatelů.
     * Jedna role má N uživatelů.
     *
     * @ORM\OneToMany(targetEntity="User", mappedBy="role")
     */
    protected $ac_user;

    /* --------------------------- Entity Methods --------------------------- */

    public function __construct() {
        parent::__construct();
        $this->ac_user = new ArrayCollection();
    }

    public function addUser(User $user) {
        $this->ac_user[] = $user;
        $user->role = $this;
    }

}

Editoval Joacim (3. 2. 2016 8:14)

F.Vesely
Člen | 369
+
0
-

Staci zmenit parent na:

/**
* Sloupec pro ID rodiče (PARENT)
* @ORM\ManyToOne(targetEntity="AclRole")
*/
protected $parent;
akadlec
Člen | 1326
+
0
-

Doctrine stránky znáš? Konkrétně self-referencing tam zmíněný je.

OT: asi bych se raději vyhnul extendovat BaseEntitu z Kdyby

Joacim
Člen | 229
+
0
-

akadlec napsal(a):

Doctrine stránky znáš? Konkrétně self-referencing tam zmíněný je.

OT: asi bych se raději vyhnul extendovat BaseEntitu z Kdyby

Mám stažený článek z IT Network, tam to takhle má(zda je to dobře nedokážu posoudit s Doctrine teprve začínám). Do manuálu jsem samosebou koukal.

Já ale u role nepotřebuji zjištovat její děti, ale jejího rodiče pro dynamickou správu ACL v nette (role dědí od role)

Editoval Joacim (3. 2. 2016 9:04)

akadlec
Člen | 1326
+
0
-

Tak si to nechej jako v tom první příspěvku a musí ti to stačit. Budeš v entitě vidět jak její potomky tak i z potomku uvidíš na rodiče.

Joacim
Člen | 229
+
0
-

akadlec napsal(a):

Tak si to nechej jako v tom první příspěvku a musí ti to stačit. Budeš v entitě vidět jak její potomky tak i z potomku uvidíš na rodiče.

Otázkou je, zda to nemám vyřešit ve fasádě a bylo by to pro mě jednodušší, v entitě bych měl pouze potomky daného rodiče v kolekci a ID rodiče, daného rodiče(pokud má rodiče)

Array Collection mužu mít jen na potomky
Na rodiče se budu ptát ve fasádě entity

Jen u OneToMany se vytváří ArrayCollection → což jsou děti

Takže budu mít ve fasádě getRole ($id) a getParent($id)

Editoval Joacim (3. 2. 2016 9:17)

Joacim
Člen | 229
+
0
-

F.Vesely napsal(a):

Staci zmenit parent na:

/**
* Sloupec pro ID rodiče (PARENT)
* @ORM\ManyToOne(targetEntity="AclRole")
*/
protected $parent;

To bych měl ale pak OneToMany na ID, mohu mít v doctrine entitě definován ID a pak AC_children kde bych se odkazoval na ID

/**
     * Sloupec pro ID role.
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     *
     */
    protected $id;

    /**
     * Sloupec pro jméno role.
     * @ORM\Column(type="string")
     */
    protected $name;

    /**
     * Sloupec pro ID rodiče (PARENT)
     * @ORM\Column(type="integer")
	 * @ORM\ManyToOne(targetEntity="AclRole")
	 * @JoinColumn(name="parent_id", referencedColumnName="id")
     */
    protected $parent;

	/*@ORM\OneToMany(targetEntity="AclRole", mappedBy="parent_id")*/
	protected $ac_children

Pro danou entitu bych měl její ID, Název, rodiče(ID rodiče) a pak v ArrayCollection Všechny její potomky a UserFasade bych měl jen metodu kde bych se ptal na rodiče dané role pro potřeby Dynamického ACL

Editoval Joacim (3. 2. 2016 10:58)

F.Vesely
Člen | 369
+
0
-

Nechapu, proc tam mas ty potomky a jeste k tomu na entitu User? To, ze je to uvedene v prikladu, jeste neznamena, ze to potrebujes. Ty potrebujes znat parent, protoze od nej dedis prava, children jsou ti k nicemu.

Overeni, jestli ma dana role na neco prava, si dej primo do te entity.

Joacim
Člen | 229
+
0
-

F.Vesely napsal(a):

Nechapu, proc tam mas ty potomky a jeste k tomu na entitu User? To, ze je to uvedene v prikladu, jeste neznamena, ze to potrebujes. Ty potrebujes znat parent, protoze od nej dedis prava, children jsou ti k nicemu.

Ok, zatím mám v entitě AclRole pouze napojeni na user. Potomci měli být na AclRole (byla to chyba které jsem si nevšimnul a již ji opravil) – potomci u každé role jsou mi zatím k ničemu to vím, že potřebuji rodiče daného potomka

Dynamická správa rolí a zdrojů

Overeni, jestli ma dana role na neco prava, si dej primo do te entity.

To jsem si myslel, že má být ve fasádě, v entitě dle dokumentace by měli být jen setry getry a validace ? Jen se ptám

navíc mi není jasné jak pořešit napojení doctrine a nette

Co budou Service, co Fasády a co Repositáře ? Nebo používáte jen Entity a fasády ?
Např: Třída BrowserInfo je service ? Vrací pouze informace o prohlížeči

Editoval Joacim (3. 2. 2016 13:00)

akadlec
Člen | 1326
+
0
-

Řek bych že si to zbytečně komplikuješ a hledáš problémy tam kde nejsou. Normálně si vytáhneš entity a přes getParent si pak přiřadíš těm rolím parenty v registraci rolí.

F.Vesely
Člen | 369
+
0
-

Joacim napsal(a):

To jsem si myslel, že má být ve fasádě, v entitě dle dokumentace by měli být jen setry getry a validace ? Jen se ptám

Ne, Entita by mela mit chovani napriklad isAllowed(), authenticate(), atd. A settery moc nedoporucuji pouzivat, radsi si je pojmenuj podle toho co delaji changePassword(), rename(), move(), atd. Pro povinne promenne pouzivej konstruktor.

Joacim
Člen | 229
+
0
-

akadlec napsal(a):

Řek bych že si to zbytečně komplikuješ a hledáš problémy tam kde nejsou. Normálně si vytáhneš entity a přes getParent si pak přiřadíš těm rolím parenty v registraci rolí.

Jo asi máš pravdu, třeba u entit dědím z BaseEntity je to kvůli tomu abych nemusel mít v entitě getry a setry (Kdyby se o to postará, bohužel netbeans to neumějí), tedy mohu mít

// Entita
protected $name;

//Fasáda
$en = new Entita();
$en->name = "Test"; // i kdyz je protected

Chtěl jsem použít 5 vrstvý model "VIZ ":https://www.zdrojak.cz/…ine-2/článek

App
--Model
-------Entities (entita = tabulka nejčastěji)
-------Facades (logika postavená nad repositories a services)
-------Repositories (orm dotazy)
-------Services (logika jako je třída Email(pro odeslání emailu), BrowserInfo (vrací informace o prohlížeči, verzi, ip adresu))

Moje otázky

  1. Co je a co není servisa ? Mám třídu Utils, Mailer, BrowserInfo (u těch si myslím že stačí jedna instance)
  2. Pokud budu mít Fasádu pro entitu a používám složití dotazy mám tyto dotazy ukládat jinam ?
// Ani nevim jak ji pojmenovat ???
// Pokud existuje záznam mladší než 30 minut vrat záznamy jinak vrat null a vytvoř nový záznam
// metoda v RestrictionFacade
private function getLoginRestricterByIp($ip) {
        $params = array("ip" => $ip, "datetime" => date('Y-m-d H:i:s', strtotime("-30 minutes"))); //(NOW() - INTERVAL 30 MINUTE)
        return $this->em->createQueryBuilder()->from(LoginRestricted::class, 'l')->where('ip = :ip AND attempt < 3 AND created >= :datetime')->setParameters($params)->orderBy('id', 'DESC');
    }
// Nette database
$record = $this->database->table('login_restricted')->where('ip_address = ? AND numberOfRetries < 3 AND created >= (NOW() - INTERVAL 30 MINUTE)', $ip_address)->order('id DESC');
  1. Lze si v nette vytvořit anonymní objekt, ke kterému bych mohl libovolně vytvářet proměnné objektu, abych mohl vytvořit objekt(který nemá třídu) toho co chci v entitě zeditovat a pak ji to jen předal(abych nemusel používat array, tedy $data["id"])
$data = new stdClass();
$data->id = 1;
$data->test = "test";

$this->editArticle($object, $data);

 public function editArticle(Article $article, $data)
 {
  $article->title = $data->title;
  $article->category = $data->category;
  $article->content = $data->content;
  $this->em->flush();
 }
  1. Jak v nette přepsat SQL výraz (NOW() – INTERVAL 30 MINUTE) přes Nette|Util|DateTime?
date('Y-m-d H:i:s', strtotime("-30 minutes")) // zatim přepsano takto

Editoval Joacim (3. 2. 2016 18:58)

F.Vesely
Člen | 369
+
+1
-
  1. Neni presne dane, co musis pouzivat. Ja treba moc Facade nepouzivam.
  2. Ja jsem si dost oblibil Query Objecty z Kdyby
  3. Muzes pouzivat stdClass nebo Nette/Utils/ArrayHash.
  4. (new DateTime)->modify("-30 minutes")

Editoval F.Vesely (3. 2. 2016 19:05)

Joacim
Člen | 229
+
0
-

F.Vesely napsal(a):

  1. Neni presne dane, co musis pouzivat. Ja treba moc Facade nepouzivam.
  2. Ja jsem si dost oblibil Query Objecty z Kdyby
  3. Muzes pouzivat stdClass nebo Nette/Utils/ArrayHash.
  4. (new DateTime)->modify("-30 minutes")

Díky moc za odpovědi na všechny dotazy, nesmírně mi to pomohlo.
Query Object je vlastně Doctrine QueryBuilder(ten už používám na jednoučelové dotazy a pod).

Snad už poslední dotaz: když mám fci

public function editArticle(Article $article, $data)
 {
  $article->title = $data->title;
  $article->category = $data->category;
  $article->content = $data->content;
  $this->em->flush();
 }

a neměl bych např definovaný atribut $data->category, nastaví se mi entyta a uloží do db sloupec $article->category jako hodnota null nebo u tohoto sloupce zůstanou hodnoty nezměněny.

Jde mi o to zda je fce edit použít univerzálně pro editacijak všech sloupců tak třeba jen dvou aniž bych musel volat editSloupec1 a pak editSloupec2

Editoval Joacim (3. 2. 2016 19:41)

F.Vesely
Člen | 369
+
+1
-

Nastavis hodnotu na null, tak se ti taky pri flush() ulozi do db jako null.

Joacim
Člen | 229
+
0
-

F.Vesely napsal(a):

Nastavis hodnotu na null, tak se ti taky pri flush() ulozi do db jako null.

To jsem si myslel, díky moc.
Takže pokud chci editovat pro danou entitu jen 3 položky z 10 musím si napsat novou metodu a nebo ošetřit každou proměnou třídy entity isset ($data->promena) což není pekny