Подтвердить что ты не робот

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

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

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

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

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

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

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

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

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

Обновление 1

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

Объекты persister создаются в методе getEntityPersister() класса 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 разработана новая функция: пользовательские persisters (также см. это). Это должно облегчить использование пользовательского класса persister. Я еще не знаю, сможет ли он создать не-БД-персидер, но он выглядит многообещающим. Однако последние обновления были в августе, поэтому я не уверен, что он все еще находится в активной разработке.

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

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 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 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 или собственные запросы.

Ответ 4

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

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

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

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

Ответ 6

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

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

https://github.com/leedavis81/drest

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