Ярлыки

.htaccess (4) тестирование (8) шаблоны проектирования (3) css (5) Debian (6) docker (2) Doctrine2 (6) Git (6) html (4) java (6) javascript (13) jquery (11) LFS (3) linux (23) mac os (4) mod_rewrite (2) MSSQL (4) MySQL (18) ORM Doctrine (17) patterns (3) PDO (3) perl (7) PHP (64) PHPUnit (8) Python (15) SEO (2) Silex (1) SimpleXML (1) SQL (14) ssh (4) Ubuntu (24) Yii1 (1) Zend Framework (19) ZendFramework2 (8)

четверг, 16 февраля 2012 г.

Doctrine2. Введение.

Doctrine2 - object-relational mapper (ORM) для PHP 5.3.0+ Ядром является реализация шаблона Data Mapper

Что такое Entities?

 

Entities (классы модели) - объекты PHP, которым не надо расширять или реализовывать никакие абстрактные классы или интерфейсы. Есть некоторые ограничения, например,
- они не могут быть final или иметь методы final;
 - реализовывать clone или wakeup;
... Entity имеет свойства сохраняемые в базе данных и получаемые из нее.

 

 Пример модели: BUG TRACKER

 

Требования: Ошибка имеет описание, дату создания, статус, корреспондента и разработчика Ошибка может происходить с разными продуктами (платформами) Продукт имеет название Корреспондент и разработчик - оба пользователи системы Пользователь может сообщить об ошибке Назначенный разработчик должен исправить ошибку Пользователь может видеть все ошибки о которых он сообщил или для решения которых он назначен Ошибки могут быть представлены как список с постраничным выводом

 

Создание проекта

 

Установите Doctrine2
$ pear channel-discover pear.doctrine-project.org
$ pear install --alldeps doctrine/DoctrineORM
Создайте каталог проекта
$ mkdir project
$ cd project
Структура должна быть такой
project
|-- config
|   |-- xml
|   `-- yaml
`-- entities

 

 Начало проектирования модели

 

Создайте классы
// Bug.php
class Bug
{
    protected $id;
    protected $description;
    protected $created;
    protected $status;
}


// Product.php
class Product
{
    protected $id;
    protected $name;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}


// User.php
class User
{
    protected $id;
    public $name; // public for educational purpose, see below

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

Создание связей
// entities/Bug.php
use Doctrine\Common\Collections\ArrayCollection;

class Bug
{
    // ... (previous code)

    protected $products = null;

    public function __construct()
    {
        $this->products = new ArrayCollection();
    }
}

// entities/User.php
use Doctrine\Common\Collections\ArrayCollection;
class User
{
    // ... (previous code)

    protected $reportedBugs = null;
    protected $assignedBugs = null;

    public function __construct()
    {
        $this->reportedBugs = new ArrayCollection();
        $this->assignedBugs = new ArrayCollection();
    }
}
Для отладки классов не используйте var_dump() и тд, используйте Doctrine\Common\Util\Debug::dump() Создаем связи между пользователем и ошибкой
// entities/Bug.php
class Bug
{
    // ... (previous code)

    protected $engineer;
    protected $reporter;

    public function setEngineer($engineer)
    {
        $engineer->assignedToBug($this);
        $this->engineer = $engineer;
    }

    public function setReporter($reporter)
    {
        $reporter->addReportedBug($this);
        $this->reporter = $reporter;
    }

    public function getEngineer()
    {
        return $this->engineer;
    }

    public function getReporter()
    {
        return $this->reporter;
    }
}


// entities/User.php
class User
{
    // ... (previous code)

    protected $reportedBugs = null;
    protected $assignedBugs = null;

    // Используется именование методов в прошедшем времени,
    // это обозначает что назначение уже произошло и
    // и методы используются лишь для обеспечения согласованности ссылки. 
    // Очевидно что одностороннее использование методов 
    // User::addReportedBug() и User::assignedToBug()
    // в пользовательском коде не добавит ошибку в коллекцию
    // в свойствах Bug::$reporter или Bug::$engineer.
    // Использование этих методов и вызов Doctrine не обновят
    // представление коллекций в базе данных.

    public function addReportedBug($bug)
    {
        $this->reportedBugs[] = $bug;
    }

    public function assignedToBug($bug)
    {
        $this->assignedBugs[] = $bug;
    }
}

 

Метаданные отображения для классов модели

 

Далее надо добавить метаданные для Doctrine, чтобы классы модели могли быть сохранены в базе данных. Это можно сделать 3 способами: XML, YAML, аннотации. В первом и втором случае, мы создаем файлы и кладем их в каталоги project/config/xml и project/config/yaml соотвественно. В последнем - аннотации добавляются непосредственно в код класса. Пример для продукта
// entities/Product.php
/**
 * @Entity @Table(name="products")
 **/
class Product
{
    /** @Id @Column(type="integer") @GeneratedValue **/
    protected $id;
    /** @Column(type="string") **/
    protected $name;

    // .. (other code)
}

<!-- config/xml/Product.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                    http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

      <entity name="Product" table="products">
          <id name="id" type="integer">
              <generator strategy="AUTO" />
          </id>

          <field name="name" type="string" />
      </entity>
</doctrine-mapping>


# config/yaml/Product.dcm.yml
Product:
  type: entity
  table: products
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    name:
      type: string


Для поля id дерективы указывают на то, что будет использован механизм генерации родной для СУБД (AUTO INCREMENT для MySQL, например). Для Bug
// entities/Bug.php
/**
 * @Entity @Table(name="bugs")
 **/
class Bug
{
    /**
     * @Id @Column(type="integer") @GeneratedValue
     **/
    protected $id;
    /**
     * @Column(type="string")
     **/
    protected $description;
    /**
     * @Column(type="datetime")
     **/
    protected $created;
    /**
     * @Column(type="string")
     **/
    protected $status;

    /**
     * @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
     **/
    protected $engineer;

    /**
     * @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
     **/
    protected $reporter;

    /**
     * @ManyToMany(targetEntity="Product")
     **/
    protected $products;

    // ... (other code)
}


<!-- config/xml/Bug.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                    http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

    <entity name="Bug" table="bugs">
        <id name="id" type="integer">
            <generator strategy="AUTO" />
        </id>

        <field name="description" type="text" />
        <field name="created" type="datetime" />
        <field name="status" type="string" />

        <many-to-one target-entity="User" field="reporter" inversed-by="reportedBugs" />
        <many-to-one target-entity="User" field="engineer" inversed-by="assignedBugs" />

        <many-to-many target-entity="Product" field="products" />
    </entity>
</doctrine-mapping>

# config/yaml/Bug.dcm.yml
Bug:
  type: entity
  table: bugs
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    description:
      type: text
    created:
      type: datetime
    status:
      type: string
  manyToOne:
    reporter:
      targetEntity: User
      inversedBy: reportedBugs
    engineer:
      targetEntity: User
      inversedBy: assignedBugs
  manyToMany:
    products:
      targetEntity: Product

Тут определны две ссылки на сущность User. Имя класса связанной сущности задается тэгом targetEntity. Имеет место двунаправленная связь, поэтому с помощью тэга inversedBy указываем атрибут связанной сущности, соответствующей нашему объекту. И наконец для User
// entities/User.php
/**
 * @Entity @Table(name="users")
 **/
class User
{
    /**
     * @Id @GeneratedValue @Column(type="integer")
     * @var int
     **/
    protected $id;

    /**
     * @Column(type="string")
     * @var string
     **/
    protected $name;

    /**
     * @OneToMany(targetEntity="Bug", mappedBy="reporter")
     * @var Bug[]
     **/
    protected $reportedBugs = null;

    /**
     * @OneToMany(targetEntity="Bug", mappedBy="engineer")
     * @var Bug[]
     **/
    protected $assignedBugs = null;

    // .. (other code)
}


<!-- config/xml/User.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                    http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

     <entity name="User" table="users">
         <id name="id" type="integer">
             <generator strategy="AUTO" />
         </id>

         <field name="name" type="string" />

         <one-to-many target-entity="Bug" 
                         field="reportedBugs"
                         mapped-by="reporter" />
         <one-to-many target-entity="Bug" 
                         field="assignedBugs" 
                         mapped-by="engineer" />
     </entity>
</doctrine-mapping>

# config/xml/User.dcm.yml
User:
  type: entity
  table: users
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    name:
      type: string
  oneToMany:
    reportedBugs:
      targetEntity: Bug
      mappedBy: reporter
    assignedBugs:
      targetEntity: Bug
      mappedBy: engineer
Связи один-ко-многим уже определены на стороне владельца (Bug), поэтому поэтому просто указываем его атрибут с помощью тэга mappedBy.

 

Настройка EntityManager

 

Выборку и сохранение сущностей в базе данных обеспечивает EntityManager. Для этого его необходимо создать и настроить.
// bootstrap_doctrine.php

// See :doc:`Configuration <../reference/configuration>` for up to date autoloading details.
use Doctrine\ORM\Tools\Setup;

require_once "Doctrine/ORM/Tools/Setup.php";
Setup::registerAutoloadPEAR();

// Create a simple "default" Doctrine ORM configuration for XML Mapping
$isDevMode = true;
$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
// or if you prefer yaml or annotations
//$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/entities"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);

// database configuration parameters
$conn = array(
    'driver' => 'pdo_sqlite',
    'path' => __DIR__ . '/db.sqlite',
);

// obtaining the entity manager
$entityManager = \Doctrine\ORM\EntityManager::create($conn, $config);
Продолжение следует (или нет) ...

2 комментария:

  1. Генерировать БД из объектов - по моему это ересь !

    ОтветитьУдалить
    Ответы
    1. Не уверен, что понимаю о чем вы ;-) Но в общем нахожу принцип orm довольно актуальным

      Удалить