Symfony 5.1: Loading an own twig extension in a microkernel - symfony

switching to smyfony, we struggle with some basic starting issues.
We are transferring an existing project to symfony.
Due to that, f.eg. configurations etc are mandatory to be in php and dynimically - so far no problem.
but: How can i load an own twig function (extension) in the microkernel ?
kernel.php
protected function configureContainer(ContainerConfigurator $container) {
$container->extension("twig", array(
"paths" => array(__DIR__ . "/../resources/views"),
"auto_reload" => true,
"cache" => false,
));
}
Twig is working fine and makes no problems ... undtil we need our own functions.
The (TEST !) twig function:
namespace App\Kernel\TwigExtensions;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class TwigTest extends AbstractExtension {
public function getFunctions() {
return array(
new TwigFunction("test", array($this, function() { return "ABC"; })),
);
}
}
using the following source within "configureContainer" has no effect:
$container->services()
->load("App\\Kernel\\TwigExtensions\\", sprintf("%s/Kernel/TwigExtensions/TwigTest.php", __DIR__))
->tag('twig.extension')
Any ideas for my next step ? Thanks !

Related

Symfony, route conflicts between controller and bundle

For example simple controller:
/**
* #Route("/{identifier}", name="page")
*/
public function page(Request $request, string $identifier)
{
$page = $this->pageRepository->findOneBy(['identifier' => $identifier]);
if (!$page || !$page->getEnabled()) {
throw $this->createNotFoundException();
}
return $this->render('cms/index.html.twig', []);
}
And a have a bundle to manage images from admin page elfinder, which will enter the /elfinder link.
And instead of getting the bundle controller, my controller gets.
/{identifier} === /elfinder
How do people usually act in such situations?
I tried to set different priority, but it does not help
Try adding your controllers with the priority required in the annotations.yaml file. Thus, if you get a 404 in the first one, Symfony will try to open the route from the next controller
Add your controllers to config/routes/annotations.yaml
page:
resource: App\Controller\_YourFistController_
type: annotation
elfinder:
resource: FM\ElfinderBundle\Controller\ElFinderController
type: annotation
Or if this option does not suit you, then you can try the optional parameter priority. symfony doc
Add to config file config/routes.yaml:
elfinder:
path: /elfinder/{instance}/{homeFolder}
priority: 2
controller: FM\ElfinderBundle\Controller\ElFinderController::show
I tried to set the priority through the configuration file. But unfortunately it didn't work.
The only thing that helped was to create your own methods that will redirect
/**
* #Route("/elfinder", name="elfinder", priority=10)
*/
public function elfinder()
{
return $this->forward('FM\ElfinderBundle\Controller\ElFinderController::show', [
'homeFolder' => '',
'instance' => 'default',
]);
}
/**
* #Route("/efconnect", name="efconnect", priority=11)
*/
public function elfinderLoad(Request $request, SessionInterface $session, EventDispatcherInterface $dispatcher)
{
return $this->forward('FM\ElfinderBundle\Controller\ElFinderController::load', [
'session' => $session,
'eventDispatcher' => $dispatcher,
'request' => $request,
'homeFolder' => '',
'instance' => 'default',
]);
}

Set queue name from parameters in symfony 3.4 and enqueue-bundle 0.9.13

This is my Symfony 3.4 configuration for php-queue bundle 0.9.13:
parameters.yml:
enqueue_dsn: 'amqp+lib://192.168.32.1'
services.yml:
messages_processor:
class: 'InteroperabilityBundle\Processor\MessagesProcessor'
tags:
- { name: 'enqueue.transport.processor', processor: 'manager_tasks_processor' }
MessagesProcessor.php:
class MessagesProcessor implements Processor, CommandSubscriberInterface, QueueSubscriberInterface
{
public function process(Message $message, Context $context)
{
// do something ...
}
public static function getSubscribedCommand(): array
{
return [
'command' => 'newMessageFromApi',
'processor' => 'manager_tasks_processor',
'queue' => 'manager_tasks',
'prefix_queue' => false,
'exclusive' => false,
];
}
public static function getSubscribedQueues(): array
{
return ['manager_tasks'];
}
}
And my command to start processor:
bin/console enqueue:transport:consume manager_tasks_processor manager_tasks --time-limit=3600
In the test environment this works. However now I need to specify a queue with a different name for production. And have another one in the testing environment (both on the same VPS).
How can I achieve this? The name of the queue is handcoded, in a static method. How can I get that name from the parameters?

Api Platform Pagination json format

i need help with the api platform pagination in json format.
here is my api_platform.yaml
api_platform:
allow_plain_identifiers: true
mapping:
paths: ['%kernel.project_dir%/src/Entity']
formats:
json: ['application/json']
when i used the format hydra (default) i got something like this
"hydra:view": {
"#id": "/api/galleries?page=1",
"#type": "hydra:PartialCollectionView",
"hydra:first": "/api/galleries?page=1",
"hydra:last": "/api/galleries?page=6",
"hydra:next": "/api/galleries?page=2"
}
can anyone help me? if it's possible to get something like that in json format or another way to aply pagination with api platform or symfony
thank you
You shoud create an event Subscriber :
<?php
namespace App\EventSubscriber;
use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator;
final class PaginateJsonSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['normalizePagination', EventPriorities::PRE_RESPOND],
];
}
public function normalizePagination(
ViewEvent $event
): void {
$method = $event->getRequest()->getMethod();
if ($method !== Request::METHOD_GET) {
return;
}
if (($data = $event->getRequest()->attributes->get('data')) && $data instanceof Paginator) {
$json = json_decode($event->getControllerResult(), true);
$pagination = [
'first' => 1,
'current' => $data->getCurrentPage(),
'last' => $data->getLastPage(),
'previous' => $data->getCurrentPage() - 1 <= 0 ? 1 : $data->getCurrentPage() - 1,
'next' => $data->getCurrentPage() + 1 > $data->getLastPage() ? $data->getLastPage() : $data->getCurrentPage() + 1,
'totalItems' => $data->getTotalItems(),
'parPage' => count($data)
];
$res = [
"data" => $json,
"pagination" => $pagination
];
$event->setControllerResult(json_encode($res));
}
}
}
In API Platform we have the jsonapi format which provides us the support for pagination. You can set the default config for number of records per page in api platform as:
# api/config/packages/api_platform.yaml
api_platform:
defaults:
pagination_items_per_page: 30 # Default value
formats:
jsonapi: ['application/vnd.api+json']
With this configuration you would require to either have an item and collection normalizer to customise the structure of the response with pagination metadata OR you can use state providers for this purpose (check here)
You would still get all the necessary pagination metadata if you just leave your default format as jsonapi
This is the very good resource to read more about API Platform pagination

Batch requests on Symfony

I am trying to reproduce the behaviour of the facebook batch requests function on their graph api.
So I think that the easiest solution is to make several requests on a controller to my application like:
public function batchAction (Request $request)
{
$requests = $request->all();
$responses = [];
foreach ($requests as $req) {
$response = $this->get('some_http_client')
->request($req['method'],$req['relative_url'],$req['options']);
$responses[] = [
'method' => $req['method'],
'url' => $req['url'],
'code' => $response->getCode(),
'headers' => $response->getHeaders(),
'body' => $response->getContent()
]
}
return new JsonResponse($responses)
}
So with this solution, I think that my functional tests would be green.
However, I fill like initializing the service container X times might make the application much slower. Because for each request, every bundle is built, the service container is rebuilt each time etc...
Do you see any other solution for my problem?
In other words, do I need to make complete new HTTP requests to my server to get responses from other controllers in my application?
Thank you in advance for your advices!
Internally Symfony handle a Request with the http_kernel component. So you can simulate a Request for every batch action you want to execute and then pass it to the http_kernel component and then elaborate the result.
Consider this Example controller:
/**
* #Route("/batchAction", name="batchAction")
*/
public function batchAction()
{
// Simulate a batch request of existing route
$requests = [
[
'method' => 'GET',
'relative_url' => '/b',
'options' => 'a=b&cd',
],
[
'method' => 'GET',
'relative_url' => '/c',
'options' => 'a=b&cd',
],
];
$kernel = $this->get('http_kernel');
$responses = [];
foreach($requests as $aRequest){
// Construct a query params. Is only an example i don't know your input
$options=[];
parse_str($aRequest['options'], $options);
// Construct a new request object for each batch request
$req = Request::create(
$aRequest['relative_url'],
$aRequest['method'],
$options
);
// process the request
// TODO handle exception
$response = $kernel->handle($req);
$responses[] = [
'method' => $aRequest['method'],
'url' => $aRequest['relative_url'],
'code' => $response->getStatusCode(),
'headers' => $response->headers,
'body' => $response->getContent()
];
}
return new JsonResponse($responses);
}
With the following controller method:
/**
* #Route("/a", name="route_a_")
*/
public function aAction(Request $request)
{
return new Response('A');
}
/**
* #Route("/b", name="route_b_")
*/
public function bAction(Request $request)
{
return new Response('B');
}
/**
* #Route("/c", name="route_c_")
*/
public function cAction(Request $request)
{
return new Response('C');
}
The output of the request will be:
[
{"method":"GET","url":"\/b","code":200,"headers":{},"body":"B"},
{"method":"GET","url":"\/c","code":200,"headers":{},"body":"C"}
]
PS: I hope that I have correctly understand what you need.
There are ways to optimise test-speed, both with PHPunit configuration (for example, xdebug config, or running the tests with the phpdbg SAPI instead of including the Xdebug module into the usual PHP instance).
Because the code will always be running the AppKernel class, you can also put some optimisations in there for specific environments - including initiali[zs]ing the container less often during a test.
I'm using one such example by Kris Wallsmith. Here is his sample code.
class AppKernel extends Kernel
{
// ... registerBundles() etc
// In dev & test, you can also set the cache/log directories
// with getCacheDir() & getLogDir() to a ramdrive (/tmpfs).
// particularly useful when running in VirtualBox
protected function initializeContainer()
{
static $first = true;
if ('test' !== $this->getEnvironment()) {
parent::initializeContainer();
return;
}
$debug = $this->debug;
if (!$first) {
// disable debug mode on all but the first initialization
$this->debug = false;
}
// will not work with --process-isolation
$first = false;
try {
parent::initializeContainer();
} catch (\Exception $e) {
$this->debug = $debug;
throw $e;
}
$this->debug = $debug;
}

ZF2 - ServiceManager & getServiceConfig problems - unable to fetch or create instance

I am coming across some problems when trying to use ZF2's authentication services. I have to following Module.php getServiceConfig function:
<?php
public function getServiceConfig()
{
return array(
'factories' => array(
'Auth\Model\CustomerTable' => function($sm) {
$tableGateway = $sm->get('CustomerTableGateway');
$table = new CustomerTable($tableGateway);
return $table;
},
'CustomerTableGateway' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Customer()); // prototype pattern implemented.
return new TableGateway('customer', $dbAdapter, null, $resultSetPrototype);
},
'Auth\Model\AuthStorage' => function($sm){
return new \Auth\Model\AuthStorage('jamietech');
},
'AuthService' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter,
'customer','username','password');
$authService = new AuthenticationService();
$authService->setAdapter($dbTableAuthAdapter);
$authService->setStorage($sm->get('Auth\Model\AuthStorage'));
return $authService;
},
),
);
}
The AuthStorage factory simply creates a new AuthStorage for us to keep track of the rememberMe function I have, and the AuthService factory creates a new Authentication Service for us. I can't see anything that I have done wrong but when running the following code in the AuthController.php:
<?php
public function loginAction()
{
//if already login, redirect to success page
if ($this->getAuthService()->hasIdentity()){
return $this->redirect()->toRoute('success');
}
$form = new LoginForm();
return array(
'form' => $form,
'messages' => $this->flashmessenger()->getMessages()
);
}
public function logoutAction()
{
$this->getSessionStorage()->forgetMe();
$this->getAuthService()->clearIdentity();
$this->flashmessenger()->addMessage("You have logged out successfully.");
return $this->redirect()->toRoute('auth', array('action'=>'login'));
}
PHPUnit encounters the following errors when running the PHPUnit command:
1: "testLoginActionCanBeAccessed - Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance of Zend\Db\Adapter\Adapter
1: "testLogoutActionCanBeAccessed - session_regenerate_id(): cannot regenerate session id - headers already sent.
And this error for both login and logout when the -process-isolation command is run:
"Serialization of closure is not allowed in: C;\Users\-----\AppData\Local\Temp\-----
If somebody could help that would be great. I am a ZF noob so try not to be too harsh.
EDIT: BTW THe global.php file includes the service_manager adapter factory illustrated in the ZF2 tutorial application.
Thank you!
Jamie Mclaughlan
did you check these:
autoload_classmap.php (for your module)
in your module.config.php
like this
service_manager' => array(
'aliases' => array(
'mymodule-ZendDbAdapter' => 'Zend\Db\Adapter\Adapter',
),
);
I hope it helps you to find the answer

Resources