Как да свържа множество релации в symfony2 с доктрина?

Опитвам се да създам приложение symfony2. Основната идея на проекта е, че има събитие, на което са поканени много гости и те са категоризирани. Създадох релационен модел за всички обекти.

Има 4 маси:

  1. Гости - който е поканен
  2. Категория - към коя категория/категории принадлежи?
  3. Събитие - събитието, на което са поканени
  4. Guest_Event (присъствие)

Стигнах до следните схеми:

xxxxBundle\Entity\Guest:
  type: entity
  table: guest
  id:
    id:
      type: integer
      generator: { strategy: AUTO }
  fields:
    name:
      type: string
      length: 100
      nullable: false
    surname:
      type: string
      length: 100
      nullable: false   
    email:
      type: string
      length: 255
      nullable: true
    address:
      type: string
      length: 255
      nullable: true
    phone:
      type: string
      length: 10
    description:
      type: text
    created_at:
      type: datetime
    updated_at:
      type: datetime
      nullable: true   
    token:
      type: string
      length: 255
      unique: true
    is_activated:
      type: boolean
      nullable: true
  manyToOne:
    category:
      targetEntity: Category
      inversedBy: guest
      joinColumn:
        name: category_id
        referencedColumnName: id
  lifecycleCallbacks:
    prePersist: [ setCreatedAtValue ]
    preUpdate: [ setUpdatedAtValue ]

Категория

xxxxBundle\Entity\Category:
  type: entity
  table: category
  id:
    id:
      type: integer
      generator: { strategy: AUTO }
  fields:
    name:
      type: string
      length: 255
      unique: true
  oneToMany:
    guests:
      targetEntity: Guest
      mappedBy: category
    attend:
      targetEntity: Attendance
      mappedBy: category

Събитие

xxxxxBundle\Entity\Event:
  type: entity
  table: event
  id:
    id:
      type: integer
      generator: { strategy: AUTO }
  fields:
    name:
      type: string
      length: 100
      nullable: false
    location:
      type: string
      length: 255
      nullable: true
    scheduled_at:
      type: datetime
  manyToMany:
    category:
      targetEntity: guest
      inversedBy: event
      joinColumn:
        name: event_id
        referencedColumnName: id
  • Един гост може да принадлежи към множество категории (manyToOne)
  • Една категория ще има много гости (manyToOne)
  • Гост може да присъства на много събития (manyToOne)
  • Едно събитие може да има много придружители (manyToMany?)
  • таблицата за присъствие (guest_event) трябва да бъде таблица за присъединяване?

Малко съм объркан относно ORM и кодирането на доктрината. Създаването на таблиците чрез SQL код или phpmyadmin ми се струва много по-лесно, но искам да вървя по трудния път! Документацията изглежда объркваща, защото всеки урок предлага различни неща, а секцията ORM на доктрината в книгата symfony2 няма пълен пример, а части от код.

Как мога да коригирам таблиците си, за да включват всички спецификации?


person Radolino    schedule 06.08.2012    source източник
comment
Трябва да сте по-конкретни по въпроса си. Каква е истинската? Какво мислиш, че не е наред с твоя модел? Между другото, ще оцените нещата с Doctrine, особено с Sf2 формуляри и заявки за данни...   -  person gremo    schedule 07.08.2012
comment
Всъщност не знам как да свържа таблиците между тях. В чист SQL бих могъл да използвам външни ключове, но доколкото знам, не е нужно да декларирате, че това е връзка много към едно или едно към едно и т.н. Тъй като doctrine & yaml изискват такива неща, това е малко объркващо.   -  person Radolino    schedule 07.08.2012
comment
Забравих да кажа (ако това е първото ви приложение Sf2), отидете за анотации. Направете PHP класове и оставете доктрината да генерира сетери/гетери. По този начин можете да имате дефиниране на поле плюс валидиране в един и същи файл (клас).   -  person gremo    schedule 07.08.2012
comment
Не трябва да правите нищо, това е доктрина, която ще генерира генериране на външни ключове вместо вас. Искате да зададете nullable=false (по подразбиране е true) за тези външни ключове, които не могат да бъдат null: docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/   -  person gremo    schedule 07.08.2012
comment
Смятате ли, че анотациите са по-лесни от yaml? Чета урока за jobeet Sf2 и той използва yml, така че предположих, че така е по-лесно ens.ro/2012/03/27/symfony2-jobeet-day-3-the-data-model Как да свържа два обекта (guests_id и events_id)? Това е отношение много към много и изглежда най-трудно от всички за кодиране.   -  person Radolino    schedule 07.08.2012
comment
Бих искал идеята да има дефиниция + валидиране на едно и също място (php), вместо да използвам yaml, но зависи от вас :) За много към много, ако няма допълнителни атрибути, можете да настроите много към много двупосочни ( ако имате нужда) връзка между гост и събитие docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/.   -  person gremo    schedule 07.08.2012
comment
Между другото, някои карти ми се струват грешни, ще публикувам отговор възможно най-скоро.   -  person gremo    schedule 07.08.2012


Отговори (4)


Моите два цента:

Един гост може да принадлежи към няколко категории

Толкова много гости могат да принадлежат към много категории, така че това е много към много гости. Ако приемем, че притежаващата страна е Guest:

xxxxBundle\Entity\Guest:
  manyToMany:
    categories:
      targetEntity: Category
      inversedBy: guests
      joinTable:
         name: guests_categories

Една категория ще има много гости (manyToOne)

Ако категорията ще има много гости, защо е много към едно? Много категории могат да бъдат присвоени на много гости:

xxxxBundle\Entity\Category:
  manyToMany:
    guests:
      targetEntity: Guest
      mappedBy: categories

Ако те разбирам правилно, една категория може да съществува дори без гост, и обратното, гост може да съществува дори без категория.

А за връзката гости/събития, ще отида отново за много към много / много към много. Разгледайте тук и се запитайте: един/много типове от моя обект могат да имат един/много видове друг обект?

person gremo    schedule 06.08.2012
comment
Много благодаря за предложенията. Това е, което търсих. Прав си за категорията с много гости. Това е много към много. Доколкото разбирам, предполагате, че всички отношения са многокъммного между тях. Една категория може да съществува без гост, но какъв е смисълът? Целта на приложението е да категоризира. Предполагам, че може да се остави нула, така че това не е голяма работа. Друг въпрос е този, който се отнася до връзката гост/събития. Трябва ли да създам таблица за свързване само за да свържа тези две? Искам да съпоставя идентификатора на госта с category.id и event.id. - person Radolino; 07.08.2012
comment
Да, за мен имате само отношения много към много (от двете страни на участващите субекти). С много към много от двете страни ВИНАГИ имате таблица за свързване, с (разбира се) 2 външни ключа. Но не можете да добавяте допълнителни атрибути (колони) към обединена таблица. Ако имате нужда от тях (например created_at, за да запишете кога гост е бил свързан към събитие), трябва да свържете двата обекта, като използвате друг обект между тях (да речем GuestEvent). Ако имате други въпроси, задайте нов (не тук). - person gremo; 07.08.2012
comment
Значи ли това, че имате таблица за присъединяване на guests_categories, таблица за присъединяване на guests_events и обект GuestEvent? Истината е, че искам да добавя допълнителен булев атрибут, докато конкретният гост ще присъства на конкретното събитие: Guest_id/Event_Id/Attendance. Значи имате таблица за присъединяване с Guest_Id, Event_Id и друга с Guest_Id, Event_Id, Attendance? Не е ли разочароващо? - person Radolino; 07.08.2012

Горещо препоръчвам да си проправите път през официалната документация на Symfony 2: http://symfony.com/doc/master/book/doctrine.html

Той ви превежда стъпка по стъпка през процеса на инициализиране на базата данни, създаване на обект, картографирането му към базата данни и след това използване на обекта.

Опитвате се да разберете множество концепции, като четете разнообразна документация. Фактът, че изглежда имате yaml файлове във вашата директория на обекти, показва основна липса на разбиране.

Първо прегледайте урока, след което добавете реалните си обекти. Това е доста ясно, след като накарате няколко неща да работят.

person Cerad    schedule 06.08.2012
comment
Прочетох много модули от документацията и успешно създадох два проекта на symphony2 досега, но те използват само една таблица, така че правя крачка напред от това и опитвам късмета си с връзките! - person Radolino; 07.08.2012
comment
Продължете да четете връзката, която ви дадох. Търсене на метаданни за съпоставяне на релации. - person Cerad; 07.08.2012

Промених решението си и използвах анотации, тъй като yaml ми изглежда объркващо.

До тук :

За обекта Гост

<?php
// xxxxBundle/Entity/Guest.php

namespace xxxxBundle\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\MaxLength;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="guest")
 */
class Guest
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
    * @ManyToMany(targetEntity="Category", inversedBy="guests")
    * @JoinTable(name="guests_categories")
    */
    protected $categories;

    /**
    * @ManyToMany(targetEntity="Event", inversedBy="events")
    * @JoinTable(name="guests_events")
    */
    protected $events;

    /**
     * @ORM\Column(type="string", length=30)
     */
    protected $name;

    /**
     * @ORM\Column(type="string", length=30)
     */
    protected $surname;

    /**
     * @ORM\Column(type="string", length=30)
     */
    protected $email;

    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $address;

    /**
     * @ORM\Column(type="string", length=10)
     */
    protected $phone;

    /**
     * @ORM\Column(type="string", length=10)
     */
    protected $mobile;

    /**
     * @ORM\Column(type="text")
     */
    protected $description;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $created_at;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $updated_at;

    /**
     * @ORM\Column(type="string")
     */
    protected $token;

    /**
     * @ORM\Column(type="boolean")
     */
    protected $is_activated;

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

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('name', new NotBlank());
        $metadata->addPropertyConstraint('surname', new NotBlank());
        $metadata->addPropertyConstraint('email', new Email(array(
            'message' => 'I do not like invalid emails. Give me a real one!')));

        $metadata->addPropertyConstraint('phone', new MaxLength(10));
        $metadata->addPropertyConstraint('mobile', new MaxLength(10));

    }    

}

За обекта Категория

<?php
// xxxxBundle/Entity/Category.php

namespace xxxxBundle\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\MaxLength;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="category")
 */
class Category
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
    * @ManyToMany(targetEntity="Guest", mappedBy="categories")
    */
    protected $guests;

    /**
     * @ORM\Column(type="string", length=30)
     */
    protected $name;

    /**
     * @ORM\Column(type="text")
     */
    protected $description;

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

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('name', new NotBlank());

    }    

}

За обекта Събитие

<?php
// xxxxBundle/Entity/Event.php

namespace xxxxBundle\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\MaxLength;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="event")
 */
class Event
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
    * @ManyToMany(targetEntity="Guest", mappedBy="categories")
    */
    protected $guests;

    /**
     * @ORM\Column(type="string", length=30)
     */
    protected $name;

    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $location;

    /**
     * @ORM\Column(type="text")
     */
    protected $description;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $scheduled_at;

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

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('name', new NotBlank());
        $metadata->addPropertyConstraint('location', new NotBlank());
    }    

}

Чувствам се объркан относно обекта Посещение. Обектът Attendance ще има следните променливи:

  • Guest_id
  • Event_id
  • Ще присъствам (да/не/може би)
  • Коментирайте
  • Replied_at
  • Актуализирано_в

Не знам кой ще бъде първичният ключ (или първичните ключове?). Таблицата db не трябва да има отделен идентификатор (или не?).

<?php
// xxxxBundle/Entity/Attendance.php

namespace xxxxBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="attendance")
 */
class Attendance
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
    * @ManyToMany(targetEntity="Guest", mappedBy="categories")
    */
    protected $guests;

    /**
    * @ManyToMany(targetEntity="Event", mappedBy="events")
    */
    protected $events;

    /**
     * @ORM\Column(type="string", length=3)
     */
    protected $will_attend;

    /**
     * @ORM\Column(type="text")
     */
    protected $description;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $replied_at;

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

}
person Radolino    schedule 07.08.2012
comment
Изпълних командата create entities, актуализацията на схемата --force и crud за всеки обект. Изглежда всичко работи добре! Освен когато се опитвам да редактирам запис на обекта Attendance, получавам следната грешка: Забележка: Недефиниран индекс: гости в /.../vendor/doctrine/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php ред 808 500 Вътрешна грешка на сървъра - ErrorException Предполагам, че има проблем с отношенията manytomany на обектите събития/гости. - person Radolino; 07.08.2012

Субектът за присъствие изисква лицето да декларира дали ще присъства на събитие или не. Това означава, че това е връзка „един към един“, защото за всяко събитие трябва да има ЕДНО събитие – ЕДИН човек – ЕДИН отговор на присъствие.

Решението е да промените следния код в обекта за присъствие:

/**
* @ORM\OneToOne(targetEntity="Guest")
*/
protected $guests;

/**
* @ORM\OneToOne(targetEntity="Event")
*/
protected $events;

След това стартирайте php app/console doctrine:generate:entities , php app/console doctrine:schema:update --force и командата crud Ако ги генерирате автоматично. Сега всичко работи добре.

person Radolino    schedule 07.08.2012