how to use api-platform serialization in custom symfony code - php

I have a standard symonfy(6) app and api-platform(3) configured for some vue apps that are embedded on specific pages of my symfony app.
I want to use the json-ld serialization used by api-platform in non api code.
I can use the symfony serializer, and this works:
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new ObjectNormalizer();
$serializer = new Serializer([$normalizer], ['json' => new JsonEncoder()]);
$output = $serializer->serialize($entity, 'json', ['groups' => 'read'])
but that lacks some useful features, like the iri's of relatet entities ..
i would like to have the same serialization output, that apiPlatform generates.
How can i call the serializer to get exact the same result as i get from a configured url/endpoint?
Why do i want this: i have an vue app that needs to collect data from many endpoints for initialization. i want to prepare and give that data via the symfony-template that holds the entrypoint for the vue app.
Update:
I figured out a partial solution after some debugging the normal api process, but i'm sure i didnt see all consequences. you can use the api-platform serializer service in your services.
I build a wrapper service around the api-platform serializer, so i can autowire it in my controller:
App\Utils\Api\OfflineApi:
arguments: ['#api_platform.serializer']
controller:
public function action(EntityClass $entity, OfflineApi $offlineApi): Response {
$offlineApi->serialize($entity, ['groups' => 'read']),

Related

How do you make symfony make:crud available for API's?

In symfony you can use the command make:crud. That works excellent with forms and twig in symfony. But is there also a way to do it with api's? That I will send an POST to the route of the annotation.
Like in python;
url = 'https://127.0.0.1:8000/players/new'
myobj = {
'postName': 'postData',
}
This python code is used when i want to test a POST.
This is a piece of a make:crud what i used, only showing the New function of the CRUD. This only works with forms. I cant send directly a POST(ex, python) to it.
/**
* #Route("/players/new", name="players_new", methods={"GET","POST"})
*/
public function new(Request $request): Response
{
$player = new Players();
$form = $this->createForm(PlayersType::class, $player);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($player);
$entityManager->flush();
return $this->redirectToRoute('players_index');
}
return $this->render('players/new.html.twig', [
'player' => $player,
'form' => $form->createView(),
]);
}
I changed your endpoint slightly to make it more API friendly. In all the public facing APIs that I've built they all return JSON. That just eases the burdens for implementation. I always use a status 201 for creation, and 400 for bad requests. This serves a traditional role for RESTful API paradigms and implementations.
/**
* #Route("/players/{player}", name="get_player", methods={"GET"})
*/
public function getPlayer(Player $player): Response {
// You might need to tweak based on your Entity name
return new JsonResponse($player);
}
/**
* #Route("/players/new", name="players_new", methods={"POST"})
*/
public function newPlayer(Request $request): Response
{
$player = new Players();
$form = $this->createForm(PlayersType::class, $player);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($player);
$entityManager->flush();
} catch (\Exception $e) {
// Probably a better exception to catch here, log it somewhere
throw $e;
}
return new JsonResponse('', Response::HTTP_CREATED);
}
return new JsonResponse(
'Invalid data provided to API, please refer to documentation',
Response::HTTP_BAD_REQUEST
);
}
Changing the method from new to newPlayer was done because a method named new is confusing down the road. One thing I would also like to point out is that with Doctrine it's best to have your entities singular. Example: Player instead of Players. You can have the relationship be players within the Entity.
The Entity Game could have $players which is a relationship to OneToMany Player Entities.
Catching the exception on flush is good standard practice there. You should return a meaningful JsonResponse as well after you've logged it.
As per the official documentation, API platform is a “powerful but easy to use full-stack framework dedicated to API driven projects”. API platform helps developers significantly speed up their development process, building complex and high performance, hypermedia-driven APIs.
It ships with Symfony 4, the Doctrine ORM, a dynamic Javascript admin created with React, and React Admin, Varnish Cache server, Helm Chart to help deploy the API in a Kubernetes cluster and a Progressive Web Application skeleton. It also includes a Docker setup for providing Nginx servers to run the API and JavaScript apps. Most inspiring is the ability of API platform to natively generate project documentation with support of OpenAPI!
In this tutorial, I will take you through how to create a simple bucket list API with CRUD operations.
Prerequisites
PHP - Version 7.0 or higher.
Docker
Postgres
Getting Started
Follow the instructions below to setup your development environment:
$ mkdir demo-app
$ cd demo-app
Download the latest compressed .tar.gz distribution. Then extract it inside of our working directory and run the commands below:
$ cd api-platform-2.4.5
$ docker-compose pull
$ docker-compose up -d
The docker-compose pull command downloads all images specified in the docker-compose.yml file. In order to start the containers, run docker-compose up -d. The -d flag runs the containers in detached mode, meaning they run in the background. In order to view the container logs, you can run this command docker-compose logs -f in a separate terminal.

Using a Swoole \Swoole\Coroutine\Http\Client based HttpClientAdapter for Guzzle - How?

I have tried to implement a Guzzle HandlerStack HttpClientAdapter for Swoole and use it via:
$handler = new \App\Swoole\HttpClientAdapter();
$client = new \GuzzleHttp\Client([
'handler' => \GuzzleHttp\HandlerStack::create($handler),
]);
$res = $client->request('POST', 'http://localhost/foo');
The handler class looks like this:
namespace App\Swoole;
class HttpClientAdapter
{
public function __invoke(RequestInterface $request, array $options)
{
// [..] init and request modification, path/port extraction removed
$cli = new \Swoole\Coroutine\Http\Client($ip, $port, $ssl);
$cli->execute($path);
$cli->close();
return new Promise\FulfilledPromise(
new Psr7\Response($cli->statusCode, $cli->headers, $cli->body)
);
}
}
This actually works - but only if i put in a MockServer Proxy in between.
Without, i just do not get any data back ... i have tried using different endpoints, but to no avail. Does anybody know how to tackle/debug the problem and/or is there a Swoole Guzzle Adapter out there in the wild?
Well, this work is awesome.
But I suggest another httpclient based on swoole coroutine:https://github.com/swlib/saber/blob/master/README-EN.md
It's developed by swoole core developer and you will find it quite look like Guzzle.

rendering symfony/form in non-symfony application

I'm trying to rewrite a "custom framework" application to the Symfony, but I can not do everything at once, so I've divided the process into steps.
From important notes - I've already implemented the symfony/templating component and the symfony/twig-bridge component.
That's how I want to output the form in the template:
<?php echo $view['form']->form($form) ?>
As I'm doing so the following error is thrown:
Symfony\Component\Form\Exception\LogicException
No block "form" found while rendering the form.
/var/www/html/vendor/symfony/form/FormRenderer.php on line 98
To render the templates I'm using the DelegatingEngine which uses the PhpEngine and the TwigEngine.
Setting up the Twig with the \Symfony\Bridge\Twig\Extension\FormExtension is well documented, but what I'm missing is the php setup. This is how I'm doing this:
new \Symfony\Component\Form\Extension\Templating\TemplatingExtension($phpEngine, $this->csrfManager());
Could you point me what am I missing or what's wrong with my setup?
I think the simplest way would have been to install the Symfony 3.3 standard edition next to your app (pending the release of Symfony Flex).
After this, find a way to use the router of Symfony with the router of your application.
So you could have the full Symfony framework, create your form type in it and let Symfony render it :
With an ajax call
With a new Symfony Kernel in your legacy app
I've found the answer:
I was using the wrong FormRendererEngineInterface. Instead of relying on the \Symfony\Component\Form\Extension\Templating\TemplatingExtension class I've registered the form helper by myself:
$phpEngine = new PhpEngine(new TemplateNameParser(), new FilesystemLoader(realpath(__DIR__.'/../Template').'/%name%'));
$twigEngine = new TwigEngine($this->twig(), new TemplateNameParser());
$this->TemplateEngine = new DelegatingEngine(array(
$phpEngine,
$twigEngine,
));
$phpEngine->addHelpers(array(
new FormHelper(new FormRenderer($this->twigFormRendererEngine())),
));
As you can see in the TemplatingEngine:
public function __construct(PhpEngine $engine, CsrfTokenManagerInterface $csrfTokenManager = null, array $defaultThemes = array())
{
$engine->addHelpers(array(
new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfTokenManager)),
));
}
It relies on the TemplatingRendererEngine while I need the TwigRendererEngine instance, as the form templates are the twig files.
Correct me if my explanation is wrong, but the solution is working.

Functional tests in symfony3, access to container after submitting a form

I'm writing functional tests for my Symfony3 application. I have a test which looks like this:
public function testList()
{
$client = static::createClient();
$client->getCookieJar()->set($this->cookie);
$this->sender->method('isSuccessfull')->will($this->returnValue(true));
$container = $client->getContainer();
$container->set('app.service1', $this->object1);
$container->set('app.service2', $this->object2);
$crawler = $client->request('GET', '/list/1');
$form = $crawler->selectButton('Save')->form();
$client->submit($form);
}
Everything is good until submitting form. Kernel losing the setted container services while submitting a form. How can I these services into container also after submitting a form? Maybe there is other option to resolve my problem?
If you check the source code for Symfony\Component\HttpKernel\Client::doRequest() class you can see that it terminates the kernel which is then started again later and that's why you loose all service you created manually.
I guess you have an app which you're testing so you could add the services to its services.yml. Another way could be extending the Client class with your own and overriding getContainer() method to always add these extra services (then you'd have to update the service definition for test.client in a compile pass with your customized class).

what are channels in symfony 2?

I am trying to use a logging mechanism as follows:
log_handler:
class: %monolog.handler.stream.class%
arguments: [ %kernel.logs_dir%/%kernel.environment%.yourFileName.log ]
logger:
class: %monolog.logger.class%
arguments: [ nameOfLoggingChannel ]
calls: [ [pushHandler, [#log_handler]] ]
however my app is crapping out parsing the "nameOfLoggingChannel". What is that? Can someone provide some guidance?
It's just a name. It will be included in the messages logged by that logger. Quoting from the docs:
Channels are a great way to identify to which part of the application a record is related. This is useful in big applications (and is leveraged by MonologBundle in Symfony2). You can then easily grep through log files for example to filter this or that type of log record.
Using different loggers with the same handlers allow to identify the logger that issued the record (through the channel name) by keeping the same handlers (for instance to use a single log file).
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
// Create some handlers
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$firephp = new FirePHPHandler();
// Create the main logger of the app
$logger = new Logger('my_logger');
$logger->pushHandler($stream);
$logger->pushHandler($firephp);
// Create a logger for the security-related stuff with a different channel
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
$securityLogger->pushHandler($firephp);

Categories