Speichern Sie Entitäten mithilfe von Lehre 2 in einer REST-API statt in einer DB

Lesezeit: 11 Minuten

Dies hängt mit meiner anderen Frage zusammen: Persistente Entitäten, die eine REST-API verwenden.

Für ein Projekt in Symfony2 muss ich in der Lage sein, Entitäten zu speichern Verwenden einer Remote-RESTful-API (Drittanbieter).. Ich möchte auch in der Lage sein, Entitäten mit Daten von dieser API abzurufen.

Mit anderen Worten, Meine Objekte werden in der Datenbank des Drittanbieters gespeichert. Sie sind nicht in meiner eigenen Datenbank gespeichert. Wann immer ich Daten speichern oder Daten finden muss, verwende ich ihre REST-API.

Ich wurde auf mehrere Bibliotheken hingewiesen, darunter eine, die von Doctrine selbst gemacht wurde. Aber keiner von ihnen bietet mir, was ich suche. Das von Doctrine hergestellte ist die beste Option, verwendet jedoch das Active Record-Muster und bietet nicht all das süße Doctrine 2-Zeug. Verstehen Sie mich nicht falsch, ich verwende seit langem Active Record-Implementierungen, aber jetzt habe ich mich in das Data Mapper-Muster von Doctrine verliebt.

Idealerweise möchte ich das ORM von Doctrine verwenden und einfach den datenbankspezifischen Teil durch Logik ersetzen, die Entitäten mithilfe eines API-Aufrufs speichert. (und ruft sie natürlich mit derselben API ab). Auf diese Weise kann ich meine Entitäten mit ungefähr derselben Syntax speichern:

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

Beachten Sie, dass ich nicht versuche, meine eigene API zu erstellen, sondern einfach versuche, mit einer Drittanbieter-API zu kommunizieren, um meine Entitäten zu speichern. Ich bin relativ neu in Doctrine, aber bisher gefällt es mir. Ich mag die Idee, die Persistenzlogik von den Entitäten zu trennen, aber bisher kann ich nicht herausfinden, wie ich sie verwenden kann, um sie mithilfe einer API zu speichern.

Es gibt einen Artikel in der Symfony-Dokumentation, der beschreibt wie man mit mehreren Entity Managern arbeitet. Ich suche nach einer ähnlichen Lösung, aber mit einem Entitätsmanager, der es mir ermöglicht, REST anstelle der DB zu verwenden.

Ich habe versucht, das ORM von Doctrine selbst zu optimieren, aber am Ende schreibe ich nur die Hälfte ihres Codes neu, weil es (scheint) zu eng mit der datenbankspezifischen Logik gekoppelt ist. Ich könnte natürlich etwas Dummes tun.

Also meine Frage ist, Gibt es eine Möglichkeit, die datenbankspezifischen Teile des ORM von Doctrine durch benutzerdefinierte Teile zu ersetzen / zu überschreiben?? Ohne viele Dinge neu zu schreiben, die für alle Persistenzmethoden gleich sein sollten? Wurde es schon einmal gemacht? Oder ist es einfach nicht möglich, weil Doctrine für die Verwendung mit einer Datenbank vorgesehen ist und für andere Anwendungen nicht flexibel genug ist?

Mein eigener Fortschritt

CakePHP scheint dazu in der Lage zu sein, indem es Sie einen Benutzerdefiniert definieren lässt Datenquelle. Auf diese Weise können Sie Ihre Modelle mit einer SQL-Datenbank speichern, aber auch mit einer API, Sitzungen usw. Ich möchte ungefähr dasselbe tun, aber mit Doctrine anstelle von CakePHP.

Aktualisierung 1

Die eigentlichen Datenbankabfragen scheinen von der ausgeführt zu werden
Doctrine\ORM\Persisters\BasicEntityPersister Klasse. Es gibt mehrere andere xxxPersister-Klassen, um mit verschiedenen Arten der Vererbung umzugehen. Es ist möglicherweise möglich, die xxxPersister-Klassen durch unsere eigenen zu ersetzen, sodass wir den DB-Code durch den REST-API-Code ersetzen können.

Die Persister-Objekte werden innerhalb der erstellt getEntityPersister() Methode der Doctrine\ORM\UnitOfWork Klasse. Die Klassennamen sind fest codiert, daher müssen wir sie überschreiben Doctrine\ORM\UnitOfWork wenn wir unsere eigenen Persister verwenden wollen.

Aktualisierung 2

Doctrine\ORM\UnitOfWork scheint fest einprogrammiert zu sein Doctrine\ORM\EntityManager, also müssen wir auch diesen überschreiben. Diese Klasse scheint jedoch einige datenbankspezifische Teile zu enthalten. Zum Beispiel benötigt der Konstruktor a Doctrine\DBAL\Connection Objekt als Parameter. Vielleicht ist es besser, einen eigenen EntityManager zu erstellen (Implementierung der Doctrine\Common\Persistence\ObjectManager Schnittstelle), solange das nicht zu viel Zeit / Aufwand kostet.

Aktualisierung 3

Der datenbankspezifische Code zum Abrufen/Laden/Finden von Objekten befindet sich in derselben Klasse wie der Code zum Persistieren/Löschen usw.: the Doctrine\ORM\Persisters\xxxPersister Klassen. Wenn wir also in der Lage sind, sie durch unsere eigenen zu ersetzen, können wir zum Persistieren von Objekten auch Objekte abrufen. Wenn du anrufst $entityRepository->findAll()zum Beispiel wird es zurückkehren $entityRepository->findBy(array()) (Weil findAll() ist einfach ein Alias ​​für findBy(array())), die den folgenden Code ausführt:

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

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

Mit anderen Worten, sobald wir bekommen EntityManager Recht zu schaffen UnitOfWork und xxxPersister Objekte, werden wir in der Lage sein, die zu verwenden find Methoden in der EntityRepository.

Aktualisierung 4

Ich habe entdeckt, dass eine neue Funktion für Doctrine entwickelt wird: benutzerdefinierte Persister (siehe auch Dies). Dies sollte es einfacher machen, eine benutzerdefinierte Persister-Klasse zu verwenden. Ich weiß noch nicht, ob es uns ermöglichen wird, einen Nicht-DB-Persister zu erstellen, aber es sieht vielversprechend aus. Die letzten Updates waren jedoch im August, daher bin ich mir nicht sicher, ob es sich noch in der aktiven Entwicklung befindet.

  • Verwenden Sie das FOSRESTBundle?

    – Michael Villeneuve

    12. Dezember 2013 um 23:40 Uhr

  • Nein. Ich möchte keine eigene API erstellen. Ich möchte meine Entitäten mit einer vorhandenen Drittanbieter-API beibehalten.

    – Nic Wortel

    12. Dezember 2013 um 23:42 Uhr

  • Können Sie ein konkreteres Beispiel für diese Aussage geben? “Ich würde gerne das ORM von Doctrine verwenden und einfach den datenbankspezifischen Teil durch Logik ersetzen, die Entitäten über einen API-Aufruf speichert.” Es gibt etwas, das mir fehlen muss. Denn bevor Sie Ihre Daten an Ihre DB übermitteln, können Sie damit machen, was Sie wollen.

    – Michael Villeneuve

    12. Dezember 2013 um 23:50 Uhr

  • Ja, sorry, ich hätte deine andere Frage lesen sollen, jetzt verstehe ich viel besser, was du zu tun versuchst. Ich werde versuchen, es herauszufinden, scheint eine sehr interessante Frage zu sein.

    – Michael Villeneuve

    12. Dezember 2013 um 23:55 Uhr

  • Kein Problem, ich glaube, meine Frage ist nur etwas vage. Es sollte klar sein, ohne die andere Frage zu lesen. Trotzdem danke für deine Zeit und Mühe. Wenn Sie etwas finden, lassen Sie es mich wissen!

    – Nic Wortel

    12. Dezember 2013 um 23:58 Uhr

Sie könnten verwenden https://github.com/doctrine/rest um einen REST-Client zu bauen, der mit dem Zielserver kommuniziert. Der wesentliche Teil hier ist die Zuordnung von Entität (lokal) zu REST-API (Ziel).

Kurz gesagt: Doctrine2 (lokale DB) -> Rest Client (Entity to Rest Mapping) -> Request (Zielserver)

Doctrine/Rest bietet auch den umgekehrten Weg: einen Doctrine Rest-Server, um Ihre lokalen Entitäten über REST (Anfragen an Ihren Server) bereitzustellen. Aber das ist nicht das, wonach Sie suchen.

  • Danke, aber ich habe die REST-Bibliothek von Doctrine bereits in meiner Frage erwähnt. Es verwendet jedoch das ActiveRecord-Muster, das ich zugunsten des DataMapper-Musters von Doctrine 2 zu vermeiden versuche, um die Dinge flexibler zu halten. Meine Wesen sollten nicht wissen, wie sie gerettet werden.

    – Nic Wortel

    16. Dezember 2013 um 10:15 Uhr

  • Je mehr ich über dieses Problem nachdenke, desto mehr würde ich sagen, dass REST eine Controller- und Service-Layer-Sache ist und nicht in das Modell gehört. Wenn Sie Modelle/Entitäten mit REST kombinieren möchten, schreiben Sie einen benutzerdefinierten RestEntityManager und fügen Sie eine RestService-Schicht (eine, die Sie auch in einem Controller verwenden würden, um REST-Anforderungen auszuführen) und einen EntityToRestMapper (zum Erstellen der Restrouten) ein – wenn Sie es sind Kommentieren Sie Ihre Entitäten nicht mit Routeninformationen.

    – Jens A. Koch

    17. Dezember 2013 um 1:32 Uhr


  • Leider hat diese Antwort mein Problem nicht wirklich gelöst. Der Ansatz, den Sie in Ihrem Kommentar beschreiben, klingt jedoch interessant und ähnelt der Lösung, an der ich gerade arbeite, ein wenig. Es ist besser als die anderen Antworten, also gebe ich Ihnen das Kopfgeld. Es wäre schön, wenn Sie Ihre Antwort durch das ersetzen / erweitern könnten, was Sie hier kommentiert haben. Vielleicht fallen Ihnen einige Dinge ein, an die ich noch nicht gedacht hatte (zumal ich Symfony / Doctrine relativ neu bin).

    – Nic Wortel

    22. Dezember 2013 um 13:08 Uhr

DoctrineRestDriver macht genau das, wonach Sie suchen.
https://github.com/CircleOfNice/DoctrineRestDriver

Doktrin konfigurieren:

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

Entität erstellen:

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

Nehmen wir an, wir haben den Wert verwendet http://www.yourSite.com/api/products für die @Table-Anmerkung der Produktentität.

Regler:

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

Sie können sogar DQL oder native Abfragen verwenden.

Da eine fertige Lösung nicht verfügbar war, beschloss ich, meine eigene zu schreiben. ich nannte es RAPL. Es ist stark von Doctrines ORM inspiriert (tatsächlich verwendet es viele der von Doctrine Common bereitgestellten Schnittstellen).

Mit RAPL kann ich einfach eine kleine YAML-Datei schreiben, um die Zuordnung zwischen meinen Entitäten und dem Webdienst zu konfigurieren, sodass ich Entitäten mit dem benutzerdefinierten EntityManager speichern/abrufen kann.

Ich glaube, du bist auf dem falschen Weg.
Ich bin jetzt nicht bereit, mich mit der Dokumentation zu befassen, aber ich verstehe den Doktrinenstapel als:

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

Und Punkt für die Implementierung, die Sie in DBAL als benutzerdefinierten Datenbanktreiber anbieten.

Ich denke, dass die Erstellung eines gemeinsamen REST-Treibers eine wirklich interessante Funktion ist und eine einfache Integration mit Diensten von Drittanbietern ermöglicht.

Ich bin mir nicht sicher, aber Sie können versuchen, es zu verwenden Lebenszyklus-Callback-Ereignisse für Entitäten, um persistente Logik über REST auszuführen.

Ich wollte etwas Ähnliches tun, also habe ich diese Bibliothek erstellt, um Doktrinentitäten als RESTful-Ressourcen verfügbar zu machen. Es verfügt über eine ganze Reihe von Funktionen und ermöglicht es Ihnen, genau zu definieren, was Sie über Pull- (GET) und Push-Methoden (POST/PUT/PATCH) verfügbar machen möchten.

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

https://github.com/leedavis81/drest

Ich hoffe es hilft

1054590cookie-checkSpeichern Sie Entitäten mithilfe von Lehre 2 in einer REST-API statt in einer DB

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy