Wie codiere ich Doctrine-Entitäten in der AJAX-Anwendung von Symfony 2.0 in JSON?

89

Ich entwickle eine Spiel-App und verwende Symfony 2.0. Ich habe viele AJAX-Anfragen an das Backend. Weitere Antworten sind die Konvertierung von Entitäten in JSON. Beispielsweise:

class DefaultController extends Controller
{           
    public function launchAction()
    {   
        $user = $this->getDoctrine()
                     ->getRepository('UserBundle:User')                
                     ->find($id);

        // encode user to json format
        $userDataAsJson = $this->encodeUserDataToJson($user);
        return array(
            'userDataAsJson' => $userDataAsJson
        );            
    }

    private function encodeUserDataToJson(User $user)
    {
        $userData = array(
            'id' => $user->getId(),
            'profile' => array(
                'nickname' => $user->getProfile()->getNickname()
            )
        );

        $jsonEncoder = new JsonEncoder();        
        return $jsonEncoder->encode($userData, $format = 'json');
    }
}

Und alle meine Controller tun dasselbe: Holen Sie sich eine Entität und codieren Sie einige ihrer Felder in JSON. Ich weiß, dass ich Normalisierer verwenden und alle Berechtigungen codieren kann. Was aber, wenn eine Entität Links zu einer anderen Entität gewechselt hat? Oder ist das Entitätsdiagramm sehr groß? Hast du irgendwelche Vorschläge?

Ich denke über ein Codierungsschema für Entitäten nach ... oder über das Verwenden NormalizableInterface, um das Radfahren zu vermeiden ..,

Dmytro Krasun
quelle

Antworten:

82

Eine weitere Option ist die Verwendung des JMSSerializerBundle . In Ihrem Controller tun Sie dann

$serializer = $this->container->get('serializer');
$reports = $serializer->serialize($doctrineobject, 'json');
return new Response($reports); // should be $reports as $doctrineobject is not serialized

Sie können die Serialisierung mithilfe von Anmerkungen in der Entitätsklasse konfigurieren. Siehe die Dokumentation unter dem obigen Link. So würden Sie beispielsweise verknüpfte Entitäten ausschließen:

 /**
* Iddp\RorBundle\Entity\Report
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Iddp\RorBundle\Entity\ReportRepository")
* @ExclusionPolicy("None")
*/
....
/**
* @ORM\ManyToOne(targetEntity="Client", inversedBy="reports")
* @ORM\JoinColumn(name="client_id", referencedColumnName="id")
* @Exclude
*/
protected $client;
Sofia
quelle
7
Sie müssen use JMS \ SerializerBundle \ Annotation \ ExclusionPolicy hinzufügen . Verwenden Sie JMS \ SerializerBundle \ Annotation \ Exclude. in Ihrer Entität und installieren Sie JMSSerializerBundle, damit dies funktioniert
ioleo
3
Funktioniert hervorragend, wenn Sie Folgendes ändern: Neue Antwort zurückgeben ($ reports);
Greywire
7
Da die Annotationen aus dem Bundle verschoben wurden, lauten die korrekten Verwendungsanweisungen jetzt: use JMS \ Serializer \ Annotation \ ExclusionPolicy; Verwenden Sie JMS \ Serializer \ Annotation \ Exclude.
Pier-Luc Gendreau
3
In der Dokumentation zu Doctrine heißt es, Objekte nicht zu serialisieren oder mit großer Sorgfalt zu serialisieren.
Bluebaron
147

Mit php5.4 können Sie jetzt:

use JsonSerializable;

/**
* @Entity(repositoryClass="App\Entity\User")
* @Table(name="user")
*/
class MyUserEntity implements JsonSerializable
{
    /** @Column(length=50) */
    private $name;

    /** @Column(length=50) */
    private $login;

    public function jsonSerialize()
    {
        return array(
            'name' => $this->name,
            'login'=> $this->login,
        );
    }
}

Und dann anrufen

json_encode(MyUserEntity);
SparSio
quelle
1
Ich mag diese Lösung sehr!
Michael
3
Dies ist eine großartige Lösung, wenn Sie
versuchen
5
Was ist mit verknüpften Entitäten?
John the Ripper
7
Dies scheint nicht mit Entitätssammlungen (dh OneToManyBeziehungen) zu funktionieren
Pierre de LESPINAY
1
Dies verstößt gegen das Prinzip der Einzelverantwortung und ist nicht gut, wenn Ihre Entitäten automatisch von Doctrine generiert werden
Jim Smith
39

Sie können automatisch in Json, Ihre komplexe Entität, codieren mit:

use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;

$serializer = new Serializer(array(new GetSetMethodNormalizer()), array('json' => new 
JsonEncoder()));
$json = $serializer->serialize($entity, 'json');
webda2l
quelle
3
Danke, aber ich habe eine Spielerentität, die einen Link zur Sammlung von Spielentitäten hat, und jede Spielentität hat einen Link zu Spielern, die darin gespielt haben. Etwas wie das. Und glauben Sie, dass GetSetMethodNormalizer korrekt funktioniert (es verwendet einen rekursiven Algorithmus)?
Dmytro Krasun
2
Ja, es ist rekursiv und das war mein Problem in meinem Fall. Daher können Sie für bestimmte Entitäten den CustomNormalizer und sein NormalizableInterface verwenden, wie Sie anscheinend wissen.
Webda2l
2
Als ich dies versuchte, bekam ich "Schwerwiegender Fehler: Zulässige Speichergröße von 134217728 Bytes erschöpft (versucht, 64 Bytes zuzuweisen) in /home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php on Linie 44 ". Ich wundere mich warum?
Jason Swett
1
Als ich es versuchte, kam ich unter die Ausnahme. Schwerwiegender Fehler: Maximale Funktionsverschachtelungsstufe von '100' erreicht, Abbruch! in C: \ wamp \ www \ myapp \ application \ library \ doctrine \ Symfony \ Component \ Serializer \ Normalizer \ GetSetMethodNormalizer.php in Zeile 223
user2350626
1
@ user2350626, siehe stackoverflow.com/questions/4293775/…
webda2l
11

So vervollständigen Sie die Antwort: Symfony2 wird mit einem Wrapper um json_encode geliefert: Symfony / Component / HttpFoundation / JsonResponse

Typische Verwendung in Ihren Controllern:

...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}

Hoffe das hilft

J.

Jerome
quelle
10

Ich fand die Lösung für das Problem der Serialisierung von Entitäten wie folgt:

#config/config.yml

services:
    serializer.method:
        class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
    serializer.encoder.json:
        class: Symfony\Component\Serializer\Encoder\JsonEncoder
    serializer:
        class: Symfony\Component\Serializer\Serializer
        arguments:
            - [@serializer.method]
            - {json: @serializer.encoder.json }

in meinem Controller:

$serializer = $this->get('serializer');

$entity = $this->get('doctrine')
               ->getRepository('myBundle:Entity')
               ->findOneBy($params);


$collection = $this->get('doctrine')
               ->getRepository('myBundle:Entity')
               ->findBy($params);

$toEncode = array(
    'response' => array(
        'entity' => $serializer->normalize($entity),
        'entities' => $serializer->normalize($collection)
    ),
);

return new Response(json_encode($toEncode));

anderes Beispiel:

$serializer = $this->get('serializer');

$collection = $this->get('doctrine')
               ->getRepository('myBundle:Entity')
               ->findBy($params);

$json = $serializer->serialize($collection, 'json');

return new Response($json);

Sie können es sogar so konfigurieren, dass Arrays in http://api.symfony.com/2.0 deserialisiert werden

rkmax
quelle
3
Es gibt einen Kochbucheintrag über die Verwendung der Serializer-Komponente in Symfony 2.3+, da Sie jetzt den integrierten aktivieren können: symfony.com/doc/current/cookbook/serializer.html
althaus
6

Ich musste nur das gleiche Problem lösen: JSON-Codierung einer Entität ("Benutzer") mit einer bidirektionalen Eins-zu-Viele-Zuordnung zu einer anderen Entität ("Standort").

Ich habe verschiedene Dinge ausprobiert und denke, jetzt habe ich die beste akzeptable Lösung gefunden. Die Idee war, den gleichen Code wie von David zu verwenden, aber irgendwie die unendliche Rekursion abzufangen, indem man dem Normalisierer sagt, er solle irgendwann aufhören.

Ich wollte keinen benutzerdefinierten Normalisierer implementieren, da dieser GetSetMethodNormalizer meiner Meinung nach ein guter Ansatz ist (basierend auf Reflexion usw.). Daher habe ich mich für eine Unterklasse entschieden, was auf den ersten Blick nicht trivial ist, da die Methode, mit der angegeben wird, ob eine Eigenschaft (isGetMethod) eingeschlossen werden soll, privat ist.

Aber man könnte die Normalisierungsmethode überschreiben, also habe ich an dieser Stelle abgefangen, indem ich einfach die Eigenschaft deaktiviert habe, die auf "Location" verweist - so wird die unendliche Schleife unterbrochen.

Im Code sieht es so aus:

class GetSetMethodNormalizer extends \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer {

    public function normalize($object, $format = null)
    {
        // if the object is a User, unset location for normalization, without touching the original object
        if($object instanceof \Leonex\MoveBundle\Entity\User) {
            $object = clone $object;
            $object->setLocations(new \Doctrine\Common\Collections\ArrayCollection());
        }

        return parent::normalize($object, $format);
    }

} 
Oxy
quelle
1
Ich frage mich, wie einfach es wäre, dies zu verallgemeinern, so dass 1. niemals die Entitätsklassen berührt werden müssen, 2. nicht nur die "Standorte" leer sind, sondern jedes Feld vom Typ "Sammlungen", das möglicherweise anderen Entitäten zugeordnet ist. Dh keine internen / Vorkenntnisse von Ent erforderlich, um es rekursionsfrei zu serialisieren.
Marcos
6

Ich hatte das gleiche Problem und entschied mich, meinen eigenen Encoder zu erstellen, der selbst mit Rekursion fertig wird.

Ich habe Klassen erstellt, die implementiert werden Symfony\Component\Serializer\Normalizer\NormalizerInterface, und einen Dienst, der alle enthält NormalizerInterface.

#This is the NormalizerService

class NormalizerService 
{

   //normalizer are stored in private properties
   private $entityOneNormalizer;
   private $entityTwoNormalizer;

   public function getEntityOneNormalizer()
   {
    //Normalizer are created only if needed
    if ($this->entityOneNormalizer == null)
        $this->entityOneNormalizer = new EntityOneNormalizer($this); //every normalizer keep a reference to this service

    return $this->entityOneNormalizer;
   }

   //create a function for each normalizer



  //the serializer service will also serialize the entities 
  //(i found it easier, but you don't really need it)
   public function serialize($objects, $format)
   {
     $serializer = new Serializer(
            array(
                $this->getEntityOneNormalizer(),
                $this->getEntityTwoNormalizer()
            ),
            array($format => $encoder) );

     return $serializer->serialize($response, $format);
}

Ein Beispiel für einen Normalisierer:

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

class PlaceNormalizer implements NormalizerInterface {

private $normalizerService;

public function __construct($normalizerService)
{
    $this->service = normalizerService;

}

public function normalize($object, $format = null) {
    $entityTwo = $object->getEntityTwo();
    $entityTwoNormalizer = $this->service->getEntityTwoNormalizer();

    return array(
        'param' => object->getParam(),
        //repeat for every parameter
        //!!!! this is where the entityOneNormalizer dealt with recursivity
        'entityTwo' => $entityTwoNormalizer->normalize($entityTwo, $format.'_without_any_entity_one') //the 'format' parameter is adapted for ignoring entity one - this may be done with different ways (a specific method, etc.)
    );
}

}

In einer Steuerung:

$normalizerService = $this->get('normalizer.service'); //you will have to configure services.yml
$json = $normalizerService->serialize($myobject, 'json');
return new Response($json);

Der vollständige Code ist hier: https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer

Julien Fastré
quelle
6

in Symfony 2.3

/app/config/config.yml

framework:
    # сервис конвертирования объектов в массивы, json, xml и обратно
    serializer:
        enabled: true

services:
    object_normalizer:
        class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
        tags:
        # помечаем к чему относится этот сервис, это оч. важно, т.к. иначе работать не будет
          - { name: serializer.normalizer }

und Beispiel für Ihren Controller:

/**
 * Поиск сущности по ИД объекта и ИД языка
 * @Route("/search/", name="orgunitSearch")
 */
public function orgunitSearchAction()
{
    $array = $this->get('request')->query->all();

    $entity = $this->getDoctrine()
        ->getRepository('IntranetOrgunitBundle:Orgunit')
        ->findOneBy($array);

    $serializer = $this->get('serializer');
    //$json = $serializer->serialize($entity, 'json');
    $array = $serializer->normalize($entity);

    return new JsonResponse( $array );
}

Die Probleme mit dem Feldtyp \ DateTime bleiben jedoch bestehen.

Lebnik
quelle
6

Dies ist eher ein Update (für Symfony v: 2.7+ und JmsSerializer v: 0.13. * @ Dev) , um zu vermeiden, dass Jms versucht, den gesamten Objektgraphen zu laden und zu serialisieren (oder im Falle einer zyklischen Beziehung ..)

Modell:

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\ExclusionPolicy;  
use JMS\Serializer\Annotation\Exclude;  
use JMS\Serializer\Annotation\MaxDepth; /* <=== Required */
/**
 * User
 *
 * @ORM\Table(name="user_table")
///////////////// OTHER Doctrine proprieties //////////////
 */
 public class User
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected   $id;

    /**
     * @ORM\ManyToOne(targetEntity="FooBundle\Entity\Game")
     * @ORM\JoinColumn(nullable=false)
     * @MaxDepth(1)
     */
    protected $game;
   /*
      Other proprieties ....and Getters ans setters
      ......................
      ......................
   */

Innerhalb einer Aktion:

use JMS\Serializer\SerializationContext;
  /* Necessary include to enbale max depth */

  $users = $this
              ->getDoctrine()
              ->getManager()
              ->getRepository("FooBundle:User")
              ->findAll();

  $serializer = $this->container->get('jms_serializer');
  $jsonContent = $serializer
                   ->serialize(
                        $users, 
                        'json', 
                        SerializationContext::create()
                                 ->enableMaxDepthChecks()
                  );

  return new Response($jsonContent);
timmz
quelle
5

Wenn Sie Symfony 2.7 oder höher verwenden und kein zusätzliches Bundle für die Serialisierung hinzufügen möchten, können Sie auf diese Weise die Doktrinentitäten in json - seialisieren.

  1. In meinem (gemeinsamen, übergeordneten) Controller habe ich eine Funktion, die den Serializer vorbereitet

    use Symfony\Component\Serializer\Encoder\JsonEncoder;
    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
    use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
    use Symfony\Component\Serializer\Serializer;
    
    // -----------------------------
    
    /**
     * @return Serializer
     */
    protected function _getSerializer()
    {  
        $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
        $normalizer           = new ObjectNormalizer($classMetadataFactory);
    
        return new Serializer([$normalizer], [new JsonEncoder()]);
    }
  2. Verwenden Sie es dann, um Entitäten in JSON zu serialisieren

    $this->_getSerializer()->normalize($anEntity, 'json');
    $this->_getSerializer()->normalize($arrayOfEntities, 'json');

Getan!

Möglicherweise müssen Sie jedoch eine Feinabstimmung vornehmen. Zum Beispiel -

Anis
quelle
4

Wenn Sie viele REST-API-Endpunkte in Symfony erstellen müssen, verwenden Sie am besten den folgenden Stapel von Bundles:

  1. JMSSerializerBundle für die Serialisierung von Doctrine-Entitäten
  2. FOSRestBundle Bundle für den Listener der Antwortansicht. Außerdem kann eine Definition von Routen basierend auf dem Controller- / Aktionsnamen generiert werden.
  3. NelmioApiDocBundle zum automatischen Generieren von Online-Dokumentation und Sandbox (mit der Endpunkte ohne externes Tool getestet werden können).

Wenn Sie alles richtig konfigurieren, sieht Ihr Entitätscode folgendermaßen aus:

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;

/**
 * @ORM\Table(name="company")
 */
class Company
{

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     *
     * @JMS\Expose()
     * @JMS\SerializedName("name")
     * @JMS\Groups({"company_overview"})
     */
    private $name;

    /**
     * @var Campaign[]
     *
     * @ORM\OneToMany(targetEntity="Campaign", mappedBy="company")
     * 
     * @JMS\Expose()
     * @JMS\SerializedName("campaigns")
     * @JMS\Groups({"campaign_overview"})
     */
    private $campaigns;
}

Dann Code in der Steuerung:

use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\View;

class CompanyController extends Controller
{

    /**
     * Retrieve all companies
     *
     * @View(serializerGroups={"company_overview"})
     * @ApiDoc()
     *
     * @return Company[]
     */
    public function cgetAction()
    {
        return $this->getDoctrine()->getRepository(Company::class)->findAll();
    }
}

Die Vorteile einer solchen Einrichtung sind:

  • @ JMS \ Expose () -Anmerkungen in der Entität können einfachen Feldern und beliebigen Arten von Beziehungen hinzugefügt werden. Es besteht auch die Möglichkeit, das Ergebnis einer Methodenausführung verfügbar zu machen (verwenden Sie dazu die Annotation @JMS \ VirtualProperty ()).
  • Mit Serialisierungsgruppen können wir exponierte Felder in verschiedenen Situationen steuern.
  • Controller sind sehr einfach. Die Aktionsmethode kann eine Entität oder ein Array von Entitäten direkt zurückgeben und wird automatisch serialisiert.
  • Mit @ApiDoc () können Sie den Endpunkt direkt vom Browser aus testen, ohne REST-Client oder JavaScript-Code
Maksym Moskvychev
quelle