JMS serializer ignores doctrine entity id - php

I tried map data from a given array to an object with the jms serializer (in a unit test) to test a doctrine entity:
Given is a simple entity class:
/**
* CashPosition
*/
class CashPosition
{
/**
* #var integer
*/
protected $cashPositionId;
/**
* #var \DateTime
*/
protected $date;
/**
* #var float
*/
protected $value;
/**
* Get cashPositionId
*
* #return integer
*/
public function getCashPositionId()
{
return $this->cashPositionId;
}
/**
* Set date
*
* #param \DateTime $date
*
* #return $this
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set value
*
* #param string $value
*
* #return $this
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Get value
*
* #return float
*/
public function getValue()
{
return $this->value;
}
}
I defined the serialization under Resources\config\serializer\Entity.CashPosition.yml
MyBundle\Entity\CashPosition:
exclusion_policy: ALL
access_type: public_method
properties:
cashPositionId:
exclude: false
expose: true
type: integer
access_type: property
date:
exclude: false
expose: true
type: DateTime<'Y-m-d'>
value:
exclude: false
expose: true
type: float
And tried to cover this with a serialization test:
public function testSerialization()
{
$data = [
'cashPositionId' => 1,
'date' => date('Y-m-d'),
'value' => 1.0,
];
/* #var $serializer Serializer */
$serializer = $this->container->get('serializer');
$cashPosition = $serializer->fromArray($data, CashPosition::class);
$this->assertInstanceOf(CashPosition::class, $cashPosition);
$this->assertEquals($data, $serializer->toArray($cashPosition));
}
But the test fails since the fromArray method does not set the cashPositionId. I tried some different configurations with the access type but had no luck. I'm not sure what's the problem here.
I'm using the following version of jms serializer:
jms/metadata 1.6.0 Class/method/property metadata management in PHP
jms/parser-lib 1.0.0 A library for easily creating recursive-descent parsers.
jms/serializer 1.6.2 Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.
jms/serializer-bundle 1.4.0 Allows you to easily serialize, and deserialize data of any complexity

Hello i think you miss the serialized_name property for cashPositionId, by default jms will translate properties from camel case to snake case.
JMS Doc

Related

API-Platform: Filtering Custom Data Provider

I'm currently experiencing issues when trying to filter my results when using an external API source (Stripe) in API-Platform.
What I need to be able to do, is return a list of subscriptions for a specified customer. So going to http://localhost/api/subscriptions?customer=123foo would return all records matched to this customer.
Now, the code below is throwing an error because of the ORM\Filter and would be functional without it, as the actual filtering is performed on Stripes API, not by me, BUT, I really want the Swagger-API GUI to have the filter box.
In short, how do I get the Annotations in my Entity to display, searchable fields within the Swagger UI when using an external data source.
What I have is an Entity as below (simplified for example purposes):
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Annotation\ApiFilter;
/**
* Subscriptions allow you to charge a customer on a recurring basis. A subscription ties a customer to a particular plan you've created.
* #ApiResource()
* #ApiFilter(SearchFilter::class, properties={"customer": "exact"})
* #package App\Entity
*/
class Subscription
{
/**
* Unique identifier for the object.
* #ApiProperty(identifier=true)
* #var string | null
*/
protected $id;
/**
* ID of the customer who owns the subscription.
* #var string | null
*/
protected $customer;
// Plus a bunch more properties and their Getters & Setters
}
And the SubscriptionCollectionDataProvider:
<?php
namespace App\DataProvider;
use App\Entity\Subscription;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Controller\BaseController;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Class SubscriptionCollectionDataProvider
* #package App\DataProvider
* #author dhayward
*/
final class SubscriptionCollectionDataProvider extends BaseController implements CollectionDataProviderInterface, RestrictedDataProviderInterface
{
protected $requestStack;
/**
* SubscriptionCollectionDataProvider constructor.
* #param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->request = $requestStack->getCurrentRequest();
}
/**
* #param string $resourceClass
* #param string|null $operationName
* #param array $context
* #return bool
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Subscription::class === $resourceClass;
}
/**
* #param string $resourceClass
* #param string|null $operationName
* #return \Generator
* #throws \Stripe\Error\Api
*/
public function getCollection(string $resourceClass, string $operationName = null): \Generator
{
$customer = $this->request->get("customer");
$data = \Stripe\Subscription::all(["customer" => $customer]);
foreach($data['data'] as $subscriptionObject){
$this->serializer()->deserialize(json_encode($subscriptionObject), Subscription::class, 'json', array('object_to_populate' => $subscription = new Subscription()));
yield $subscription;
}
}
}
Error result, which is presumably because I'm using an ORM/Filter without any ORM setup:
Call to a member function getClassMetadata() on null
Any pointers would be greatly appreciated.
So I finally managed to work it out. It was as simple as creating my own version of the SearchFilter, implementing ApiPlatform\Core\Api\FilterInterface.
<?php
namespace App\Filter;
use ApiPlatform\Core\Api\FilterInterface;
/**
* Class SearchFilter
* #package App\Filter
*/
class SearchFilter implements FilterInterface
{
/**
* #var string Exact matching
*/
const STRATEGY_EXACT = 'exact';
/**
* #var string The value must be contained in the field
*/
const STRATEGY_PARTIAL = 'partial';
/**
* #var string Finds fields that are starting with the value
*/
const STRATEGY_START = 'start';
/**
* #var string Finds fields that are ending with the value
*/
const STRATEGY_END = 'end';
/**
* #var string Finds fields that are starting with the word
*/
const STRATEGY_WORD_START = 'word_start';
protected $properties;
/**
* SearchFilter constructor.
* #param array|null $properties
*/
public function __construct(array $properties = null)
{
$this->properties = $properties;
}
/**
* {#inheritdoc}
*/
public function getDescription(string $resourceClass): array
{
$description = [];
$properties = $this->properties;
foreach ($properties as $property => $strategy) {
$filterParameterNames = [
$property,
$property.'[]',
];
foreach ($filterParameterNames as $filterParameterName) {
$description[$filterParameterName] = [
'property' => $property,
'type' => 'string',
'required' => false,
'strategy' => self::STRATEGY_EXACT,
'is_collection' => '[]' === substr($filterParameterName, -2),
];
}
}
return $description;
}
}

Symfony jms serializer enable when needed

I have jms serializer and in my class entity have annotation. I have case when I need apply jms serializer annotation only in some case. How to do this ? First I think need move annotation from entity class to uml maybe and create some handler where I can enable this annotation for this entity, in the rest of the time jms are not applicable to entity
my entity
/**
* #Annotation\ExclusionPolicy("all")
*/
class Application implements ContainsRecordedMessages
{
/**
* #var int
*
* #Annotation\Groups({
* "get_application"
* })
* #Annotation\Expose()
*/
private $id;
/**
* #var int
*
* #Annotation\Groups({
* "post_application"
* })
* #Annotation\SerializedName("company_id")
* #Annotation\Expose()
*/
private $companyId;
/**
* #var string
*
* #Annotation\Groups({
* "post_application"
* })
* #Annotation\SerializedName("channel_sale")
* #Annotation\Expose()
*/
private $channelSale;
and I have class manager for this entity where I want enable jms and the disable jms serializer annotation
class ApplicationManager
{
public function someFunction()
{
$this->enableJmsForEntity(Application::class);
//some logic
//
$this->disableJmsForEntity(Application::class);
}
}
my goal - that jms serializer don't work in serializer proccess for response. Only in my service, wehre I create entity with deserializer function, jms serializer annotation for entity enable. Because in this old project all respons look like this
return $this->json($application, Response::HTTP_CREATED);
vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php:114
protected function json($data, $status = 200, $headers = array(), $context = array())
{
if ($this->container->has('serializer')) {
$json = $this->container->get('serializer')->serialize($data, 'json', array_merge(array(
'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
), $context));
return new JsonResponse($json, $status, $headers, true);
}
return new JsonResponse($data, $status, $headers);
}
and after adding jms annotation we have problem, response in changing ..

Sylius: How to extend Taxon model?

I'm trying to extend Sylius\Component\Core\Model\Taxon by adding new data fields. The same procedure did work on another model outside of Sylius Core. When running doctrine:migrations:diff, the error message is "The table with name 'sylius_dev.sylius_taxon' already exists."
The response for php bin/console debug:container --parameter=sylius.model.taxon.class does not change at all.
This is my new class in /src/AppBundle/Entity/FooTaxon.php:
<?php
namespace AppBundle\Entity;
use Sylius\Component\Core\Model\Taxon as BaseTaxon;
class FooTaxon extends BaseTaxon
{
/**
* #var string
*/
private $field_one;
/**
* #return string
*/
public function getFieldOne(): string
{
return $this->field_one;
}
/**
* #param string $new_value
*/
public function setFieldOne(string $new_value): void
{
$this->field_one = $new_value;
}
/**
* #var int
*/
private $field_two;
/**
* #return int
*/
public function getFieldTwo(): int
{
return $this->field_two;
}
/**
* #param int $new_value
*/
public function setFieldTwo(int $new_value): void
{
$this->field_two = $new_value;
}
}
This is my /src/AppBundle/Resources/config/doctrine/FooTaxon.orm.yml:
AppBundle\Entity\FooTaxon:
type: entity
table: sylius_taxon
fields:
field_one:
type: string
nullable: false
field_two:
type: integer
nullable: false
And here is the new entry in /app/config/config.yml:
sylius_core:
resources:
product_taxon:
classes:
model: AppBundle\Entity\FooTaxon
Any help would be appreciated since I'm new to both Symfony and Sylius.
You should use this instead of sylius_core node:
sylius_taxonomy:
resources:
taxon:
classes:
model: AppBundle\Entity\FooTaxon
And better use upperCase in entity property names instead of snake_case.

Symfony Doctrine2 manyToMany relationship not removed - Specific to SQLite

I have several classes using a Taggable trait to set up a tag system common to several doctrine entities (Project, Note, ...).
The relationship between these entities and these tags is a ManyToMany relationship that I can not make multi-directional.
My problem: When I delete a Project entity, it is removed from the project table, but the relationships in the project_tag table between this project and the tags are not deleted. Then, if I create a new Project entity, an exception is thrown.
An exception exists while executing 'INSERT INTO project_tag (project_id, tag_id) VALUES (?,?)' With params [2, 4]:
SQLSTATE [23000]: Integrity constraint violation: 19 UNIQUE constraint failed: project_tag.project_id, project_tag.tag_id
Entities :
Tag
/**
* Tag
*
* #ORM\Table(name="tag")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository")
*/
class Tag
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #ORM\Column(name="last_use_at", type="datetime", nullable=false)
* #var \DateTime
*/
private $lastUseAt;
public function __construct()
{
$this->lastUseAt = new \DateTime();
}
public function __toString()
{
return $this->name;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #return \DateTime
*/
public function getLastUseAt(): \DateTime
{
return $this->lastUseAt;
}
/**
* #param \DateTime $lastUseAt
*/
public function setLastUseAt(\DateTime $lastUseAt)
{
$this->lastUseAt = $lastUseAt;
}
}
Taggable
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", cascade={"persist"})
*/
protected $tags;
/**
* Add tag
*
* #param Tag $tag
*
* #return $this
*/
public function addTag(Tag $tag)
{
$tag->setLastUseAt(new \DateTime());
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param Tag $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
Project
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProjectRepository")
*/
class Project
{
use Taggable;
}
Note
class Note
{
use Taggable;
}
Is this the only solution or is my annotation incomplete / incorrect?
I tried with JoinColumns, JoinTable and onDelete = "cascade" but nothing works.
In the meantime, I dodged the problem with this instruction placed before the suppresion.
$project->getTags()->clear();
Full code of the action in the controller :
/**
* #Route("/project/{id}/delete", name="project_delete")
*/
public function deleteAction($id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->find($id);
if(!$project) {
return $this->redirectToRoute('index');
}
$project->getTags()->clear();
$em->remove($project);
$em->flush();
return $this->redirectToRoute('index');
}
I think I found a better solution: you can set the PRAGMA within Doctrine configuration. Like:
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_sqlite'
#server_version: '5.7'
#charset: utf8mb4
#default_table_options:
#charset: utf8mb4
#collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
options:
'PRAGMA foreign_keys': 'ON'
I just tried it on my Symfony 4 application, re-created the database and tested using DB Browser for SQLite and it works as I expected.
Hope this helps
I managed to fix the problem. Here's my solution working for SQLite conections.
Create an eventListener listening on the kernel.request event :
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
/**
* #var Registry
*/
private $doctrine;
public function __construct(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->doctrine->getConnection()->exec('PRAGMA foreign_keys = ON');
}
}
Service declaration
app.event_listener.request_listener:
class: AppBundle\EventListener\RequestListener
arguments:
- '#doctrine'
tags:
- { name: kernel.event_listener, event: kernel.request }
I think the problem is that you have your trait Taggable set as the owning side of the ManyToMany relationship but your are deleting the inverse side and expecting something to happen as a result. Doctrine will only check the owning side of the relationship in order to persist any changes. See here for docs on this.
You can solve by making the Taggable the inverse side of each of your relationships, or by manually telling doctrine to delete the owning side.
The first solution will probably not work for you since you won't (easily) specify multiple inverse sides. (Are you sure a trait is the right way to go for this??)
The second solution is easy. In your entities like Project for your deleteTag($tag) function, call a delete function on the owning side (e.g., deleteProject($project). You will have to create if one does not exist.
class Project
{
use Taggable;
public function deleteTag($tag)
{
$this->tags->removeElement($tag);
// persist on the owning side
$tag->deleteProject($this);
}
}
EDIT:
After seeing full code, it looks like you are deleting correctly. Now you need to tell doctrine to carry that through. See this post for full details, but basically you can change your trait to this:
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(
* targetEntity="AppBundle\Entity\Tag",
* cascade={"persist"},
* onDelete="CASCADE"
* )
*/
protected $tags;
// ...
}

How to deal with array vars in Twig on EasyAdminBundle?

I have this entity definition:
class Operator
{
...
/**
* #var array
* #ORM\Column(type="text", nullable=true)
*/
private $prefix;
/**
* #param $prefix
* #return $this
*/
public function addPrefix($prefix)
{
if (!in_array($prefix, $this->prefix, true)) {
$this->prefix[] = $prefix;
}
return $this;
}
/**
* #param array $prefixes
* #return $this
*/
public function setPrefix(array $prefixes)
{
$this->prefix = array();
foreach($prefixes as $prefix) {
$this->addPrefix($prefix);
}
return $this;
}
/**
* #return array The prefixes
*/
public function getPrefix()
{
$prefix = is_array($this->prefix) ? $this->prefix : ['04XX'];
return array_unique($prefix);
}
...
}
I am using EasyAdminBundle for manage this entity in the backend so here is the config for it:
easy_admin:
entities:
Operator:
class: PlatformAdminBundle\Entity\Operator
...
form:
fields:
...
- { property: 'prefix', label: 'prefix' }
Any time I try to create a new Operator I run into this error:
ContextErrorException: Notice: Array to string conversion
I can't find where is the problem since I am using the same on a User entity that inherit from BaseUser (from FOSUser) and it works. This is how it looks like for User entity and should be the same for Operator:
What I am missing? Can any give me some advice? I am stuck!
Orm prefix column should be array type.
/**
* #var array
* #ORM\Column(type="array", nullable=true)
*/
private $prefix;
And run
php app/console doctrine:schema:update --force

Categories