FOSHttpCache with Symfony reverse proxy - php

So I'm trying to configure the FOSHttpCacheBundle (latest release ^2.0) with the default Symfony HttpCache.
Here is the content of my AppCache.php according to the docs:
<?php
use FOS\HttpCache\SymfonyCache\CacheInvalidation;
use FOS\HttpCache\SymfonyCache\EventDispatchingHttpCache;
use FOS\HttpCache\SymfonyCache\DebugListener;
use FOS\HttpCache\SymfonyCache\CustomTtlListener;
use FOS\HttpCache\SymfonyCache\PurgeListener;
use FOS\HttpCache\SymfonyCache\RefreshListener;
use FOS\HttpCache\SymfonyCache\UserContextListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
class AppCache extends HttpCache implements CacheInvalidation
{
use EventDispatchingHttpCache;
/**
* Overwrite constructor to register event listeners for FOSHttpCache.
*/
public function __construct(
HttpKernelInterface $kernel,
StoreInterface $store,
SurrogateInterface $surrogate = null,
array $options = []
) {
parent::__construct($kernel, $store, $surrogate, $options);
$this->addSubscriber(new CustomTtlListener());
$this->addSubscriber(new PurgeListener());
$this->addSubscriber(new RefreshListener());
$this->addSubscriber(new UserContextListener());
if (isset($options['debug']) && $options['debug']) {
$this->addSubscriber(new DebugListener());
}
}
/**
* Made public to allow event listeners to do refresh operations.
*
* {#inheritDoc}
*/
public function fetch(Request $request, $catch = false)
{
return parent::fetch($request, $catch);
}
}
Now according to the Symfony docs, enabling the caching proxy is just a matter of setting the app.php file as follow (uncommenting that last line):
$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();
$kernel = new AppCache($kernel);
Which gives me a nice:
PHP message: PHP Catchable fatal error: Argument 2 passed to
Symfony\Component\HttpKernel\HttpCache\HttpCache::__construct() must
be an instance of
Symfony\Component\HttpKernel\HttpCache\StoreInterface, none given,
called in /php/api/current/web/app.php on line 11 and defined in
/php/api/current/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
on line 78
Which makes perfect sense given the HttpCache class constructor.
So the question is, is it the doc that's not up to date, or is it just me missing something really obvious?

use Symfony\Component\HttpKernel\HttpCache\Store;
$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$store = new Store('app/cache');
$kernel = new AppCache($kernel, $store);

Related

Invalid JSON in Tests Authentifications PHPUnit

Good morning all , I did a complete migration of my symfony 2.8 application to version 5.4. I am now at the unit testing stage. I copied the tests to my new project, however I'm having some difficulties with the API authentication. I launched the unit tests without modification with the classic configuration of rest bundle. In my tests, I test the authentication of a user upstream in order to recover the rights necessary to test the different endpoints. When I want to authenticate with _username, and _password, I get the following error in my response content:
<!-- Invalid JSON. (400 Bad Request) -->
Here is the content of my authUser function of my abstract class which allows to authenticate the user :
<?php
namespace WORD\UserBundle\Tests\Model;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Client;
abstract class AbstractAuthTestCase extends WebTestCase
{
/**
* #var Client
*/
protected $client = null;
/**
* ContainerInterface
*/
//protected $container;
// public function setUp(): void
// {
// self::ensureKernelShutdown();
// $this->client = static::createClient();
// // $this->container = $this->client->getContainer();
// $this->client->setServerParameter('HTTPS', true);
// $this->client->setServerParameter('HTTP_HOST', self::$container->getParameter('api_host'));
// $this->client->setServerParameter('HTTP_CONTENT_TYPE', 'application/json');
// }
public function authUser($username='stack.fr', $password='overflow', $referer='https://stack.over.local/')
{
$this->newClient();
$this->client->setServerParameter('HTTP_REFERER', $referer);
$this->client->request('POST', '/api/login_check', array(
'_username' => $username,
'_password' => $password,
));
$response = json_decode($this->client->getResponse()->getContent(), true);
if (isset($response['token'])) {
$this->client->setServerParameter('HTTP_AUTHORIZATION', 'Bearer ' . $response['token']);
}
return $response;
}
private function newClient()
{
$this->client = static::createClient();
//$this->container = $this->client->getContainer();
$this->client->setServerParameter('HTTPS', true);
$this->client->setServerParameter('HTTP_HOST', self::$container->getParameter('api_host'));
$this->client->setServerParameter('HTTP_CONTENT_TYPE', 'application/json');
}
}
Could you tell me if I forgot something please? I'm working on Symfony 5.4 with the FOS/RESTBundle 3.4
According to the documentation, this test method is still functional on symfony. 5 version. Thank you for your help
I finally found the solution to my problem.
I had to change the format of the query:
$this->client->request(
'POST',
'/api/login_check',
[],
[],
['CONTENT_TYPE' => 'application/json'],
'{"_username":"'.$username.'","_password": "'.$password.'" }'
);

Sharing the same instance of an object: auryn vs. PHP-DI

I am trying to build my first no-framework PHP application and I am following this tutorial.
I am relatively new to some concepts described in the tutorial. Despite this, I decided to use, as Dependency Injector, PHP-DI instead of the suggested one (rdlowrey/auryn).
I have created everything according to the tutorial except for the file Bootstrap.php (and the file Dependencies.php:
<?php declare(strict_types = 1);
require(__DIR__ . '/../vendor/autoload.php');
...
$container = include('Dependencies.php');
$request = $container->make('Http\HttpRequest');
$response = $container->make('Http\HttpResponse');
...
switch ($routeInfo[0]) {
...
case \FastRoute\Dispatcher::FOUND:
$className = $routeInfo[1][0];
$method = $routeInfo[1][1];
$vars = $routeInfo[2];
$class = $container->make($className);
$class->$method($vars); // (**)
break;
}
echo $response->getContent(); // (*)
$class can be only an instance of a Homepage class which has only one method (show()), called in (**):
class Homepage
{
private $request;
private $response;
private $renderer;
public function __construct(
Request $request,
Response $response,
Renderer $renderer
) {
$this->request = $request;
$this->response = $response;
$this->renderer = $renderer;
}
public function show() {
$data = [
'name' => $this->request->getParameter('name', 'stranger'),
];
$html = $this->renderer->render('Homepage', $data);
$this->response->setContent($html); // (***)
}
}
With all that said, the application returns a 200 HTTP response with an empty body [here (*)]
but if I try to print the content of the HTTP response after (***) I get the correct response.
This could mean that there are two different instances of an HttpResponse class. (Is that right?)
By using rdlowrey/auryn, the author of the tutorial, used the method share() to share the same HttpReponse instance among classes, as shown in the "original" Dependencies.php file:
<?php declare(strict_types = 1);
use \Auryn\Injector;
...
$injector = new Injector;
$injector->alias('Http\Response', 'Http\HttpResponse');
$injector->share('Http\HttpResponse');
...
return $injector;
Is there a way to get the same behavior using PHP-DI (with PHP definitions)?
Here's my version of Dependencies.php:
<?php declare(strict_types = 1);
$definitions = [
'Http\Request' => DI\create('Http\HttpRequest')->constructor(
$_GET, $_POST, $_COOKIE, $_FILES, $_SERVER),
'Http\HttpRequest' => function () {
$r = new Http\HttpRequest($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
return $r;
},
'Http\Response' => DI\create('Http\HttpResponse'),
'Twig\Environment' => function () {
$loader = new Twig\Loader\FilesystemLoader(
dirname(__DIR__) . '/templates');
$twig = new Twig\Environment($loader);
return $twig;
},
'Example\Template\TwigRenderer' => function (Twig\Environment $renderer) {
return new Example\Template\TwigRenderer($renderer);
},
'Example\Template\Renderer' => DI\create(
'Example\Template\TwigRenderer')->constructor(
DI\get('Twig\Environment')),
];
$containerBuilder = new DI\ContainerBuilder;
$containerBuilder->addDefinitions($definitions);
$container = $containerBuilder->build();
return $container;
In Bootstrap.php, getting (get()) HttpRequest/HttpResponse instances, instead of making (make()) them, solved the problem.
...
$container = include('Dependencies.php');
$request = $container->get('Http\HttpRequest');
$response = $container->get('Http\HttpResponse');
...
As clearly stated in the documentation:
The make() method works like get() except it will resolve the entry
every time it is called. [..] if the entry is an object, an new instance will be created every time [..]

__construct function not constructing correctly in the container symfony 3.4

Hello all i got a a report-bundle and a service AdminUsersStatsListBlockService that need a UserRepository.php within the report bundle to access function, i tried to add the BookingBundle.php within the report bundle to the construct function but i keep constructing without it here's my code and my errors:
HERE AdminUsersStatsListBlockService.php (so i tried to add the BookingRepository here):
<?php
/*
* This file is part of the Cocorico package.
*
* (c) Cocolabs SAS <contact#cocolabs.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Cocorico\ReportBundle\Block\Service;
use Cocorico\BookingBundle\Entity\Booking;
use Cocorico\ReportBundle\Repository\UserRepository;
use Cocorico\ReportBundle\Repository\BookingRepository; /*(added)*/
use Sonata\AdminBundle\Admin\Pool;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Block\Service\AbstractBlockService;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Validator\ErrorElement;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AdminUsersStatsListBlockService extends AbstractBlockService
{
protected $userRepository;
protected $bookingRepository;/*(added)*/
protected $adminPool;
/**
* #param string $name
* #param EngineInterface $templating
* #param UserRepository $userRepository
* #param Pool $adminPool
* #param BookingRepository $bookingRepository/*(added)*/
*/
public function __construct(
$name,
EngineInterface $templating,
UserRepository $userRepository,
Pool $adminPool = null,
BookingRepository $bookingRepository/*(added)*/
) {
parent::__construct($name, $templating);
$this->userRepository = $userRepository;
$this->bookingRepository = $bookingRepository;/*(added)*/
$this->adminPool = $adminPool;
}
/**
* {#inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
$stat = $blockContext->getSetting('stat');
switch ($stat) {
case 'offerers-expiring':
$results = $this->userRepository->getTopOfferersWithBookingsStatusCount(
Booking::STATUS_EXPIRED,
null,
null,
$blockContext->getSetting('limit')
);
break;
case 'offerers-refusing':
$results = $this->userRepository->getTopOfferersWithBookingsStatusCount(
Booking::STATUS_REFUSED,
null,
null,
$blockContext->getSetting('limit')
);
break;
case 'offerers-accepting':
$results = $this->userRepository->getTopOfferersWithBookingsStatusCount(
Booking::STATUS_PAYED,
null,
null,
$blockContext->getSetting('limit')
);
break;
case 'bookings-expired-list':
$results = $this->bookingRepository->getBookingsExpired(); /*there i want to use it*/
break;
default:
$results = array();
}
return $this->renderResponse(
$blockContext->getTemplate(),
array(
'block' => $blockContext->getBlock(),
'settings' => $blockContext->getSettings(),
'results' => $results,
'admin_pool' => $this->adminPool,
),
$response
);
}
}
Here's the error i get:
request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\FatalThrowableError: "Type error: Too few arguments to function Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService::__construct(), 4 passed in /var/www/Symfony/var/cache/prod/Container7aqlalh/getCocoricoReport_Admin_Block_Users_StatsListService.php on line 13 and exactly 5 expected" at /var/www/Symfony/vendor/cocorico/report-bundle/Block/Service/AdminUsersStatsListBlockService.php line 40 {"exception":"[object] (Symfony\Component\Debug\Exception\FatalThrowableError(code: 0): Type error: Too few arguments to function Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService::__construct(), 4 passed in /var/www/Symfony/var/cache/prod/Container7aqlalh/getCocoricoReport_Admin_Block_Users_StatsListService.php on line 13 and exactly 5 expected at /var/www/Symfony/vendor/cocorico/report-bundle/Block/Service/AdminUsersStatsListBlockService.php:40)"} []
and the container still not constructing with the BookingRepository:
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the public 'cocorico_report.admin.block.users.stats_list' shared service.
include_once $this->targetDirs[3].'/vendor/sonata-project/block-bundle/src/Block/BlockServiceInterface.php';
include_once $this->targetDirs[3].'/vendor/sonata-project/block-bundle/src/Block/Service/BlockServiceInterface.php';
include_once $this->targetDirs[3].'/vendor/sonata-project/block-bundle/src/Block/Service/AbstractBlockService.php';
include_once $this->targetDirs[3].'/vendor/cocorico/report-bundle/Block/Service/AdminUsersStatsListBlockService.php';
return $this->services['cocorico_report.admin.block.users.stats_list'] = new \Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService('cocorico_report.admin.block.users.stats_list', ${($_ = isset($this->services['templating']) ? $this->services['templating'] : $this->load('getTemplatingService.php')) && false ?: '_'}, ${($_ = isset($this->services['cocorico_report.user.repository']) ? $this->services['cocorico_report.user.repository'] : $this->load('getCocoricoReport_User_RepositoryService.php')) && false ?: '_'}, ${($_ = isset($this->services['sonata.admin.pool']) ? $this->services['sonata.admin.pool'] : $this->getSonata_Admin_PoolService()) && false ?: '_'});
Edit: found this, is this the loader ? :
services:
cocorico_report.admin.block.stats:
class: Cocorico\ReportBundle\Block\Service\AdminStatsBlockService
arguments:
- "cocorico_report.admin.block.stats"
- "#templating"
- "#cocorico_report.report.manager"
tags:
- { name: sonata.block }
cocorico_report.admin.block.users.stats_list:
class: Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService
arguments:
- "cocorico_report.admin.block.users.stats_list"
- "#templating"
- "#cocorico_report.user.repository"
- "#sonata.admin.pool"
tags:
- { name: sonata.block }
Thanks in advance for help!:)
So all you need to do is to add the booking repository to the service definition.
# ReportBundle/Resources/services.yml
services:
cocorico_report.admin.block.users.stats_list:
class: Cocorico\ReportBundle\Block\Service\AdminUsersStatsListBlockService
arguments:
- "cocorico_report.admin.block.users.stats_list"
- "#templating"
- "#cocorico_report.user.repository"
- "#sonata.admin.pool"
- "#cocorico_report.booking.repository" # ADD THIS #
tags:
- { name: sonata.block }
The assumption here is that you also have the cocorico_report.booking.repository service already defined. If the repository is something you added then you will have to find the user repository service definition and basically clone it.
There is a lot more information available on how to explicitly configure services . Just try to avoid confusing it with the autowire approach.

Is this data being overwritten by another component?

I'm doing some programming in Silex with the symfony components and I think I have found a bug with the symfony/serializer and the symfony/validator components.
First let me explain what I'm traing to achieve, then let's go to the code.
My objective is to annotate a class with information like serialization directives as well as validation directives. As the reading of these annotations can cost a litle cpu, I like to cache them in memory. For this purpose, I'm using memcache wrapper in the Doctrine/Common/Cache package.
The problem I face is that both the symfony/serializer and the symfony/validator write Metadata to the cache using the class name as key. When they try to retrieve the metadata later, they throw an exception, because the cache has invalid metadata, either an instance of Symfony\Component\Validator\Mapping\ClassMetadata or Symfony\Component\Serializer\Mapping\ClassMetadataInterface.
Following is a reproductible example (sorry if its big, I tried to make as small as possible):
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class Foo
{
/**
* #var int
* #Assert\NotBlank(message="This field cannot be empty")
*/
private $someProperty;
/**
* #return int
* #Groups({"some_group"})
*/
public function getSomeProperty() {
return $this->someProperty;
}
}
use Doctrine\Common\Annotations\AnnotationReader;
use \Memcache as MemcachePHP;
use Doctrine\Common\Cache\MemcacheCache as MemcacheWrapper;
$loader = require_once __DIR__ . '/../vendor/autoload.php';
\Doctrine\Common\Annotations\AnnotationRegistry::registerLoader([$loader, 'loadClass']);
$memcache = new MemcachePHP();
if (! $memcache->connect('localhost', '11211')) {
throw new \Exception('Unable to connect to memcache server');
}
$cacheDriver = new MemcacheWrapper();
$cacheDriver->setMemcache($memcache);
$app = new \Silex\Application();
$app->register(new Silex\Provider\SerializerServiceProvider());
$app['serializer.normalizers'] = function () use ($app, $cacheDriver) {
$classMetadataFactory = new Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory(
new Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader(new AnnotationReader()), $cacheDriver);
return [new Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer($classMetadataFactory) ];
};
$app->register(new Silex\Provider\ValidatorServiceProvider(), [
'validator.mapping.class_metadata_factory' =>
new \Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory(
new \Symfony\Component\Validator\Mapping\Loader\AnnotationLoader(new AnnotationReader()),
new \Symfony\Component\Validator\Mapping\Cache\DoctrineCache($cacheDriver)
)
]);
$app->get('/', function(\Silex\Application $app) {
$foo = new Foo();
$app['validator']->validate($foo);
$json = $app['serializer']->serialize($foo, 'json');
return new \Symfony\Component\HttpFoundation\JsonResponse($json, \Symfony\Component\HttpFoundation\Response::HTTP_OK, [], true);
});
$app->error(function (\Exception $e, \Symfony\Component\HttpFoundation\Request $request, $code) {
return new \Symfony\Component\HttpFoundation\Response('We are sorry, but something went terribly wrong.' . $e->getMessage());
});
$app->run();
After running this example you get fatal errors.
Can anyone confirm that I'm not making a hard mistake here?
Currently my workaround for this is rewrite the DoctrineCache class making use of a namespace for the cache keys. Its working, but I think its ugly.
I think what you need to do is two separate CacheDrivers. See https://github.com/doctrine/cache/blob/master/lib/Doctrine/Common/Cache/CacheProvider.php for how namespaces are used there.
You could:
$validatorCacheDriver = new MemcacheWrapper();
$validatorCacheDriver->setMemcache($memcache);
$validatorCacheDriver->setNamespace('symfony_validator');
$serializerCacheDriver = new MemcacheWrapper();
$serializerCacheDriver->setMemcache($memcache);
$serializerCacheDriver->setNamespace('symfony_serializer');
// note that the two drivers are using the same memcache instance,
// so only one connection will be used.
$app['serializer.normalizers'] = function () use ($app, $serializerCacheDriver) {
$classMetadataFactory = new Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory(
new Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader(new AnnotationReader()), $serializerCacheDriver);
return [new Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer($classMetadataFactory) ];
};
$app->register(new Silex\Provider\ValidatorServiceProvider(), [
'validator.mapping.class_metadata_factory' =>
new \Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory(
new \Symfony\Component\Validator\Mapping\Loader\AnnotationLoader(new AnnotationReader()),
new \Symfony\Component\Validator\Mapping\Cache\DoctrineCache($validatorCacheDriver)
)
]);
I've trimmed the code to only show the parts that play some part in the solution. I hope this helps!

Symfony2 : get current directory error

When I try to get the current directory with :
$this->container->getParameter('kernel.root_dir').'/../web/
I've got this error : Fatal error: Using $this when not in object context in C:\XXX on line 124
Code :
class AdminController {
/**
* Add event controller.
*
* #param Request $request Incoming request
* #param Application $app Silex application
*/
public function addEventAction(Request $request, Application $app) {
$event = new Event();
$types= $app['dao.type']->findAllSelectList();
$eventForm = $app['form.factory']->create(new EventType($types), $event);
$eventForm->handleRequest($request);
if ($eventForm->isSubmitted() && $eventForm->isValid()) {
var_dump($event->getCoverImageLink());
$file = $event->getCoverImageLink();
$fileName = md5(uniqid()).'.'.$file->guessExtension();
var_dump($fileName);
//$path = $this->container->getParameter('kernel.root_dir').'/../web';//$this->get('kernel')->getRootDir() . '/../web';
var_dump($this);
$app['dao.event']->save($event);
$app['session']->getFlashBag()->add('success', 'The event was successfully created.');
}
return $app['twig']->render('event_form.html.twig', array(
'title' => 'New event',
'eventForm' => $eventForm->createView()));
}
How to fix this error please? What is the correct function to use?
It appears that you are using Silex, not Symfony 2. Being a very minimalistic framework, silex doesn't give you all the configuration and dependency injection goodies that Symfony does.
The easiest approach to be able to retrieve the application root, would be to define it yourself in bootstrap.php. Simply add something like this at the top:
define('APP_ROOT', __DIR__ . '/../');
Now you can just use the constant in your controller:
public function addEventAction(Request $request, Application $app) {
...
$path = APP_ROOT . '/../web';
...
}

Categories