Symfony 4 serialize entity with relationship unexpected value NULL - symfony

I have ManyToOne relationship between Posts and User:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User\User")
*/
private $author;
After I obtain response form my endpoint which looks like this:
array:2 [
"message" => "OK"
"data" => array:1 [
0 => Post {#6077
-id: 1
-title: "sda"
-teaser: "asdj"
-content: "asd"
-createdDateTime: DateTime #1517078058 {#6075
date: 2018-01-27 18:34:18.0 UTC (+00:00)
}
-deletedDateTime: null
-author: User {#6294
+__isInitialized__: false
-id: 1
-firstName: null
-lastName: null
-email: null
-activationCode: null
-isActive: null
-createdDateTime: null
-deletedDateTime: null
…2
}
-statusId: false
}
]
]
I'm trying to serialize it to JSON. Perviously(Symfony 3) there was a problem with Circular References which I managed to solve but this is little bit different.
I keep getting 500 response with exception:
NotNormalizableValueException
An unexpected value could not be normalized: NULL
If I remove relationship between entities it's fine so it's clearly the problem.
It's treating related object as something that cannot be normalized. For some reason. Not sure why it's happening. Didn't find anything in the docs on that.
Anything I missed here?

I think I have solved my problem. I went to:
/vendor/symfony/serializer/Serializer.php
After little investigation I was able to find out that normalizer is falling over PHP RESOURCE.
It turned out that I had one field of VARBINARY type in my User table. According to doctrine documentation that type is translated to PHP resource.
Conclusion: Symfony serialization does not have any problems with serializing entity and related child entities. It is simply not capable of normalizing resource type of data.
I think that's something worth remembering.

I would need some more info to help you. What serializer are you using, the Symfony one? How are you loading the serializer? Whatever it may be, the error is clear: the normalizer of the serializer you are using does not know what to do when finds a null field. My guess is that you are loading just one normalizer (of the around 5 available) to your serializer as it is shown in the official Symfony Serializer docs.

Related

Symfony API-Platform serializer maxDepth does not work

The Symfony API-Platform app I am working with has many entities with relations with self and they are recursively requesting the relations and can then exceed the memory and crash.
I did find this question here on SO, but there's no conclusive solution.
Attempting to limit the depth of the recurrence, I did the follow the documentation, as follows:
/config/packages/framework
framework:
serializer:
default_context:
enable_max_depth: true
I am not sure if the above is being actually applied, as it seems to accept anything under default_context. But it does show correctly when I run php bin/console debug:config framework.
The documentation above states that enable_max_depth needs to be set to true, but it is unclear on where/how to change that.
/src/Entity/SectorHierarchy
use Symfony\Component\Serializer\Annotation\MaxDepth;
#[
ORM\ManyToOne(targetEntity: self::class),
Groups(['sectorHierarchy:post', 'sectorHierarchy:get', 'sectorHierarchy:patch']),
MaxDepth(1)
]
private ?self $parent = null;
The problem was where to set the enable_max_depth to true.
So the code above I placed on /config/packages/framework is not applicable.
Instead the following should be added to the normalization_context:
'get' => [
'normalization_context' => [
'groups' => ['sectorHierarchy:get'],
'enable_max_depth' => true,
]
]

How to create api with resource not based on entity API Platform?

I need to create a GET endpoint to return a resource which is fetched from another application via http client, not based on entity. The resource I fetched is an array:
[
"id" => 1234
"first_name" => ""
"last_name" => ""
"email" => ""
"country" => 1
"country_code" => 93
"phone_number" => "3434343"
"nationality" => "AF"
"professional_industry" => "Health Care"
"job_title" => "Medical Doctor - General Practitioner"
"specialisation" => "No specialisation"
"career_length_month" => 1
"career_length_year" => 1
]
Then I need to query database to fetch some data to add to the resource array.
So I created it in src/Controller/MyController.php:
/**
* #Route("/api/v1/get-profile", name="get_profile")
* #return JsonResponse
*/
public function getMemberInfo(): JsonResponse
{
// step 1 : use http client to request data from another application
// step 2 : query DB to fetch some data and add to data array
return new JsonResponse($data, Response::HTTP_OK);
}
But now, I would like my api return json api response format : https://jsonapi.org/.
With resource based on entity, it is supported completely by api-platform. I don't need to do much. I just adding "key" #[ApiResource] in entity class and config some things. Then I have a api with json api format, so easily:
{
"data": {
"id": "",
"type": "",
"attributes": {}
}
}
But how about with resource not based on Entity? Is there any built-in feature of api-platform I could use, or I have to do a transformer by myself ?
You need override the OpenAPI specification how described in documentation:
https://api-platform.com/docs/core/openapi/#overriding-the-openapi-specification
Then, in the new class, you must add the schema definition that you require, adding new PathItem instances, each PathItem require new Operation instances, etc.
Sorry for not paste examples, it require so many code. I'm working this way in a project, but it's not public for now.
I hope I put you in the way.
The good practice is to can create a custom data provider. This custom data provider will be responsible to fetch data from the external source. Then, API-Platform will be able to use it like a regular entity.
Documentation
Full example in the API-Platform demo

How to inject Symfony Serializer to the controller properly?

I have a problem with injecting Symfony Serializer into the controller.
Here is a working example of what behavior I want to achive:
public function someAction(): Response
{
$goodViews = //here I get an array of Value Objects or DTO's ;
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
// normalize gives me a proper array for serialization
return $this->json(
$serializer->normalize($goodViews)
);
}
But now I need to change a direct creation of Serializer with dependency injection to controllers constructor or action. Other way, I think, is to create service which will get ObjectNormalizer and JsonEncoder as arguments, then create a Serializer and then normalize arrays of objects in special method and return result. But I can not figure out how to create serializer in service.yml or describe service dependencies properly. Symfony docs also get simple serializer service or create it manually as I did in the code example.
I thought to get serializer service from a service container in the action (with $this->get('serializer')) or inject it into the controllers constructor with NormalizerInterface (I need to normalize arrays of objects mostly), but this injected serializer fell with such an error:
"message": "Cannot normalize attribute \"options\" because the
injected serializer is not a normalizer", "class":
"Symfony\Component\Serializer\Exception\LogicException",
so I suppose, that its not configured in a way I've done with manual Serializer creation.
Our version of Symfony is 3.4 Thanks for your attention.
The decision of my problem is a little bit tricky. ObjectNormalizer was overriden by custom normalizer (with decorate part in the custom service defenition - see https://symfony.com/doc/3.4/service_container/service_decoration.html). Thats why in the framework preconfigured Symfony Serializer I got our custom one, which produced a mistake:
Cannot normalize attribute \"options\" because the injected serializer
is not a normalizer
So I've created a new serializer service with ObjectNormalizer:
new_api.serializer_with_object_normalizer:
class: Symfony\Component\Serializer\Serializer
arguments:
0:
- "#our_custom_serviec_which_override_object_normalizer.inner"
1:
- "#serializer.encoder.json"
public function someAction(SerializerInterface $serializer): Response // Add serializer as an argument
{
$goodViews = //here I get an array of Value Objects or DTO's ;
// normalize gives me a proper array for serialization
return $this->json(
$serializer->normalize($goodViews)
);
}
In services.yml
# *Bundle/Resources/services.yaml
services:
YourNamespace/*Bundle/Controller/YourController:
tags: [ 'controller.service_arguments' ]
Try it and let us know. This should get you going, but there are better ways you can configure your project. Check here for more info on using controllers as services and how to autowire them.
https://symfony.com/doc/3.4/service_container.html
https://symfony.com/doc/3.4/serializer.html

Routing requirements 0

I have a problem with my Symfony2.2 beta routes. (symfony-2-2-0-beta-1)
I use annoations routes like this:
#Route("/form/{id}", defaults={"id"=0}, requirements={"id"="\d+"});
And the error is:
An exception has been thrown during the rendering of a template ("Parameter "id" for route "[...]_form" must match "\d+" ("" given).") in [...]:form.html.twig at line 1.
The line 1 is:
{{ path("[...]_form") }}
I think the route is correct, because I define "id=0" as default.
And in Symfony2.1 the same code works.
Have you tried setting the default in your action and taking it out of the annotation?
/**
* #Route("/form/{id}", name="my_form", requirements={"id"="\d+"});
*/
public function myFunction($id = 0){
...
I believe this is one of the changes in Symfony 2.2 although I haven't tried it yet. http://symfony.com/blog/new-in-symfony-2-2-small-things-matter#defining-default-values-when-using-the-route-annotation
You can try
requirements:
id: \S|\d+

How to generate entities from database view with doctrine and symfony2

I am trying to generate entities from database using standard console commands as described in Symfony2 documentation here: http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html.
php app/console doctrine:mapping:convert --from-database --force yml "src/My/HomeBundle/Resources/config/doctrine/metadata/orm"
php app/console doctrine:mapping:import MyHomeBundle yml
php app/console doctrine:generate:entities MyHomeBundle
After this, all tables are generated correctly. The problem is that this won't generate entities for database views. When I add yml files myself into src/My/HomeBundle/Resources/config/doctrine/metadata/orm for example:
UserInGroup:
type: entity
table: user_in_group_view
fields:
id:
id: true
type: integer
unsigned: false
nullable: false
generator:
strategy: IDENTITY
userId:
type: integer
unsigned: false
nullable: false
column: user_id
userGroupId:
type: integer
unsigned: false
nullable: false
column: user_group_id
lifecycleCallbacks: { }
I get this exception when running php app/console doctrine:generate:entities MyHomeBundle:
Notice: Undefined index: My\HomeBundle\Entity\UserInGroup in C:\Users\ThisIsMe\Projects\SymfonyTestProject\vendor\doctrine\lib\Doctrine\ORM\Mapping\Driver\AbstractFileDriver.php line 121
Similar question was posted here: How to set up entity (doctrine) for database view in Symfony 2
I know I can create Entity class, but I was hoping that I could get this generated so if I change my view, I could just regenerate entity classes. Any suggestions?
Now you create your orm files only. You need to follow 2 more steps. I will give you the complete steps from begining.
Before doing this delete all yml files in your orm directory that you had created early.
I hope MyHomeBundle is your bundle name
1).php app/console doctrine:mapping:convert yml ./src/My/HomeBundle/Resources/config/doctrine --from-database --force
Symfony2 generate entity from Database
2).php app/console doctrine:mapping:import MyHomeBundle yml
3).php app/console doctrine:generate:entities MyHomeBundle
Hope this helps you.
Got the same issue, i use xml instead of yml but must be the same.
Check in your orm entity if the name include the correct route, exemple:
<entity name="Myapp\MyrBundle\Entity\MyEntity" table="myentity">
Because when i generate my orm from database the name was like that:
<entity name="MyEntity" table="myentity">
So doctrine didn't understand the right path.
Hope i'm clear and this will help you!
I know it's an old question but I found the trick (Symfony 6) to generate entities from SQL view (I use a SQL server database).
So I modified two methods of the SQLServerPlatform.php file of the doctrine bundle.
public function getListTablesSQL()
{
// "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
// Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication
return 'SELECT name, SCHEMA_NAME (uid) AS schema_name FROM sysobjects'
. " WHERE type = 'V' AND name != 'sysdiagrams' AND category != 2 ORDER BY name";
}
I changed the where condition parameter 'U' by 'V' for view.
Same operation for the method getListTableColumnsSQL(), we change here the 'U' parameter of the where condition by 'V'.
public function getListTableColumnsSQL($table, $database = null)
{
return "SELECT col.name,
type.name AS type,
col.max_length AS length,
~col.is_nullable AS notnull,
def.definition AS [default],
col.scale,
col.precision,
col.is_identity AS autoincrement,
col.collation_name AS collation,
CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type
FROM sys.columns AS col
JOIN sys.types AS type
ON col.user_type_id = type.user_type_id
JOIN sys.objects AS obj
ON col.object_id = obj.object_id
JOIN sys.schemas AS scm
ON obj.schema_id = scm.schema_id
LEFT JOIN sys.default_constraints def
ON col.default_object_id = def.object_id
AND col.object_id = def.parent_object_id
LEFT JOIN sys.extended_properties AS prop
ON obj.object_id = prop.major_id
AND col.column_id = prop.minor_id
AND prop.name = 'MS_Description'
WHERE obj.type = 'V'
AND " . $this->getTableWhereClause($table, 'scm.name', 'obj.name');
}
Maybe this will help someone.
As you can see here:
http://symfony.com/doc/current/cookbook/doctrine/reverse_engineering.html
the reverse engineering process from db to entity is not fully implemented yet:
"As the Doctrine tools documentation says, reverse engineering is a one-time process to get started on a project. Doctrine is able to convert approximately 70-80% of the necessary mapping information based on fields, indexes and foreign key constraints. Doctrine can't discover inverse associations, inheritance types, entities with foreign keys as primary keys or semantical operations on associations such as cascade or lifecycle events. Some additional work on the generated entities will be necessary afterwards to design each to fit your domain model specificities."

Resources