Сохранять объекты в REST API вместо DB с помощью Doctrine 2

Это связано с моим другим вопросом: Персистентные объекты, использующие API REST .

Для проекта в Symfony2 мне нужно иметь возможность сохранять объекты, используя удаленный (сторонний) RESTful API . Я также хочу иметь возможность извлекать объекты с данными из этого API.

Другими словами, мои объекты сохраняются в сторонней базе данных . Они не сохранены в моей собственной базе данных. Всякий раз, когда мне нужно сохранять данные или находить данные, я использую их REST API.

Я был указан в нескольких библиотеках, в том числе и о самой доктрине . Однако ни один из них не предлагает мне то, что я ищу. Тот, что сделал Doctrine, является лучшим вариантом, но использует шаблон Active Record и не предлагает все сладкие вещи Doctrine 2. Не поймите меня неправильно, я давно использую приложения Active Record, но теперь я влюбился в шаблон Data Mapper от Doctrine.

В идеале, я бы хотел использовать ORM Doctrine и просто заменить часть, относящуюся к базе данных, логикой, которая сохраняет объекты, используя вызов API. (и, конечно, извлекает их, используя тот же API). Таким образом, я могу сохранить свои объекты, используя примерно такой же синтаксис:

// current way to save $entity in database: $em = $this->getDoctrine()->getManager(); $em->persist($entity); $em->flush(); // desired way to save $entity using REST API: // (just an example, it doesn't have to be exactly like this) $em = $this->getDoctrine()->getManager('rest'); $em->persist($entity); $em->flush(); 

Обратите внимание: я не пытаюсь создать собственный API, я просто пытаюсь связаться с сторонним API, чтобы сохранить мои сущности. Я относительно новичок в Доктрине, но мне это нравится. Мне очень нравится идея отделить логику персистентности от сущностей, но пока я не могу узнать, как я могу использовать это, чтобы сохранить их с помощью API.

В документации Symfony есть статья, описывающая, как работать с несколькими Entity Managers . Я ищу решение, подобное этому, но с менеджером сущностей, который позволяет мне использовать REST вместо DB.

Я пытался самостоятельно настроить ORM Doctrine, но я только переписываю половину своего кода, потому что он (кажется) слишком тесно связан с логикой, специфичной для базы данных. Конечно, я мог бы делать что-то глупое.

Итак, мой вопрос заключается в том, есть ли способ заменить / переопределить части ORM, относящиеся к базе данных, с помощью пользовательских ? Не переписывая много вещей, которые должны быть общими для всех методов сохранения? Это было сделано раньше? Или это просто невозможно, потому что Doctrine предназначен для использования с базой данных и недостаточно гибка для других целей?

Мой собственный прогресс

CakePHP, похоже, может это сделать, позволяя вам определить пользовательский DataSource . Таким образом, вы можете сохранить свои модели с использованием базы данных SQL, но также использовать API, сеансы и т. Д. Я хочу сделать примерно то же самое, но использовать Doctrine вместо CakePHP.

Обновление 1

Фактические запросы к базе данных, похоже, выполняются классом Doctrine\ORM\Persisters\BasicEntityPersister . Существует несколько других классов xxxPersister для решения разных типов наследования. Возможно, можно заменить классы xxxPersister нашими собственными, поэтому мы можем заменить код DB кодом REST API.

Объекты persister создаются в getEntityPersister() класса Doctrine\ORM\UnitOfWork . Doctrine\ORM\UnitOfWork жестко закодированы, поэтому нам нужно переопределить Doctrine\ORM\UnitOfWork если мы хотим использовать наши собственные Doctrine\ORM\UnitOfWork .

Обновление 2

Doctrine\ORM\UnitOfWork , по-видимому, жестко закодирован в Doctrine\ORM\EntityManager , поэтому нам нужно переопределить это. Однако этот класс, как представляется, содержит некоторые части, относящиеся к базе данных. Например, для его конструктора в качестве параметра требуется объект Doctrine\DBAL\Connection . Возможно, лучше создать собственный EntityManger (реализация интерфейса Doctrine\Common\Persistence\ObjectManager ), если это не займет слишком много времени / усилий.

Обновление 3

Код базы данных для поиска / загрузки / поиска живет в том же классе, что и код для сохранения / удаления и т. Д.: Классы Doctrine\ORM\Persisters\xxxPersister . Поэтому, если мы можем заменить их своими собственными, чтобы сохранить объекты, мы также можем восстановить объекты. Например, когда вы вызываете $entityRepository->findAll() , он возвращает $entityRepository->findBy(array()) (поскольку findAll() является просто псевдонимом для findBy(array()) ), который будет запускать следующий код :

 $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); return $persister->loadAll($criteria, $orderBy, $limit, $offset); 

Другими словами, как только мы получим EntityManager для создания правильных UnitOfWork и xxxPersister , мы сможем использовать методы find в EntityRepository .

Обновление 4

Я обнаружил, что новая функция разработана для Doctrine: custom persisters (также см. Это ). Это должно облегчить использование пользовательского класса persister. Я еще не знаю, сможет ли он создать не-БД-персидер, но он выглядит многообещающим. Однако последние обновления были в августе, поэтому я не уверен, что он все еще находится в активной разработке.

Вы можете использовать https://github.com/doctrine/rest для создания клиента REST, который ведет переговоры с целевым сервером. Существенной частью здесь является сопоставление сущности (локальной) с REST API (target).

Короче говоря: Doctrine2 (локальная БД) -> Клиент Rest (привязка объекта к отдыху) -> Запрос (целевой сервер)

Doctrine / Rest также обеспечивает обратную связь: сервер оставления Doctrine, чтобы разоблачить ваши локальные объекты через REST (запросы на ваш сервер). Но это не то, что вы ищете.

DoctrineRestDriver точно выполняет то, что вы ищете. https://github.com/CircleOfNice/DoctrineRestDriver

Настроить Доктрину:

doctrine: dbal: driver_class: "Circle\\DoctrineRestDriver\\Driver" host: "http://www.your-url.com/api" port: 80 user: "Circle" password: "CantRenember"

Объект построения:

 /** * This annotation marks the class as managed entity: * * @ORM\Entity * * You can either only use a resource name or the whole url of * the resource to define your target. In the first case the target * url will consist of the host, configured in your options and the * given name. In the second one your argument is used as it is. * Important: The resource name must begin with its protocol. * * @ORM\Table("products|http://www.yourSite.com/api/products") */ class Product { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=100) */ private $name; public function getId() { return $this->id; } public function setName($name) { $this->name = $name; return $this; } public function getName() { return $this->name; } } 

Предположим, мы использовали значение http://www.yourSite.com/api/products для аннотации @Table для сущности продукта.

контроллер:

 <?php namespace CircleBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\HttpFoundation\Response; class UserController extends Controller { /** * Sends the following request to the API: * POST http://www.yourSite.com/api/products HTTP/1.1 * {"name": "Circle"} * * Let's assume the API responded with: * HTTP/1.1 200 OK * {"id": 1, "name": "Circle"} * * Response body is "1" */ public function createAction() { $em = $this->getDoctrine()->getManager(); $entity = new CircleBundle\Entity\Product(); $entity->setName('Circle'); $em->persist($entity); $em->flush(); return new Response($entity->getId()); } /** * Sends the following request to the API by default: * GET http://www.yourSite.com/api/products/1 HTTP/1.1 * * which might respond with: * HTTP/1.1 200 OK * {"id": 1, "name": "Circle"} * * Response body is "Circle" */ public function readAction($id = 1) { $em = $this->getDoctrine()->getManager(); $entity = $em->find('CircleBundle\Entity\Product', $id); return new Response($entity->getName()); } /** * Sends the following request to the API: * GET http://www.yourSite.com/api/products HTTP/1.1 * * Example response: * HTTP/1.1 200 OK * [{"id": 1, "name": "Circle"}] * * Response body is "Circle" */ public function readAllAction() { $em = $this->getDoctrine()->getManager(); $entities = $em->getRepository('CircleBundle\Entity\Product')->findAll(); return new Response($entities->first()->getName()); } /** * After sending a GET request (readAction) it sends the following * request to the API by default: * PUT http://www.yourSite.com/api/products/1 HTTP/1.1 * {"name": "myName"} * * Let's assume the API responded the GET request with: * HTTP/1.1 200 OK * {"id": 1, "name": "Circle"} * * and the PUT request with: * HTTP/1.1 200 OK * {"id": 1, "name": "myName"} * * Then the response body is "myName" */ public function updateAction($id = 1) { $em = $this->getDoctrine()->getManager(); $entity = $em->find('CircleBundle\Entity\Product', $id); $entity->setName('myName'); $em->flush(); return new Response($entity->getName()); } /** * After sending a GET request (readAction) it sends the following * request to the API by default: * DELETE http://www.yourSite.com/api/products/1 HTTP/1.1 * * If the response is: * HTTP/1.1 204 No Content * * the response body is "" */ public function deleteAction($id = 1) { $em = $this->getDoctrine()->getManager(); $entity = $em->find('CircleBundle\Entity\Product', $id); $em->remove($entity); $em->flush(); return new Response(); } } 

Вы даже можете использовать DQL или собственные запросы.

Поскольку готовое к использованию решение было недоступно, я решил написать свое. Я назвал его RAPL . Он сильно вдохновлен ORM от Doctrine (на самом деле он использует многие интерфейсы, предоставляемые Doctrine Common).

Используя RAPL, я могу просто написать небольшой файл YAML для настройки сопоставления между моими сущностями и веб-службой, что позволяет мне сохранять / извлекать объекты, используя пользовательский EntityManager.

Я думаю, вы не в порядке.
Сейчас я не готов вникать в документацию, но я понимаю доктрину как:

ORM -> DQL (язык запросов доктрины) -> dbal -> Некоторая база данных sql

И пункт для реализации вы используете в DBAL как пользовательский драйвер базы данных.

Я думаю, что создать общую REST-Driver интересную функцию, и это упростит интеграцию с сторонними службами.

Я не уверен, но вы можете попытаться использовать события обратного вызова жизненного цикла для объектов для выполнения постоянной логики через REST.

Я хотел сделать аналогичную вещь, поэтому я создал эту библиотеку, чтобы помочь разоблачить объекты доктрины как ресурсы RESTful. Он имеет множество возможностей и позволяет точно определить, что вы хотите открыть с помощью методов pull (GET) и push (POST / PUT / PATCH).

http://leedavis81.github.io/drest/

https://github.com/leedavis81/drest

Надеюсь, поможет