JMS Serializer: how to use camel case for properties - symfony

I'm using FOS Rest bundle and JMS Serializer to create a REST Api. The problem is I would like to keep the property names in the JSON response camel cased instead of using _.
For example, I have a property called employeeIdentifier, by default that gets converted to employee_identifier.
I saw that there's an option in the config to disable the lowercase and get rid of the _, but then it becomes EmployeeIdentifier.
Is there any way that JMS Serializer keeps the original name of the property? Thanks in advance

I found a way to do it globally, if you want to keep the property names as is you need to use the IdenticalPropertyNamingStrategy
There are several ways to accomplish this, first by changing the config(Thanks #Phantom):
#config.yml
jms_serializer:
property_naming:
id: 'jms_serializer.identical_property_naming_strategy'
Second, you could override the default alias for this
services:
jms_serializer.naming_strategy:
alias: jms_serializer.identical_property_naming_strategy
The bundle defines these https://github.com/schmittjoh/JMSSerializerBundle/blob/master/Resources/config/services.xml so you should be able to override them
Another way to do it is when you initialize the builder:
$serializebuilder = JMS\Serializer\SerializerBuilder::create();
$serializebuilder->setPropertyNamingStrategy(new \JMS\Serializer\Naming\IdenticalPropertyNamingStrategy());
$serializer = $serializebuilder->build();

Having upgraded jms/serilizer-bundle from 1.1 to 2.2 the parameter hack described above did not work. You can override the service definition as follows:
#app/config/services.yml
services:
....
jms_serializer.serialized_name_annotation_strategy:
class: JMS\Serializer\Naming\SerializedNameAnnotationStrategy
arguments:
- '#jms_serializer.identical_property_naming_strategy'

Worked for me (Symfony 4.4 and JMS ^3.8) with this config in config/packages/jms_serializer.yaml :
jms_serializer:
property_naming:
id: jms_serializer.identical_property_naming_strategy
and removing the cache manually
https://github.com/schmittjoh/serializer/issues/1037

I found a way to do it but it's not the best way I think, there's an annotation SerializedName wich allows you to override the property serialization. The problem is that you have to do it one by one on every property with camel case, here's the documentation:
YAML: http://jmsyst.com/libs/serializer/master/reference/yml_reference
Annotation: http://jmsyst.com/libs/serializer/master/reference/annotations#serializedname

I had to add the following to parameters.yml instead of config.yml:
jms_serializer.serialized_name_annotation_strategy.class: JMS\Serializer\Naming\SerializedNameAnnotationStrategy

Related

Why set factory in DefinitionDecorator instead of set new class with calls directive?

There is part of code in FOSElasticaExtension Extension class
$Def = new DefinitionDecorator('foo');
$Def->replaceArgument(0, $bar);
$Def->addTag('baz', array( 'name' => $qux, ));
$Def->setFactory(array(new Reference('quux'), 'corge'));
so in yaml it might look like this:
services:
foo:
arguments:
- '$bar'
tags:
- { name: baz }
factory: ["#quux", corge]
Why set factory in DefinitionDecorator instead of set new class with calls: directive?
services:
foo:
arguments:
- '$bar'
tags:
- { name: baz }
class: #quux
calls: corge
Can you please write a code example how you would expect it to look like? I don't really get the point of your question.
To generically answer why it's done like this you have to understand how Symfony compiles the service container. The service container has a huge impact on performance that's why it's compiled after the tree is completely built, which is after instantiating all the extensions. That also means that your extension doesn't really have the classes, but only references to classes. I assume the index class is not registered as a service and that's why it must be retrieved via the registered client service which is used as a factory. I hope this answers your question, if not feel free to expand your question or add a comment.
edit: That is an interesting question. I checked Symfony's DependencyInjection, but from a cursory glance I can't find how exactly both approaches are different. From checking DefinitionTest I assume it's possible to do something like:
$def->setMethodCalls(array(array($factoryReference,'getIndex')));
which looks a bit more complicated. This might be why setFactory was preferred.

dependency on a non-existent parameter

I renamed a service from Notifications to Notification (Because I have already a class called Notifications), but when I tried to call it I have the following error:
ParameterNotFoundException in ParameterBag.php line 106:
The service "notification" has a dependency on a non-existent parameter "app.bundle.notification.class". Did you mean one of these: "app.bundle.utils.class", "app.bundle.notifications.class"?
In my Bundle/Services I have 2 files:
- Notification.php
- Utils.php
In Notification.php the class is called class Notification {...}
In my config file services.yml
notification:
class: %app.bundle.notification.class%
arguments: [#templating]
I don't know where it can found the suggest value app.bundle.notifications.class
I tried to clear the cache but I have the same error.
You have to declare your service by this way:
parameters:
your.service.class: ACME\YourBundle\Services\ServiceName
services:
your.service:
class: "%your.service.class%"
arguments: [#templating]
Hope it will help you :)
Symfony errors on container mistakes are - IMO - one of the best thing of this stack. Look closer:
Did you mean one of these: "app.bundle.utils.class", "app.bundle.notifications.class"?
Compiler tells you probable parameters you can use (and which exist). Check the ones of your bundle and rename this param to the new convention.
The simpliest thing to do: search whole project if it looks a bit hidden.
PS. Don't forget to delete cache.

Getting Symfony base URL from a service?

I have a service that needs to access the current application base URL (what's returned by app.request.getBaseURL() in Twig views). Currently, my config is like this:
services:
WidgetModel:
class: AppBundle\Model\WidgetModel
scope: prototype
arguments: ['%widgets%']
So as a second argument, I would like to inject the base URL. Is it possible? If not, what would be proper solution?
As far as I know there is no builtin base url service. Which is actually a bit of a red flag that maybe having your component depending on it might not be such a good idea. But I can't think of a good reason why.
So normally, one would just inject the request object. But that has it's own problems as documented here: http://symfony.com/blog/new-in-symfony-2-4-the-request-stack
Instead, inject the #request_stack service and pull the url from it:
class WidgetModel
{
public __construct($widgets,$requestStack)
{
$this->baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
If you do find yourself needing the baseUrl in multiple services then you could define your own factory type service to generate it. But again, that might mean your design needs rethinking.
You can use the expression language in your service definition.
This example should do what you want:
services:
WidgetModel:
class: AppBundle\Model\WidgetModel
scope: prototype
arguments: [%widgets%, "#=service('request').getBaseUrl()"]
It fetches the request service and then executes the getBaseUrl method.
You will need to add a second parameter in your WigetModel for the base URL.
To complete the answer, in Symfony 3 you can use request_stack to get the base url using expressions languages (updated link) such as:
services:
WidgetModel:
class: AppBundle\Model\WidgetModel
scope: prototype
arguments: [%widgets%,"#=service('request_stack').getCurrentRequest().getBaseUrl()"

FOSRestBundle: How to remove {_format} parameter?

I need to support only single API format which is JSON and I don't like to {_format} in my routes. Is it possible to remove it?
In your config.yml, make sure you have this configured:
fos_rest:
format_listener: true
routing_loader:
default_format: json
include_format: false
Hope that helps
EDIT:
There is an example in the FOSRestBundle Docs that shows how to use the ClassResourceInterface. The biggest difference is that you don't have to manually define your routes at all. The interface will generate your routes based on you class name and the method name. So it is very important what you name your methods (you can override how the class name is used, this is shown in the docs)
for example, something like this:
use FOS\RestBundle\Routing\ClassResourceInterface {
class UserController implements ClassResourceInterface {
public function cgetAction() {
//return a list of all users
}
}
would generate a route that looks like this: [GET] /users. This is how I use the bundle, and it works great. I also don't have to use the {_format} option anywhere because I don't have to define the routes manually anywhere.
note - see my original answer as well, I made an edit that might also help with how you are using the bundle. I haven't tried using the bundle the way you are, so I'm not sure if this will work or not, but the docs make it seem like it will work.

add custom logic to internal Symfony classes like SwitchUserListener or TemplateGuesser

I got a problem to add custom logic to some Symfony classes.
SwitchUserListener
I want to add a check, that a user cannot switch to a another user, which have more rights/roles, than the initial user.
First attempt
Overwrite the parameter in the security_listeners.xml with the key:
security.authentication.switchuser_listener.class But where can I overwrite it?
In the security.yml it didn't work:
security:
...
authentication:
switchuser_listener:
class: Symfony\Component\Security\Http\Firewall\SwitchUserListener
Second attempt
Overwrite the service for the SwitchUserListner service id: security.authentication.switchuser_listener
I create the same service in my service.xml of my bundle, but my class was not used / called.
Another idea was to overwrite only the class, but that only works for bundles, but the SwitchUserListener was not in the SecurityBundle, it was in the symfony component directory and that seemed to me as a really bad idea to overwrite the SecurityBundle
Third attempt
Now I get the solution: First time I didn't realize that the dispatcher call listener for the SWTICH_USER event in the SwitchUserListener:
$switchEvent = new SwitchUserEvent($request, $token->getUser());
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent);
So I need only to create a service with the special tag for this event type:
<tag name="kernel.event_listener" event="security.switch_user" method="onSecuritySwitchUser" />
And do the check in the given method.
This seems to be a better solution thatn the other two. But there is still a problem. In my listener for the SwitchUserEvent I need to ignore my custom check if the user wants to exit the switched user.
So I need to check the requested path: ignore if path containts '?switch_user=_exit'
But the path (URL parameter) can be changed:
# app/config/security.yml
security:
firewalls:
main:
# ...
switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
But in my bundle I can't read this parameter, because it will not be passed to the service container. It will be passed to the constructor of the SwitchUserListner class and will be saved there as private attribute, never accessable (without Reflection) from outside. (that happens here: SecurityExtension.php line 591) So what to do? Define the parameter twice go against DRY. Use Reflection?
And the other point is that there aren' every time events that will be fired on which I write a subscriber class. So what would be another / best solution for it?
I ask this question because I will get some similar problem where I want to add or overwrite something of the symfony intern components.
TemplateGuesser
I wanted to modify the TemplateGuesser: For a specific bundle all Templates which has the annotation #Tempalte the tempate file should be located with the controller TestController#showAction at this path:
Resources/views/customDir/Test/show.html.twig
So the guesser should be put and locate everything into a additional folder customDir instead of using only views. When using the render function with a specific template, the guesser should ignore the annotation.
I created my own Guesser and overwrite the service id: sensio_framework_extra.view.guesser and in comparision to the SwitchUserListener this time my class is really called instead of the original guesser. Why it works here but not with the SwitchUserListener?
Is this a good solution at all? I also tried to add a second listener, which calls the TemplateGuesser, its the service sensio_framework_extra.view.listener with the class Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener But that didn't work.
Whenever you need to add custom logic or extend the framework behaviour, you can use and abuse the container configuration. That means you can overwrite pretty much every service Symfony defines by just creating a new class that extends that service – or not, really – and creating the service definition for it with the same key as the original service you wanted to extend or change behaviour.
For instance, Symfony has a base template guesser registered as a service with the sensio_framework_extra.view.guesser id. If you want to extend that or change behaviour, you only need to create your own class and register it with the same id of the original service – remember that the bundles loading order affects the service definitons with the same id, where the last one loaded is the one that will be created.
That should solve both of your problems.

Resources