Tworzę grę i używam Symfony 2.0. Mam wiele żądań AJAX do zaplecza. Więcej odpowiedzi konwertuje encję na JSON. Na przykład:
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');
}
}
Wszystkie moje kontrolery robią to samo: pobierają encję i kodują niektóre jej pola do JSON. Wiem, że mogę używać normalizatorów i zakodować wszystkie uprawnienia. Ale co się stanie, jeśli podmiot ma cykliczne połączenia z innym podmiotem? Czy wykres jednostek jest bardzo duży? Masz jakieś sugestie?
Myślę o schemacie kodowania dla jednostek ... lub używaniu go, NormalizableInterface
aby uniknąć cykli ...,
źródło
Dzięki php5.4 możesz teraz:
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, ); } }
A potem zadzwoń
źródło
OneToMany
relacji)Możesz automatycznie kodować do Json, złożonej jednostki za pomocą:
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');
źródło
Aby uzupełnić odpowiedź: Symfony2 zawiera opakowanie wokół json_encode: Symfony / Component / HttpFoundation / JsonResponse
Typowe zastosowanie w kontrolerach:
... use Symfony\Component\HttpFoundation\JsonResponse; ... public function acmeAction() { ... return new JsonResponse($array); }
Mam nadzieję że to pomoże
jot
źródło
Znalazłem rozwiązanie problemu serializacji jednostek:
#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 }
w moim kontrolerze:
$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));
inny przykład:
$serializer = $this->get('serializer'); $collection = $this->get('doctrine') ->getRepository('myBundle:Entity') ->findBy($params); $json = $serializer->serialize($collection, 'json'); return new Response($json);
możesz nawet skonfigurować go do deserializacji tablic w http://api.symfony.com/2.0
źródło
Po prostu musiałem rozwiązać ten sam problem: kodowanie json jednostki („Użytkownik”) mającej jedno-do-wielu dwukierunkowe skojarzenie z inną jednostką („lokalizacja”).
Próbowałem kilku rzeczy i myślę, że teraz znalazłem najlepsze akceptowalne rozwiązanie. Pomysł polegał na użyciu tego samego kodu, który napisał David, ale w jakiś sposób przechwycił nieskończoną rekurencję, nakazując Normalizatorowi zatrzymanie się w pewnym momencie.
Nie chciałem implementować niestandardowego normalizatora, ponieważ ten GetSetMethodNormalizer jest moim zdaniem fajnym podejściem (opartym na refleksji itp.). Postanowiłem więc utworzyć podklasę, co na pierwszy rzut oka nie jest trywialne, ponieważ metoda określania, czy należy dołączyć właściwość (isGetMethod), jest prywatna.
Ale można by przesłonić metodę normalize, więc przechwyciłem w tym momencie, po prostu wyłączając właściwość, która odwołuje się do „Location” - tak więc nieskończona pętla zostaje przerwana.
W kodzie wygląda to tak:
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); } }
źródło
Miałem ten sam problem i zdecydowałem się stworzyć własny koder, który sam sobie poradzi z rekurencją.
Stworzyłem klasy, które implementują
Symfony\Component\Serializer\Normalizer\NormalizerInterface
i usługę, która przechowuje wszystkie plikiNormalizerInterface
.#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); }
Przykład normalizatora:
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.) ); } }
W kontrolerze:
$normalizerService = $this->get('normalizer.service'); //you will have to configure services.yml $json = $normalizerService->serialize($myobject, 'json'); return new Response($json);
Pełny kod jest tutaj: https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer
źródło
w 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 }
i przykład dla twojego kontrolera:
/** * Поиск сущности по ИД объекта и ИД языка * @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 ); }
ale problemy z typem pola \ DateTime pozostaną.
źródło
Jest to bardziej aktualizacja (dla Symfony v: 2.7+ i JmsSerializer v: 0.13. * @ Dev) , aby uniknąć prób załadowania i serializacji całego grafu obiektu (lub w przypadku relacji cyklicznej).
Model:
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 ...................... ...................... */
Wewnątrz akcji:
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);
źródło
Jeśli używasz Symfony 2.7 lub nowszego i nie chcesz dołączać żadnego dodatkowego pakietu do serializacji, być może możesz skorzystać z tego sposobu, aby przenieść encje doktryny do json -
W moim (wspólnym, nadrzędnym) kontrolerze mam funkcję, która przygotowuje serializator
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()]); }
Następnie użyj go do serializacji jednostek do formatu JSON
$this->_getSerializer()->normalize($anEntity, 'json'); $this->_getSerializer()->normalize($arrayOfEntities, 'json');
Gotowe!
Ale możesz potrzebować dopracowania. Na przykład -
źródło
Kiedy potrzebujesz stworzyć wiele punktów końcowych REST API w Symfony, najlepszym sposobem jest użycie następującego stosu pakietów:
Po prawidłowym skonfigurowaniu wszystkiego kod encji będzie wyglądał następująco:
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; }
Następnie kod w kontrolerze:
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(); } }
Korzyści z takiej konfiguracji to:
źródło
Teraz możesz również użyć Doctrine ORM Transformations do konwersji encji na zagnieżdżone tablice skalarów iz powrotem
źródło