I am attempting to use a custom handler for JMS Serializer Bundle
class CustomHandler implements SubscribingHandlerInterface
{
public static function getSubscribingMethods()
{
return array(
array(
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => 'integer',
'method' => 'serializeIntToJson',
),
);
}
public function serializeIntToJson(JsonSerializationVisitor $visitor, $int, array $type, Context $context)
{
die("GIVE ME SOMETHING");
}
}
This does nothing, and does not die. This is how I am registering the handler
$serializer = SerializerBuilder::create()
->configureHandlers(function(HandlerRegistry $registry) {
$registry->registerSubscribingHandler(new MyHandler());
})
->addDefaultHandlers()
->build();
$json = $serializer->serialize($obj, 'json');
My handler is never called and I cannot manipulate the data on serialisation.
You need to create a service for this handler:
custom_jms_handler:
class: MyBundle\Serializer\CustomHandler
tags:
- { name: jms_serializer.subscribing_handler }
Then make sure you use the registered JMS serializer service
$json = $this->get('jms_serializer')->serialize($obj, 'json');
I have this which works
$serializer = SerializerBuilder::create()
->configureListeners(function(EventDispatcher $dispatcher) {
$dispatcher->addSubscriber(new ProjectSubscriber($this->container));
$dispatcher->addSubscriber(new UserSubscriber($this->container));
})
->addDefaultListeners()
->addMetadataDir(realpath($this->get('kernel')->getRootDir()."/../") . '/src/Jake/NameOfBundle/Resources/config/serializer')
->build();
return $serializer->serialize($project, 'json');
$project is my entity.
You can omit this line if you don't have serializer configs
->addMetadataDir(realpath($this->get('kernel')->getRootDir()."/../") . '/src/Jake/NameOfBundle/Resources/config/serializer')
I think my main issue was this ->addDefaultListeners().
In config.yml I have
jms_serializer:
metadata:
auto_detection: true
directories:
NameOfBundle:
namespace_prefix: ""
path: "#JakeNameOfBundle/Resources/config/serializer"
I don't have anthing set up to make JMS a service.
Related
I want intercept all errors of Symfony after that show them in JSON.
In friendsofsymfony/rest-bundle v2 I can set parameters
fos_rest:
exception:
enabled: true
exception_controller: 'App\Controller\ExceptionController::showAction'
...
But in V3 the parameter exception_controller is deleted.
My current config of FOS REST:
fos_rest:
view:
formats:
xml: false
json: true
view_response_listener: force
serializer:
groups: ['Default']
serialize_null: true
format_listener:
rules:
- { path: ^/api/v1, priorities: [ json ], fallback_format: json, prefer_extension: true }
exception:
enabled: true
Official manual say that should use handlers in JMS.
https://symfony.com/doc/current/bundles/FOSRestBundle/4-exception-controller-support.html
But it does not contain explanation how config set in yaml.
You can intercept all errors by a symfony subscriber
<?php
declare(strict_types=1);
namespace App\Util\Serializer\Normalizer;
use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\JsonSerializationVisitor;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
class CustomExceptionHandler implements SubscribingHandlerInterface
{
private bool $debug;
public function __construct(bool $kernelDebug)
{
$this->debug = $kernelDebug;
}
public static function getSubscribingMethods(): array
{
return [
[
'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
'format' => JsonEncoder::FORMAT,
'type' => \Exception::class,
'method' => 'serializeToJson',
'priority' => -1,
],
];
}
public function serializeToJson(
JsonSerializationVisitor $visitor,
\Exception $exception,
array $type,
Context $context
) {
$data = $this->convertToArray($exception, $context);
return $visitor->visitArray($data, $type);
}
/**
* #return array<string, mixed>
*/
protected function convertToArray(\Exception $exception, Context $context): array
{
$statusCode = null;
if ($context->hasAttribute('template_data')) {
$templateData = $context->getAttribute('template_data');
if (array_key_exists('status_code', $templateData)) {
$statusCode = $templateData['status_code'];
}
}
$data['error'] = $this->getMessageFromThrowable($exception, $statusCode);
return $data;
}
protected function getMessageFromThrowable(\Throwable $throwable, ?int $statusCode): string
{
if ($this->debug) {
return $throwable->getMessage();
}
return array_key_exists($statusCode, Response::$statusTexts) ? Response::$statusTexts[$statusCode] : 'error';
}
}
services.yaml
App\Utils\Serializer\Normalizer\CustomExceptionHandler:
$kernelDebug: '%kernel.debug%'
I am using a package that is not especially made for symfony (TNTsearch), and have put all the functions I want to use in a service I called TNTsearchHelper.php. This service requires some variables, including some that could be found in the .env file. I currently define and construct these in my class:
class TntSearchHelper
{
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$config = [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'databasename',
'username' => 'user',
'password' => 'pw',
'storage' => 'my/path/to/file',
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
];
$this->config = $config;
}
What I would really like is to simply use the variables for my database that are set in the .env file. Is there any way to do this? This service is not registered in services.yaml because this is not neccesary with the autowire: true option, so I don't have any config options/file for my service in the config and wonder if I can keep it that way.
Yes. It's possible. If you want to use env variables for configuration, you have two options:
1.Use getenv:
$config = [
'driver' => 'mysql',
'host' => getenv('MYSQL_HOST'),
'database' => getenv('MYSQL_DB'),
'username' => getenv('MYSQL_LOGIN'),
'password' => getenv('MYSQL_PASSWORD'),
'storage' => 'my/path/to/file',
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
];
2.Configure your service in services.yaml:
services:
App\TntSearchHelper:
arguments:
- '%env(MYSQL_HOST)%'
- '%env(MYSQL_DB)%'
- '%env(MYSQL_LOGIN)%'
- '%env(MYSQL_PASSWORD)%'
And change your __construct function to this:
public function __construct(string $host, string $db, string $login, string $password, EntityManagerInterface $em)
{
$this->em = $em;
$config = [
'driver' => 'mysql',
'host' => $host,
'database' => $db,
'username' => $login,
'password' => $password,
'storage' => 'my/path/to/file',
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
];
$this->config = $config;
}
Also make sure that all this env variables are set because there's only DATABASE_URL variable in .env file by default
I know three possibilities. Each case has 03 steps configuration : 1 - declare yours variables in env. 2 - config service file 3 - and call your parameter
_ In controllers extending from the AbstractController, and use the getParameter() helper :
YAML file config
# config/services.yaml
parameters:
kernel.project_dir: "%env(variable_name)%"
app.admin_email: "%env(variable_name)%"
In your service,
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class UserController extends AbstractController
{
// ...
public function index(): Response
{
$projectDir = $this->getParameter('kernel.project_dir');
$adminEmail = $this->getParameter('app.admin_email');
// ...
}
}
_ In services and controllers not extending from AbstractController, inject the parameters as arguments of their constructors.
YAML file config
# config/services.yaml
parameters:
app.contents_dir: "%env(variable_name)%"
services:
App\Service\MessageGenerator:
arguments:
$contentsDir: '%app.contents_dir%'
In your service,
class MessageGenerator
{
private $params;
public function __construct(string $contentsDir)
{
$this->params = $contentsDir;
}
public function someMethod()
{
$parameterValue = $this->params;
// ...
}
}
_ Finally, if some service needs access to lots of parameters, instead of injecting each of them individually, you can inject all the application parameters at once by type-hinting any of its constructor arguments with the ContainerBagInterface:
YAML file config
# config/services.yaml
parameters:
app.parameter_name: "%env(variable_name)%"
In your service,
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
class MessageGenerator
{
private $params;
public function __construct(ContainerBagInterface $params)
{
$this->params = $params;
}
public function someMethod()
{
$parameterValue = $this->params->get('app.parameter_name');
// ...
}
}
source Accessing Configuration Parameters
I'm having a problem recovering my entities, the entities are in the AppBundle / Entity folder, but symfony can not find it ...
Here is the error: Class 'Product' does not exist
Here is the function myManager () present in a controller
public function myManager(){
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/AppBundle/Entity"), $isDevMode);
// database configuration parameters
$conn = array(
'dbname' => 'teste',
'user' => 'root',
'password' => '',
'host' => '127.0.0.1',
'driver' => 'pdo_mysql',
);
$entityManager = EntityManager::create($conn, $config);
return $entityManager;
}
the function testAction () that calls the manager and tries to load the Product entity
public function testAction(){
$em = $this->myManager()->getRepository('Product');
return $this->render('toto.html.twig');
}
link of documentation : Doctrine
You need to use the correct notation to make a reference to your entity:
$manager->getRepository('MyBundleName:Product')
I am sure it is a small error but I cannot find it.
I am trying to follow the official doc and implement an event listener on the pre_serialize event.
My service declaration:
<service id="app.question_serializer_subscriber" class="AppBundle\Serializer\QuestionSerializerSubscriber">
<tag name="jms_serializer.event_subscriber"/>
</service>
My subscriber:
<?php
namespace AppBundle\Serializer;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
class QuestionSerializerSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
array(
'event' => 'serializer.pre_serialize',
'method' => 'onPreSerialize',
)
);
}
public function onPreSerialize(ObjectEvent $event)
{
die('in event');
}
}
And my controller:
$question = $repo->findLastVersionByQuestionId((int) $questionId);
$serializer = SerializerBuilder::create()->build();
$context = new SerializationContext();
return new JsonResponse(json_decode(
$serializer->serialize(
$question,
'json',
$context
),
true
));
When I access the route my entity Question is serialized and displayed, but why does the die('in event'); is not displayed ?
Maybe it has a relation with the fact that my object is a Doctrine entity (issue 666 or PR 677 )
I finally find the issue. The problem is
$serializer = SerializerBuilder::create()->build();
This does not work but this does:
$serializer = $this->get('jms_serializer');
Try adding the class attribute, as example:
public static function getSubscribedEvents()
{
return array(
array(
'event' => 'serializer.pre_serialize',
'class' => 'FQCN_class_name',
'method' => 'onPreSerialize',
)
);
}
Another difference regarding the doc is in the argument of the class method: you should use PreSerializeEvent instead of ObjectEvent:
So like this:
public function onPreSerialize(PreSerializeEvent $event)
{
// ...
}
Check your service is correctly load from the container as example with the console command:
php app/console debug:container --tag=jms_serializer.event_subscriber
Hope this help
I have a REST API and have an Entity Userwith field called Avatar, in DB I save name XXXX.jpg but when I return I want to add a url in this field Avatar, for example www.mylink.com/XXXX.jpg.
I'm trying with a service implements SubscribingHandlerInterfacebut I don't know how I can use it.
I have this method in this service:
class UrlManager implements SubscribingHandlerInterface
{
public static function getSubscribingMethods()
{
return array(
array(
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => 'AppBundle/Entity/User',
'method' => 'serializeUrlAvatar',
),
);
}
public function serializeUrlAvatar(User $user)
{
$url = 'www.mylink.com';
return array(
"avatar" => $url . $user->getAvatar()
);
}
}
but how can I call this service to modify url when I serialize.
Now I do this:
$_format = 'json';
$json = $this->get('jms_serializer')->serialize($user, $_format);
return new Response($json, 200, ['Content-Type' => 'application/' . $_format]);
In service.yml:
app.url_converter_service:
class: AppBundle\Service\UrlManager
tags:
- { name: jms_serializer.subscribing_handler }
Update
In my controller I call this function like this:
$result = $this->get('app.url_converter_service')->serializeUrlAvatar($user);
$json = $this->get('jms_serializer')->serialize($result, $_format);
return new Response($json, 200, ['Content-Type' => 'application/' . $_format]);
So my question is, exists a way to remove the first line and serialize correctly (add the url) when I serialize?
Have you registered your service like this?
# app/config/services.yml
avatar_url_handler:
class: YourBundle\Serializer\Handler\AvatarUrlHandler
tags:
- { name: jms_serializer.subscribing_handler }
I found a solution. I create a service which implements EventSubscriberInterface like this:
class UserSerializeHandler implements EventSubscriberInterface
{
private $user_uploads;
public function __construct($user_uploads){
$this->user_uploads = $user_uploads;
}
public static function getSubscribedEvents()
{
return array(
array(
'event' => 'serializer.pre_serialize',
'class' => User::class,
'method' => 'onPreSerializeUser'
));
}
public function onPreSerializeUser(PreSerializeEvent $event)
{
/** #var User $user */
$user = $event->getObject();
$avatar = $user->getAvatar();
$user->setAvatar($this->user_uploads . "/" . $avatar);
}
}
In service.yml:
app.serializer_user_service:
class: AppBundle\Service\UserSerializeHandler
arguments: ['%user_uploads%']
tags:
- { name: jms_serializer.event_subscriber }
I have user_uploads in parameters.yml like this:
user_uploads: 'https://myUrl.com'
And in any Controller that I serialize a User, I add the url in the Avatar paramter.
$json = $this->get('jms_serializer')->serialize($user, $_format);
return new Response($json, 200, ['Content-Type' => 'application/' . $_format]);