Guardar entidades en una API REST en lugar de una base de datos utilizando Doctrine 2


Esto está relacionado con mi otra pregunta: Entidades persistentes usando una API REST.

Para un proyecto en Symfony2 necesito ser capaz de persistir entidades usando una API RESTful remota (de terceros) . También quiero poder recuperar entidades con datos de esa API.

En otras palabras, mis objetos se guardan en la base de datos de terceros. Ellos no están guardados en mi propia base de datos. Cada vez que necesito guardar datos, o encontrar datos, utilizo su API REST.

He sido señalado a varias bibliotecas, incluyendo una hecha por la Doctrina misma. Sin embargo, ninguno de ellos me ofrece lo que estoy buscando. El hecho por Doctrine es la mejor opción, pero utiliza el patrón de Registro Activo y no ofrece todas las cosas de sweet Doctrine 2. No me malinterpretes, he estado usando implementaciones de Active Record durante mucho tiempo, pero ahora me he enamorado del patrón de Mapeador de datos de Doctrine.

Idealmente, me gustaría poder usar Doctrine de Doctrine y simplemente reemplace la parte específica de la base de datos con lógica que guarda las entidades mediante una llamada a la API. (y por supuesto los recupera usando esa misma API). De esta manera puedo guardar mis entidades usando aproximadamente la misma sintaxis:

// 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();

Tenga en cuenta que no estoy tratando de construir mi propia API, simplemente estoy tratando de comunicarse con una API de terceros con el fin de guardar mis entidades. Soy relativamente nuevo en la Doctrina, pero me está gustando hasta ahora. Me gusta mucho la idea de separar la lógica de la persistencia de las entidades, pero hasta ahora no puedo averiguar cómo puedo usar eso para guardarlas usando una API.

Hay un artículo en la documentación de Symfony, describiendo cómo trabajar con varios Entity Managers. Estoy buscando una solución similar a esta, pero con un entity manager que me permita usar REST en lugar de la base de datos.

He estado tratando de modificar el Doctrine de Doctrine yo mismo, pero solo termino reescribiendo la mitad de su código porque (parece ser) demasiado unido a la Lógica específica de la base de datos. Podría estar haciendo algo estúpido, por supuesto.

Entonces mi pregunta es, ¿hay alguna manera de reemplazar / anular las partes específicas de la base de datos del Doctrine de Doctrine con las personalizadas? Sin reescribir muchas cosas que deberían ser comunes para todos los métodos de persistencia? Se ha hecho antes? ¿O simplemente no es posible porque Doctrine está destinado a ser utilizado con una base de datos y no es lo suficientemente flexible para otros usos?

Mi propio progreso

CakePHP parece ser capaz de hacer esto, al permitirle definir una fuente de datos personalizada . De esta manera puede guardar sus modelos usando una base de datos SQL, pero también usando una API, sesiones, etc. Quiero hacer más o menos lo mismo, pero usando Doctrine en lugar de CakePHP.

Actualización 1

Las consultas de base de datos reales parecen ser ejecutadas por el Doctrine\ORM\Persisters\BasicEntityPersister clase. Hay varias otras clases xxxPersister, para tratar con diferentes tipos de herencia. Podría ser posible sustituir el Clases xxxPersister con nuestras propias, por lo que podemos reemplazar el código de la base de datos con el código de la API REST.

Los objetos persister se crean dentro del método getEntityPersister() del Doctrine\ORM\UnitOfWork clase. Los nombres de clase están codificados de forma rígida, por lo que necesitamos anular Doctrine\ORM\UnitOfWork si queremos usar nuestros propios persisters.

Actualización 2

Doctrine\ORM\UnitOfWork parece estar codificado en Doctrine\ORM\EntityManager, así que tenemos que anular ese también. Sin embargo, esta clase parece contener algunas partes específicas de la base de datos. Por ejemplo, es el constructor requiere un objeto Doctrine\DBAL\Connection como parámetro. Tal vez sea mejor crear nuestro propio EntityManger (implementando la interfaz Doctrine\Common\Persistence\ObjectManager), siempre y cuando no tome demasiado tiempo / esfuerzo.

Actualización 3

El código específico de la base de datos para recuperar/cargar/encontrar objetos vive en la misma clase que el código para persistir / eliminar, etc.: las clases Doctrine\ORM\Persisters\xxxPersister. Así que si somos capaces de reemplazarlos con los nuestros, con el fin de persistir objetos, podemos recuperar objetos también. Cuando call $entityRepository->findAll(), por ejemplo, devolverá $entityRepository->findBy(array()) (porque findAll() es simplemente un alias para findBy(array())) que ejecutará el siguiente código:

$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);

return $persister->loadAll($criteria, $orderBy, $limit, $offset);

En otras palabras, una vez que conseguimos EntityManager crear los objetos UnitOfWork y xxxPersister correctos, podremos usar los métodos find en el EntityRepository.

Actualización 4

Descubrí que se desarrolla una nueva característica para la Doctrina: persistidores personalizados (también vea esto). Esto debería facilitar el uso de un persister personalizado clase. Aún no se si nos permitirá crear un persister que no sea DB, pero parece prometedor. Sin embargo, las últimas actualizaciones fueron en agosto, así que no estoy seguro de si todavía está en desarrollo activo.

Author: Nic Wortel, 2013-12-13

6 answers

Puede usar https://github.com/doctrine/rest para construir un cliente REST, que habla con el servidor de destino. La parte esencial aquí es la asignación de la entidad (local) a la API REST (destino).

En resumen: Doctrine2 (base de datos local) -> Cliente Rest (asignación de entidad a rest) -> Solicitud (servidor de destino)

Doctrine/Rest proporciona también lo contrario: un servidor Doctrine Rest, para exponer sus entidades locales a través de REST (solicitudes a su servidor). Pero eso no es lo que estás buscando para.

 10
Author: Jens A. Koch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-16 03:00:21

DoctrineRestDriver está haciendo exactamente lo que usted está buscando. https://github.com/CircleOfNice/DoctrineRestDriver

Configurar Doctrina:

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

Entidad de construcción:

/**
 * 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;
    }
}

Supongamos que hemos utilizado el valor http://www.yourSite.com/api/products para la anotación @Table de la entidad product.

Controlador:

<?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();
    }
}

Incluso puede usar consultas DQL o nativas.

 7
Author: Tobias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-27 20:15:44

Como una solución lista para usar no estaba disponible, decidí escribir la mía. Lo llamé RAPL . Está fuertemente inspirado por el Doctrine de Doctrine (de hecho, utiliza muchas de las interfaces proporcionadas por Doctrine Common).

Usando RAPL simplemente puedo escribir un pequeño archivo YAML para configurar la asignación entre mis entidades y el servicio web, lo que me permite persistir/recuperar entidades utilizando el EntityManager personalizado.

 3
Author: Nic Wortel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-30 13:27:30

Creo que no estás bien.
No estoy listo para profundizar en la documentación ahora, pero entiendo doctrine stack como:

OR - > DQL (doctrine query language) ->dbal ->Some database sql

Y punto para la implementación que presenta en DBAL como controlador de base de datos personalizado.

Creo que crear REST-Driver común característica realmente interesante y hará fácil integración con servicios de terceros.

 2
Author: nonlux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-19 10:24:06

No estoy seguro, pero puede intentar usar eventos de devolución de llamada del ciclo de vida para que las entidades realicen una lógica persistente a través de REST.

 1
Author: lisachenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-21 13:06:24

Quería hacer algo similar, así que construí esta biblioteca para ayudar a exponer las entidades doctrine como recursos RESTful. Tiene una buena cantidad de características, y le permite definir exactamente lo que desea exponer a través de los métodos pull (GET) y push (POST/PUT/PATCH).

Http://leedavis81.github.io/drest /

Https://github.com/leedavis81/drest

Espero que ayude

 0
Author: Lee Davis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-20 09:05:50