symfony 4.2 max_depth_handler serializer how to implement? - symfony

Hey just an hour ago I asked a question about the new circular_reference_handler in symfony 4.2's serializer.
(use the "circular_reference_handler" key of the context instead symfony 4.2)
The answer to that question leads me to a new problem of the maximum nesting level reached.
In the documentation (https://symfony.com/doc/current/components/serializer.html#handling-serialization-depth)
There is no mention of this context key or how to implement it.
If I use the example of the circular_reference_handler of my previous question i'll add the context key in the framework.yaml file under :
framework:
serializer:
max_depth_handler: 'App\Serializer\MyMaxDepthHandler'
And create the class
namespace App\Serializer;
class MyMaxDepthHandler
{
public function __invoke($object){
//TODO how to handle this
}
}
And in order for the serializer to use this handler I set the context for the serialize function :
$this->serializer->serialize($object, 'json', ['enable_max_depth' => true]);
Now my question is how do I handle this ? Does anyone have an example of what to put in the body of this __invoke function ?
Any help would be greatly appreciated

So I would simply do this:
<?php
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
$this->serializer->serialize($object, 'json', [ObjectNormalizer::ENABLE_MAX_DEPTH => true, ObjectNormalizer::MAX_DEPTH_HANDLER => new MyMaxDepthHandler()]);
About the code inside __invoke, you can return whatever data you need in fact. For example just return the id of the related object. Useful in some case for the output json
And you need to update your __invoke method like this:
<?php
namespace App\Serializer;
class MyMaxDepthHandler
{
public function __invoke($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []){
return $innerObject->id;
}
}
You can find a detailled explanation in the Handling Serialization Depth section of the documentation
I guess the Serializer ends by calling normalize inside when you call the serialize method but double check about it. If it is not the case maybe call the normalize method directly in case this solution does not work. Because the documentation provide an example only with normalize

Related

How do I prevent phpunit from overriding method type-hints

I have a wrapper that gives me a more convenient API to create mocks. You can ignore the implementation details. Just note the fact that:
it's a trait used on my test classes
the method used to obtain the mock is type-hinted
trait MockFacilitator {
protected function positiveStub (string $target, array $overrides, array $constructorArguments = []):MockBuilder {
$builder = $this->getBuilder($target, $constructorArguments, $overrides);
$this->stubSingle($overrides, $builder);
return $builder;
}
private function stubSingle (array $overrides, MockBuilder $builder):void {
foreach ($overrides as $method => $newValue)
$builder->expects($this->any())
->method($method)->will($this->returnValue($newValue));
}
private function getBuilder (string $target, array $constructorArguments, array $methodsToRetain):MockBuilder {
$builder = $this->getMockBuilder($target);
if (!empty($constructorArguments))
$builder->setConstructorArgs($constructorArguments);
if (!empty($methodsToRetain))
$builder->setMethods(array_keys($methodsToRetain));
return $builder->getMock();
}
Used in my tests like so
$handler = $this->positiveStub(LoginRequestHandler::class, ["isValidRequest" => true]);
Now, the generated stub not only creates the new class that I want (LoginRequestHandler) , but creates new classes for the method return types as well(MockBuilder) ! As is explained on this thread https://stackoverflow.com/a/39070492, such code will not compile.
Is it that phpunit dislikes type hinting method return types? I have seen on other threads that arguments with reference types will equally be overwritten; although in their case, the type was removed altogether, citing inability of phpunit to load those types as the reason. In my case, it probably mocks those return types as well
Is there a way to restrict type generation to only what goes into the mock builder? I prefer to leave out everything else as is in my original class
MockBuilder::disableAutoReturnValueGeneration() can be used to disable the automatic return value generation. The requires using the Mock Builder API instead of createStub(), createMock(), etc., of course.
Of course, automatic return value generation is not triggered, even when it is enabled, which is the default, when you explicitly configure return values for your stubs.

dynamic class loading - Attempted to load class _CLASS_ from namespace _NAMESPACE_

So i try to load a class inside a service in Symfony4.
It doesn't matter if i load it as classname or as App\to\class\name\classname.
It generates the same error.
Other posts said you need to add the whole fully qualified classname..
This doesn't work. Am I missing something?
Code below:
<?php
// src/Service/AdwordsService.php
namespace App\Service;
use App\Service\AdTypes\ExpendedTextAdTypes as ExpendedTextAdTypes;
class AdwordsService{
...
public function getAdsModel($ad) //<-- for example "EXPANDED_TEXT_AD"
{
$type = explode('_',strtolower($ad->getType()));
$modelName = array_map('ucfirst', $type);
$modelName = implode('',$modelName).'Types';
// will load ExpandedTextAdTypes
return new $modelName($ad);
}
...
}
Class that it tries to load:
<?php
// src/Service/AdTypes/ExpendedTextAdTypes.php
namespace App\Service\AdTypes;
class ExpendedTextAdTypes
{
private $adData;
public function __construct($ad)
{
$this->adData = $ad;
}
}
The ultimate problem(s) was a simple typo: EXPANDED_TEXT_AD vs EXPENDED_TEXT_AD
along with the need to use a fully qualified class name:
// No need for any use statements
// use App\Service\AdTypes\ExpendedTextAdTypes as ExpendedTextAdTypes;
public function getAdsModel($ad) //<-- for example "EXPENDED_TEXT_AD"
{
$type = explode('_',strtolower($ad));
$modelName = array_map('ucfirst', $type);
$modelName = implode('',$modelName).'Types';
$modelName = 'App\\Service\\AdTypes\\' . $modelName; // Add this
return new $modelName($ad);
}
As a rule, typo questions are considered to be off-topic. But this question actually has two issues as well as pointing out that the use statement is not needed. So I guess it can qualify as an answer.
The question's title is also misleading. I clicked on the question because I had never seen CLASS in an error message. It would have been better to have posted the actual error message thus possible making it easier to detect the typo.
And finally, a bit of unsolicited advice. This sort of transformation from EXPENDED_TEXT_AD to ExpendedTextAdTypes can be difficult to maintain and definitely locks you in to a class naming scheme. Why not just use ExpendedTextAd instead of EXPENDED_TEXT_AD? Symfony does this sort of thing all the time.

How to render a view from service class in symfony?

I'm trying to make a function in my service class, that render a twig page. I've tried to do like this:
service.yml:
********
parameters:
error.class: AppBundle\Utils\Error
services:
app.error:
class: '%error.class%'
arguments: [#templating]
Error.php (service class):
****
class Error
{
public function __construct($templating)
{
$this->templating = $templating;
}
public function redirectToError($condition,$message)
{
if($condition){
return $this->templating->render('default/error.html.twig',array(
'error_message' => $message,
));
}
}
}
and error.html.twig that have some random text to see if it gets there.
After that I get this answer from browser:
Can somebody to tell me what is the problem?
YAML can be a bit iffy when it comes to syntax, make sure your using all spaces (no tab chars). And makes sure every indentation is the same amount of space characters. Like 2/4/6/8 for each level or 4/8/12 etc if you prefer 4 wide.
The code you posted should be fine, but its probably something silly as described above. If it was actually a wrong section/ parameter in the file symfony should tell you what is unexpected as it actually validates YAML files on its content.
Allright so ['#templating'] takes care of the YAML parse error, the next part is how to use a service. Which is done using the service container.
In a controller there is an alias for it and you can do something like:
// required at the top to use the response class we use to return
use Symfony\Component\HttpFoundation\Response;
// in the action we use the service container alias
// short for $this->container->get('app.error');
$content = $this->get('app.error')->redirectToError(true, 'Hello world');
// as your redirectToError function returns a templating->render, which only returns a
// string containing the the rendered template, however symfony
// requires a Response class as its return argument.
// so we create a response object and add the content to it using its constructor
return new Response($content);
A few small things:
$condition, is probably likely to change if not it seems it should not be in the function but around the function call, as it seems weird to call an redirectToError but there is no error, instead we just call it when we do have an error.
And its recommended to if you are setting a class variable to define it (details on visibility):
class Error {
// visibility public, private, protected
protected $templating;
You should put ' around #templating
services:
app.error:
class: AppBundle\Utils\Error
arguments: ['#templating']

Display nested list with doctrine2 and zf2

Following this tutorial and putting in all together to make it work in my project, just to display a nested list (using doctrine 2 and zf2) , I can not enter into the foreach. Using this snippet of code:
$root_categories = $em->getRepository('Controleitor\Model\Entity\Category')->findBy(array('parent_category' => null));
$collection = new \Doctrine\Common\Collections\ArrayCollection($root_categories);
$category_iterator = new \MYMODULE\Model\Entity\RecursiveCategoryIterator($collection);
$recursive_iterator = new \RecursiveIteratorIterator( $category_iterator, \RecursiveIteratorIterator::SELF_FIRST);
foreach ($recursive_iterator as $index => $child_category){
echo 'test';
}
Debug::dump($recursive_iterator);die;
I'm expecting to print the 'test' string but it only print this:
object(RecursiveIteratorIterator)#414 (0) {}
But when I do before the dump:
$recursive_iterator->current()->getTitle();
I got the title.. It fails somehow looping the \Doctrine\Common\Collections\ArrayCollection object.
If you're using different Debug class instead of Doctrine's one, that may the suspect. Try Doctrine\Common\Util\Debug::dump().
Explain comes from official documentation:
Lazy load proxies always contain an instance of Doctrine’s
EntityManager and all its dependencies. Therefore a var_dump() will
possibly dump a very large recursive structure which is impossible to
render and read. You have to use Doctrine\Common\Util\Debug::dump() to
restrict the dumping to a human readable level. Additionally you
should be aware that dumping the EntityManager to a Browser may take
several minutes, and the Debug::dump() method just ignores any
occurrences of it in Proxy instances.
I had the same issue. I've discussed with the author of this tutorial, he recommended me to check the valid() function of the RecursiveCategoryIterator class and there was the problem.
Since I was using "use" statetment and left a backslash before th class name:
use Entity\Category;
use Doctrine\Common\Collections\Collection;
class RecursiveCategoryIterator implements \RecursiveIterator
{
//.......
public function valid()
{
return $this->posts->current() instanceof \Category;
}
There ware two ways to solve this problem:
1. To remove the backslash:
return $this->posts->current() instanceof Category;
2. To use full namespace:
use Entity\Category; // remove this line
//.......
return $this->posts->current() instanceof \Entity\Category;
Hope that helps.

FOSRestBundle and JMSSerializer runtime expose

My Symfony2 API uses FOSRestBundle and JMSSerializer, with property annotations, but there are many times when I don't want to expose every property. I understand JMS has exclusion groups, but I can't figure out how to include those in my Symfony controllers. There should be a way to use PHP on a dynamic basis but that seems to be missing from the documentation too.
If you use View class like in this example, you can set serialization context with setSerializationContext method
public function getUsersAction()
{
$data = // get data, in this case list of users.
$view = $this->view($data, 200)
->setSerializationContext(SerializationContext::create()->setGroups(array('list')))
;
return $this->handleView($view);
}
Since FOSRest 2.0 version you must use this:
$view = $this->view($response, $code);
$view->setContext($view->getContext()->setGroups(['get_client']));

Resources