Doctrine ConversionException during message execution - symfony

During message execution we get always the following error:
Doctrine\DBAL\Types\ConversionException^ {
message: "Could not convert database value to 'array' as an error was triggered by the unserialization: 'unserialize(): Error at offset 245 of 5267 bytes'"
code: 0
file: "/app/vendor/doctrine/dbal/src/Types/ConversionException.php"
line: 117
trace: {
./vendor/doctrine/dbal/src/Types/ConversionException.php:117 { …}
./vendor/doctrine/dbal/src/Types/ArrayType.php:48 { …}
Doctrine\DBAL\Types\ArrayType->Doctrine\DBAL\Types\{closure}() {
namespace: "Doctrine\DBAL\Types"
short_class: "ArrayType"
}
./vendor/doctrine/dbal/src/Types/ArrayType.php:52 { …}
./vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php:140 { …}
./vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php:63 { …}
./vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php:270 { …}
./vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php:919 { …}
./vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php:227 { …}
./src/MessageHandler/StartMigrationSignalHandler.php:97 {
App\MessageHandler\StartMigrationSignalHandler->__invoke(StartMigrationSignal $signal)^
› $users = $this->em->getRepository(User::class)->findBy(
› ['enabled' => true], ['username' => 'ASC'], $perPage, $offset
› );
arguments: {
$criteria: [ …2]
$orderBy: [ …2]
$limit: [ …2]
$offset: [ …2]
}
}
./vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:96 { …}
./vendor/symfony/messenger/Middleware/SendMessageMiddleware.php:74 { …}
./vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php:34 { …}
./vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php:68 { …}
./vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php:48 { …}
./vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php:37 { …}
./vendor/symfony/messenger/Middleware/TraceableMiddleware.php:43 { …}
./vendor/symfony/messenger/MessageBus.php:77 { …}
./vendor/symfony/messenger/TraceableMessageBus.php:41 { …}
./vendor/symfony/messenger/RoutableMessageBus.php:54 { …}
./vendor/symfony/messenger/Worker.php:160 { …}
./vendor/symfony/messenger/Worker.php:108 { …}
./vendor/symfony/messenger/Command/ConsumeMessagesCommand.php:225 { …}
./vendor/symfony/console/Command/Command.php:298 { …}
./vendor/symfony/console/Application.php:1042 { …}
./vendor/symfony/framework-bundle/Console/Application.php:96 { …}
./vendor/symfony/console/Application.php:299 { …}
./vendor/symfony/framework-bundle/Console/Application.php:82 { …}
./vendor/symfony/console/Application.php:171 { …}
./bin/console:42 { …}
}
}
After some time of debugging, we could limit the failing entry to this query:
SELECT * FROM `security_user` WHERE `enabled` = '1' ORDER BY `username` LIMIT 1 OFFSET 7209
I looked at the entry, but it appears to be flawless. Also if I call manually the unserialize function on the serialized database fields I can decode them without problem. Funny enough I can view the entity just fine in our SonataAdmin interface.
The fields in question would be of type array, but as only one entry fails I guess it is not caused by some issue in the class:
class User extends BaseUser
{
// ...
/**
* #var array
* #ORM\Column(type="array")
*/
protected $userData = [];
/**
* #var array
* #ORM\Column(type="array")
*/
protected $ssoRoles = [];
/**
* #var string[]
* #ORM\Column(type="array")
*/
protected $roles;
}
Anybody knows what could be the cause or how to debug this?

Related

API Platform: How to normalize a collection of embedded entities in GraphQL?

I'm trying to make a collection of subresources selectable in GraphQL (with pagination). I'd like to be able to query:
query {
getA(id: '/api/A/1') {
aId
subresources {
totalCount
pageInfo {
endCursor
startCursor
hasNextPage
hasPreviousPage
}
edges {
node {
bId
}
}
}
}
}
and get the result:
{
aId: 1,
subresources: {
"totalCount": XX,
"pageInfo": {
"endCursor": "MQ==",
"startCursor": "MA==",
"hasNextPage": true,
"hasPreviousPage": false
},
edges: [
{
node: {
bId: 11
}
},
{
node: {
bId: 12
}
},
{
node: {
bId: 13
}
}
]
}
}
I'm not using Doctrine at all- I'm using custom data providers. The problem I'm encountering is that even when I return an A entity from DataProvider::getItem() that has an array of B subresources, I get an empty array for subresources in GraphQL. I get the correct data in REST though.
I'm following the instructions given in SymfonyCasts and I found a related API Platform issue, but I'm still having no luck.
I traced through API Platform core and I think it has to do with how the entity is normalized in GraphQL. Specifically, an empty array is returned in ItemNormalizer::normalizeCollectionOfRelations(). However, there's a comment saying "to-many are handled directly by the GraphQL resolver" but I'm not sure what that refers to.
Here's the entity code.
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
#[ApiResource(
graphql: ['item_query', 'collection_query', 'create', 'update', 'delete'],
collectionOperations: ['get', 'post'],
itemOperations: ['get', 'put', 'patch', 'delete'],
normalizationContext: ['groups' => ['read']],
denormalizationContext: ['groups' => ['write']],
)]
class A {
#[ApiProperty(identifier: true)]
#[Groups(['read', 'write'])]
public ?int $aId = null,
/** #var B[] */
#[ApiProperty(readableLink: true, writableLink: true)]
#[Groups(['read', 'write'])]
public $subresources = []
}
And:
#[ApiResource(
graphql: ['item_query', 'collection_query', 'create', 'update', 'delete'],
collectionOperations: ['get', 'post'],
itemOperations: ['get', 'put', 'patch', 'delete'],
normalizationContext: ['groups' => ['read']],
denormalizationContext: ['groups' => ['write']],
)]
class B {
#[ApiProperty(identifier: true)]
#[Groups(['read', 'write'])]
public ?int $bId = null,
}
My ADataProvider:
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): A {
$bs = $this->bDataProvider->getCollection(B::class, null, []);
return new A(123, $bs);
}
My BDataProvider:
/**
* #return ArrayPaginator<B>
*/
public function getCollection(string $resourceClass, string $operationName = null, array $context = []): ArrayPaginator {
return ArrayPaginator::fromList([new B(11), new B(12), new B(13)]);
}
ArrayPaginator implements IteratorAggregate and PaginatorInterface.
Specifically I see this error:
{
"errors": [
{
"debugMessage": "Collection returned by the collection data provider must implement ApiPlatform\\Core\\DataProvider\\PaginatorInterface or ApiPlatform\\Core\\DataProvider\\PartialPaginatorInterface.",
"message": "Internal server error",
"extensions": {
"category": "internal"
},
"locations": [
{
"line": 29,
"column": 5
}
],
"path": [
"a",
"b"
],
"trace": [
{
"file": "/homedir/core/src/GraphQl/Resolver/Stage/SerializeStage.php",
"line": 100,
"call": "ApiPlatform\\Core\\GraphQl\\Resolver\\Stage\\SerializeStage::serializeCursorBasedPaginatedCollection(array(0), array(5), array(6))"
},
TLDR: How does one use annotations (or YAML) to make attributes that are collections of subresources selectable in GraphQL?
Any help/ideas are appreciated, thanks for reading!
Found a solution: the ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface needs to be implemented by the BDataProvider.
It gets used in the ReadStage of api platform's graphql resolver. Surprisingly, it's found nowhere in the REST resolver, so this won't get called on a REST request.
The only method that needs to be implemented is getSubresource(). My basic first implementation looks like this:
public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) {
if ($context['collection']) {
return $this->getCollection($resourceClass, $operationName, $context);
}
$id = // get your id from $identifiers;
return $this->getItem($resourceClass, $id, $operationName, $context);
}
This isn't found in the docs unfortunately, but there are a few pulls (1, 2) open to add it.

How configure JMSSerializer in Symfony to serialize custom class to/from int?

I am working on a Symfony 3.4 based web app project which uses JMSSerializer to serialize different custom classes to JSON to send this data to mobile apps.
How can I serialize/deserialize a custom class to/from to int?
Assume we have the following classes:
<?php
// AppBundle/Entity/...
class NotificationInfo {
public $date; // DateTime
public $priority; // Int 1-10
public $repeates; // Boolean
public function toInt() {
// Create a simple Int value
// date = 04/27/2020
// priority = 5
// repeats = true
// ==> int value = 4272020 5 1 = 427202051
}
public function __construnct($intValue) {
// ==> Split int value into date, priority and repeats...
}
}
class ToDoItem {
public $title;
public $tags;
public $notificationInfo;
}
// AppBundle/Resources/config/serializer/Entiy.ToDoItem.yml
AppBundle\Entity\ToDoItem:
exclusion_policy: ALL
properties:
title:
type: string
expose: true
tags:
type: string
expose: true
notificationInfo:
type: integer
expose: true
So the class NotificationInfo also has function to create it from int and to serialize it to in. How to tell the serializer that it should serialize the value of $notificationInfo to int?
I could use the following instead:
notificationInfo:
type: AppBundle\Entity\NotificationInfo
expose: true
However in this case I need to configure the serialization of NotificationInfo where I can only specify which property should serialized to which value...
EDIT:
This is the JSON I would like to create:
{
"title": "Something ToDO",
"tags": "some,tags",
"notificationInfo": 427202051
}
This is what I am NOT looking for:
{
"title": "Something ToDO",
"tags": "some,tags",
"notificationInfo": {
"intValue": 427202051
}
}
After a lot more digging I found the following solution for my problem: I added a custom serialization Handler which tells JMSSerializer how to handle my custom class:
class NotificationInfoHandler implements SubscribingHandlerInterface {
public static function getSubscribingMethods() {
return [
[
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => 'NotificationInfo',
'method' => 'serializeNotificationInfoToJson',
],
[
'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
'format' => 'json',
'type' => 'NotificationInfo',
'method' => 'deserializeNotificationInfoToJson',
],
;
public function serializeNotificationInfoToJson(JsonSerializationVisitor $visitor, NotificationInfo $info, array $type, Context $context) {
return $info->toInt();
}
public function deserializeNotificationInfoToJson(JsonDeserializationVisitor $visitor, $infoAsInt, array $type, Context $context) {
return (is_int($infoAsInt) ? NotificationInfo::fromInt($infoAsInt) : NotificationInfo::emptyInfo());
}
}
Thanks to autowire the handler is automatically added and can be used in the serializer metadata:
notificationInfo:
type: NotificationInfo
expose: true
you can use VirtualProperty method to add any method of you class
into json response
use JMS\Serializer\Annotation as Serializer;
class NotificationInfo
{
/**
* #return int
* #Serializer\VirtualProperty()
* #Serializer\SerializedName("formatedLocation")
*/
public function toInt()
{
return 4272020;
}
}

Save entity with subresource into redis with Symfony & Api Platform

I have 2 entities, Event & User. So basically A user can have multiple event.
I have an EventItemDataProvider and in the getItem method I did something like that:
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Event
{
if ($event = $this->cache->get($id, Event::class)) {
return $event;
}
$event = $this->em->getRepository(Event::class)->find($id);
if($event) {
$this->cache->set($id, $event, 120);
}
return $event;
}
When the data is returned without the cache I have this result that is correct with the right username:
{
"#context": "/contexts/Event",
"#id": "/events/20",
"#type": "Event",
"title": "new global event 3",
"description": "A big global event 3",
"createdAt": "2019-07-05T09:20:48+00:00",
"owner": {
"#id": "/users/3",
"#type": "User",
"username": "test3"
}
}
But if I hit the same method a second time, it will retrieve the data from the cache (redis) but this time username will be empty like that:
{
"#context": "/contexts/Event",
"#id": "/events/20",
"#type": "Event",
"title": "new global event 3",
"description": "A big global event 3",
"createdAt": "2019-07-05T09:20:48+00:00",
"owner": {
"#id": "/users/3",
"#type": "User",
"username": ""
}
}
Why is my username empty when I retrieve it from redis?
Basically my cache class has these methods:
public function get(string $uniq, string $className)
{
$value = $this->cache->getItem('test_key')->get();
return $value;
}
public function set(string $uniq, object $entity, int $expires = 60)
{
$save = $this->cache->getItem('test_key');
$save->set($entity);
$save->expiresAfter($expires);
$saved = $this->cache->save($save);
return $saved;
}
The owner field in my Event entity:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="events")
* #ORM\JoinColumn(nullable=false)
* #Groups({"event:read", "event:write", "event:update"})
* #Assert\Valid()
*/
private $owner;
The username field in my User entity:
/**
* #ORM\Column(type="string", length=25, unique=true)
* #Groups({"user:read", "user:write", "user:update", "event:item:get", "event:update"})
* #Assert\NotBlank(message="Please provide a username")
*/
private $username;
My redis cache configuration:
cache:
app: cache.adapter.redis
default_redis_provider: "redis://redis"
pools:
custom.cache.entity:
adapter: cache.app
default_lifetime: 120
Why my username is filled when I retrieve it from Doctrine Repository but empty when I retrieve it from Redis?
With Xdebug I can see that if I retrieve the data from Doctrine I have something like that:
But if I retrieve the data from the cache I have something like that:
In this second case, initializer -> this seems infinite, so I think that the problem is probably here, but what should I do to solve this issue?

symfony entity createAt and updateAt type is datetime, when i wrote api,the result is bad

I am sorry for the title being so non-descriptive but the question I am asking is way to broad to fit in 10 words.
when i use symfony entity createAt and updateAt type is datetime, when i wrote api,the result is bad.I not want change createAt and updateAt to string type.thanks
entity defiend is like this:
/**
* #var \DateTime
*
* #ORM\Column(name="updateAt", type="datetime", options={"comment":"更新时间"})
*/
private $updateAt;
the method in repository :
public function getList($userId, $isMime)
{
$qb = $this->createQueryBuilder('c')
->select('c.title,c.nodeName as node_name, c.node, c.updateAt as datetime');
if($isMime == 0){
$qb->leftJoin(ApprovalInformation::class,'a','WITH','a.flowId = 11 and a.itemId = c.id');
$qb->where('a.approverId = :uid');
}else{
$qb->where('c.userId = :uid');
}
$qb->setParameter('uid', $userId);
return $qb->getQuery()->getResult();
}
controller
$list = $cardRepo->getList($this->user->getId(), $mime);
return $this->json([
'code' => 1,
'msg' => '获取成功',
'data' => $list
]);
{
"code": 1,
"msg": "获取成功",
"data": [
{
"title": "测试报告",
"node_name": "处长批示",
"node": 11,
"datetime": {
"date": "2019-05-07 19:04:00.000000",
"timezone_type": 3,
"timezone": "Asia/Shanghai"
}
},
{
"title": "测试报告2222",
"node_name": "处长审批",
"node": 2,
"datetime": {
"date": "2019-05-07 19:28:14.000000",
"timezone_type": 3,
"timezone": "Asia/Shanghai"
}
}
]
}
my except result like is:
{
"code": 1,
"msg": "获取成功",
"data": [
{
"title": "测试报告",
"node_name": "处长批示",
"node": 11,
"datetime": "2019-05-07 19:04:00",
},
{
"title": "测试报告2222",
"node_name": "处长审批",
"node": 2,
"datetime": "2019-05-07 19:28:14",
}
]
}
I want to know an efficient solution.thanks
You are using $this->json() in your controller which will serialize your entity using json_encode(). This is what is causing the datetime to be returned like this:
$ php -a
php > echo json_encode(new DateTime());
{"date":"2019-05-07 14:20:22.137677","timezone_type":3,"timezone":"UTC"}
There is multiple ways around this. You can create an array from your data before calling $this->json():
return $this->json(
array_map(
function ($entity) {
return [
'title' => $entity->getTitle(),
...
'updateAt' => $entity->getUpdateAt()->format('Y-m-d H:i:s'),
];
},
$list
)
);
Alternatively you could use the Symfony Serializer: https://symfony.com/doc/current/components/serializer.html#serializing-an-object
If you use the Serializer, make sure you also have the DateTimeNormalizer registered.
Just do it following code in your getUpadateAt function in entity:
return $this->updateAt->format('Y-m-d h:I:s);

Disable Soft Deleteable filter for hard delete record doesn't work

I'm trying to disable "Soft Deleteable" filter during functional testing and I do it as follow:
First option: tried to disable at tearDown() in my test:
protected function tearDown() {
parent::tearDown();
$entityUser = $this->em->getRepository("UserSecurityBundle:User")->find($this->user->getUser()->getId());
$filter = $this->em->getFilters()->disable('softdeleteable');
$this->em->remove($entityUser);
$this->em->flush();
$this->em->close();
}
Didn't work.
Second option: make a doctrine_test.yml and import in config_test.yml:
imports:
- { resource: config.yml }
- { resource: doctrine_test.yml }
- { resource: security_test.yml }
In this case I remove the doctrine.yml and include in config_prod.yml.
Didn't work.
This is how my doctrine_test.yml section look like:
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: false
Third option: disable the filter in setUp():
public function setUp() {
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()->get('doctrine')->getManager();
$fixture = new LoadFeeData();
$fixture->load($this->em);
$this->em->getFilters()->disable('softdeleteable');
$this->user = $this->createUser();
parent::setUp();
}
Didn't work.
Any advice? Solution?
So after some research, after doing more test I found the solution, see below:
protected function tearDown() {
parent::tearDown();
$entityAccount = $this->em->getRepository("UserSecurityBundle:Account")->find(array("id" => $this->user->getId(), "user" => $this->user->getUser()->getId()));
$entityUser = $entityAccount->getUser();
$entityCompanyHasWtax = $this->em->getRepository("ApprovalBundle:CompanyHasWtax")->findOneBy(array("company" => $this->company, "wtax" => $this->fee, "user" => $this->user->getUser()->getId()));
foreach ($this->em->getEventManager()->getListeners() as $eventName => $listeners) {
foreach ($listeners as $listener) {
if ($listener instanceof \Gedmo\SoftDeleteable\SoftDeleteableListener) {
$this->em->getEventManager()->removeEventListener($eventName, $listener);
}
}
}
$this->em->remove($entityCompanyHasWtax);
$this->em->remove($entityAccount);
$this->em->remove($entityUser);
$this->em->flush();
$this->em->close();
}
Aparently Doctrine has a bug since disabling the filter in this way:
$this->em->getFilters()->disable('softdeleteable');
Doesn't work, good look for others
Although this question is a bit old maybe it is useful to someone:
Creating your own event listener might be a better solution:
class SoftDeleteableListener extends BaseSoftDeleteableListener
{
/**
* #inheritdoc
*/
public function onFlush(EventArgs $args)
{
$ea = $this->getEventAdapter($args);
$om = $ea->getObjectManager();
//return from event listener if you disabled filter: $em->getFilters()->disable('softdeleteable');
if (!$om->getFilters()->isEnabled('softdeleteable')) {
return;
}
parent::onFlush($args);
}
}
And adding in your config:
gedmo.listener.softdeleteable:
class: AppBundle\EventListener\SoftDeleteableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
source: https://github.com/Atlantic18/DoctrineExtensions/issues/1175

Resources