I'm working on a Symfony 2.8.6 application and I tried to configure my Monolog as described here and here.
This is my Monolog config (bundle version 2.11.1):
monolog:
handlers:
main_critical:
type: fingers_crossed
action_level: error
handler: grouped
channels: ["!doctrine", "!event", "!php"]
excluded_404s:
- ^/
grouped:
type: group
members: [streamed, crash, buffered]
streamed:
type: rotating_file
max_files: 10
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: notice
crash:
type: rotating_file
max_files: 10
path: "%kernel.logs_dir%/%kernel.environment%.critical.log"
level: critical
buffered:
type: buffer
handler: swift
swift:
type: swift_mailer
from_email: noreply#xxxxxxx.com
to_email: user#xxxxx.com
subject: "[App] - Errore %kernel.environment%"
level: error
Monolog logs every 404 error, also missing assets like css and js.
How can avoid this?
Where I'm wrong?
Maybe the problem is related with my ExceptionListener?
/**
* #param GetResponseForExceptionEvent $event
*/
public function onKernelException(GetResponseForExceptionEvent $event)
{
//The logger object is my logger
$this->logger->log($event->getException(), $event->getRequest());
if (!$event->getRequest()->isXmlHttpRequest()) {
return;
}
$event->setResponse(new JsonResponse('message' => 'Ops. Houston we have a problem.'));
}
Thank you.
I post how I resolved. The problem isn't related with Monolog, but with my logger service.
Because I wrapped how to log exceptions, before I didn't pass the exception when I call
$logger->error('message error');
So when Monolog tries to enable the NotFoundActivationStrategy (file is located in /vendor/symfony/monolog-bundle/NotFoundActivationStrategy.php), this check fails:
public function isHandlerActivated(array $record)
{
$isActivated = parent::isHandlerActivated($record);
if (
$isActivated
&& $this->request
&& isset($record['context']['exception'])
&& $record['context']['exception'] instanceof HttpException
&& $record['context']['exception']->getStatusCode() == 404
) {
//blacklist is the pattern that you can set on config.yml at excluded_404s node
return !preg_match($this->blacklist, $this->request->getPathInfo());
}
return $isActivated;
}
So I resolved with
$this->logger->error('message', array('exception' => $exception));
Hope this helps someone.
You can filter errors in onKernelException method. See example below:
<?php
namespace AppBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class AppExceptionListener
{
private $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if(!$exception instanceof NotFoundHttpException)
{
$this->logger->info('Error');
}
}
}
Related
I need to write APItests for a project(on Symfony) using Codeception. My API test is functional and using Symfony+REST modules.
The Api method I'm testing uses an external service call. I just stub the response of the class method, but the stub does not work and the real class method is called.
What do I need to fix in my test code or maybe I need to use another way?
Config codeception.yml
namespace: App\Tests
paths:
tests: tests
output: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
actor_suffix: Tester
bootstrap: codeception_bootstrap.php
extensions:
enabled:
- Codeception\Extension\RunFailed
params:
- .env.test
Config api.suite.yml
actor: ApiTester
modules:
enabled:
- Symfony:
app_path: 'src'
environment: 'test'
- Doctrine2:
depends: Symfony
cleanup: true
- REST:
url: /api/v1/
depends: Symfony
- \App\Tests\Helper\Api
Test code /tests/api/TestCest.php
namespace App\Tests\Api;
use App\Tests\ApiTester;
class TestApiCest extends BaseApiCest
{
public function addMetrics(ApiTester $I)
{
$this->mockB2bService($I);
$I->sendPost('/request', $body);
$I->seeResponseCodeIs(201);
}
}
Code for stubing
/tests/_support/Helper/ApiHelper.php
namespace App\Tests\Helper;
use Codeception\Module;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ApiHelper extends Module
{
public function getContainer(): ContainerInterface
{
/** #var Module\Symfony $symfony */
$symfony = $this->getModule('Symfony');
return $symfony->kernel->getContainer();
}
}
/api/BaseApiCest.php
namespace App\Tests\Api;
use App\Module\Service\B2BService\B2bResponse;
use App\Tests\ApiTester;
use App\Tests\Helper\ApiHelper;
use Codeception\Stub;
abstract class BaseApiCest
{
protected ApiHelper $apiHelper;
protected function _inject(ApiHelper $apiUser)
{
$this->apiHelper = $apiUser;
}
protected function mockB2bService(ApiTester $I): void
{
$container = $this->apiHelper->getContainer();
$serviceId = '\App\Module\Service\B2BService\B2bService';
$auth = Stub::make(
\App\Module\Service\B2BService\B2bService::class, [
'createSomeActive' => new B2bResponse(['success' => true, 'message' => '', 'code' => 200])
]);
$container->set($serviceId, $auth);
$I->persistPermanentService($serviceId);
}
Class I try to stub
/src/Module/Service/B2bService.php
class B2bService implements B2bServiceInterface
{
public function createSomeActive(SomeParams $params): B2bResponse
{
$response = $this->httpClient->post('/somerequest', $requestBody);
$decodedResponse = $this->jsonDecodeResponse($response);
return $decodedResponse;
//if sucsess then return ['success' => true, 'message' => '', 'code' => 200]
}
}
I've decided to install "gedmo/doctrine-extensions" on Symfony to use Translatable.
It works fine, except that listener is ignoring default locale I've specified, always falling back to en_US.
Translatable is plugged in as service:
#config.yml
services:
gedmo.listener.translatable:
class: Gedmo\Translatable\TranslatableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
- [ setDefaultLocale, [ ru ] ]
- [ setTranslationFallback, [ true ] ]
And when I try to find() object in database it always fetches en_US instead of ru:
$test = $em->find('Vendor\Entity\Test', 1);
//outputs row with 'locale' = "en_US" from `ext_translations_test` table
Only if I specify language directly, like:
$test->setTranslatableLocale('ru');
$em->refresh($test);
It gives desired translation.
Is there any way to set default locale that will work?
UPDATE
Adding another call function in config.yml fixed the problem, altough now I'm not quite sure what setDefaultLocale() function actually does, as Gedmo\Translatable\TranslatableListener::$defaultLocale property provided with a most horrid commentary I've ever seen. Will try to find more...
services:
gedmo.listener.translatable:
class: Gedmo\Translatable\TranslatableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
- [ setDefaultLocale, [ ru ] ]
- [ setTranslatableLocale, [ ru ] ]
- [ setTranslationFallback, [ true ] ]
According to: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/symfony2.md
Note: if you noticed, there's Acme\DemoBundle\Listener\DoctrineExtensionListener you will need to create this listener class if you use loggable or translatable behaviors. This listener will set the locale used from request and username to loggable. So, to finish the setup create Acme\DemoBundle\Listener\DoctrineExtensionListener
Make sure you have setup the kernel listener as well.
namespace Acme\DemoBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DoctrineExtensionListener implements ContainerAwareInterface
{
/**
* #var ContainerInterface
*/
protected $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function onLateKernelRequest(GetResponseEvent $event)
{
$translatable = $this->container->get('gedmo.listener.translatable');
$translatable->setTranslatableLocale($event->getRequest()->getLocale());
}
public function onKernelRequest(GetResponseEvent $event)
{
$securityContext = $this->container->get('security.context', ContainerInterface::NULL_ON_INVALID_REFERENCE);
if (null !== $securityContext && null !== $securityContext->getToken() && $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$loggable = $this->container->get('gedmo.listener.loggable');
$loggable->setUsername($securityContext->getToken()->getUsername());
}
}
}
And add the following to your config file:
services:
extension.listener:
class: Acme\DemoBundle\Listener\DoctrineExtensionListener
calls:
- [ setContainer, [ #service_container ] ]
tags:
# translatable sets locale after router processing
- { name: kernel.event_listener, event: kernel.request, method: onLateKernelRequest, priority: -10 }
# loggable hooks user username if one is in security context
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
types:
product:
mappings:
title: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
status:
brand.name: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
brand:
type: "nested"
properties:
status: ~
persistence:
driver: orm
model: MyBundle\Entity\Product\Product
provider:
query_builder_method: customProductQueryBuilderElastica
listener: ~
finder: ~
This is my mappings for type product. customProductQueryBuilderElastica contains code which populates only products which have active status and have related brand status active. It is working perfectly if i change products from my admin.
what i want to do is when i change my brand status to inactive, all related products should be removed from ES.
For that i have used brand as nested of product and created listener for it as explained here and now i am able to change brand status for every products in my ES automatically but i want to remove such products when brand status sets to inactive. How can this be achieved in better way?.
After many tries. i finally achieved what i want. I am posting my code here and try to help others.
Thanks to #maercky. i have taken reference to his answer which is given here
Here is my config.yml file.
types:
product:
mappings:
title: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
status:
brand.name: { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
brand:
type: "nested"
properties:
status: ~
persistence:
driver: orm
model: XXX\MyBundle\Entity\Product\Product
provider:
query_builder_method: customProductQueryBuilderElastica
listener: ~
finder: ~
This code will go to service.yml
fos_elastica.listener.brand.product:
class: 'XXX\MyBundle\Listener\ElasticaBrandListener'
arguments:
- #fos_elastica.object_persister.search.product
- ['postPersist', 'postUpdate', 'postRemove', 'preRemove']
- #fos_elastica.indexable
calls:
- [ setContainer, [ '#service_container', #fos_elastica.object_persister.search.product ] ]
tags:
- { name: 'doctrine.event_subscriber' }
and finally this is my Listener for Brand
<?php
namespace XXX\MyBundle\Listener;
use FOS\ElasticaBundle\Doctrine\Listener as BaseListener;
use Doctrine\Common\EventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
use XXX\MyBundle\Entity\Supplier\Brand;
use FOS\ElasticaBundle\Persister\ObjectPersister;
class ElasticaBrandListener extends BaseListener
{
/** #var \Symfony\Component\DependencyInjection\ContainerInterface */
private $container;
private $objectPersisterProducts;
public function setContainer(ContainerInterface $container,ObjectPersister $objectPersisterProduct) {
$this->container = $container;
$this->objectPersisterProducts = $objectPersisterProduct;
}
/**
* #param Doctrine\Common\EventArgs $eventArgs
*/
public function postUpdate(EventArgs $eventArgs)
{
/** #var $brand Brand */
$brand = $eventArgs->getEntity();
if ($brand instanceof Brand) {
$this->scheduledForUpdate[] = $brand;
foreach ($brand->getProducts() as $product) {
$brand_status = $brand->getStatus();
$product_status = $product->getStatus();
if($brand_status == 'active' && $product_status == 'active'){
$this->objectPersisterProducts->replaceOne($product);
}else{
$this->objectPersisterProducts->deleteOne($product);
}
}
}
}
}
?>
All this works for me well and so i am contributing this for others.
I'm trying to disable "Soft Deleteable" filter during functional testing and I do it as follow:
First option: tried to disable at tearDown() in my test:
protected function tearDown() {
parent::tearDown();
$entityUser = $this->em->getRepository("UserSecurityBundle:User")->find($this->user->getUser()->getId());
$filter = $this->em->getFilters()->disable('softdeleteable');
$this->em->remove($entityUser);
$this->em->flush();
$this->em->close();
}
Didn't work.
Second option: make a doctrine_test.yml and import in config_test.yml:
imports:
- { resource: config.yml }
- { resource: doctrine_test.yml }
- { resource: security_test.yml }
In this case I remove the doctrine.yml and include in config_prod.yml.
Didn't work.
This is how my doctrine_test.yml section look like:
filters:
softdeleteable:
class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
enabled: false
Third option: disable the filter in setUp():
public function setUp() {
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()->get('doctrine')->getManager();
$fixture = new LoadFeeData();
$fixture->load($this->em);
$this->em->getFilters()->disable('softdeleteable');
$this->user = $this->createUser();
parent::setUp();
}
Didn't work.
Any advice? Solution?
So after some research, after doing more test I found the solution, see below:
protected function tearDown() {
parent::tearDown();
$entityAccount = $this->em->getRepository("UserSecurityBundle:Account")->find(array("id" => $this->user->getId(), "user" => $this->user->getUser()->getId()));
$entityUser = $entityAccount->getUser();
$entityCompanyHasWtax = $this->em->getRepository("ApprovalBundle:CompanyHasWtax")->findOneBy(array("company" => $this->company, "wtax" => $this->fee, "user" => $this->user->getUser()->getId()));
foreach ($this->em->getEventManager()->getListeners() as $eventName => $listeners) {
foreach ($listeners as $listener) {
if ($listener instanceof \Gedmo\SoftDeleteable\SoftDeleteableListener) {
$this->em->getEventManager()->removeEventListener($eventName, $listener);
}
}
}
$this->em->remove($entityCompanyHasWtax);
$this->em->remove($entityAccount);
$this->em->remove($entityUser);
$this->em->flush();
$this->em->close();
}
Aparently Doctrine has a bug since disabling the filter in this way:
$this->em->getFilters()->disable('softdeleteable');
Doesn't work, good look for others
Although this question is a bit old maybe it is useful to someone:
Creating your own event listener might be a better solution:
class SoftDeleteableListener extends BaseSoftDeleteableListener
{
/**
* #inheritdoc
*/
public function onFlush(EventArgs $args)
{
$ea = $this->getEventAdapter($args);
$om = $ea->getObjectManager();
//return from event listener if you disabled filter: $em->getFilters()->disable('softdeleteable');
if (!$om->getFilters()->isEnabled('softdeleteable')) {
return;
}
parent::onFlush($args);
}
}
And adding in your config:
gedmo.listener.softdeleteable:
class: AppBundle\EventListener\SoftDeleteableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
source: https://github.com/Atlantic18/DoctrineExtensions/issues/1175
I've created the forms using Sonata Admin Bundle. Then I've created my own Controller (TestController) and override the CRUD controller,
I've added a new function in the TestController,
namespace IFI2\MainProjectBundle\Controller;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Bridge\Monolog\Logger;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
//use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class TestController extends Controller
{
public function getProductPricesAction() {
file_put_contents("/Applications/XAMPP/htdocs/IFI2 CMS/Logs.txt","HELO",FILE_APPEND);
return new Response(json_encode($response));
}
}
Then I'm trying to access this function via my javascript Code,
<script type="text/javascript">
function test1() {
$.ajax({
type:"POST",
//dataType: "json",
url: '{{ path('main_project.admin.test')}}',
success: function(successMsg) {
alert("successMsg");
},
error: function(errorMsg) {
alert("errorMsg");
}
});
}
</script>
Here's my routing.yml,
main_project.admin.test:
pattern: /getProductPrices/
defaults: { _controller: IFI2MainProjectBundle:Test:getProductPrices}
I've already had services.yml entry for this entity,
main_project.admin.cobrand:
class: MainProjectBundle\Admin\TestAdmin
arguments: [~, MainProjectBundle\Entity\Test, "MainProjectBundle:Test"]
tags:
- {name: sonata.admin, manager_type: orm, group: admin, label: Test}
calls:
- [setTemplate, [edit, MainProjectBundle:Test:edit.html.twig]]
I'm getting the following error in my response,
There is no _sonata_admin defined for the controller MainProjectBundle\Controller\TestController and the current route main_project.admin.test
Kindly, help me how to embed it ?
Thanks,
Faisal Nasir
Add new route in your Admin method configureRoutes
protected function configureRoutes(RouteCollection $collection)
{
parent::configureRoutes($collection);
$collection->add('get_product_prices');
}
Remove your route main_project.admin.test
New route has $baseRouteName from your admin as prefix and has name:
base_route_name_get_product_prices
using
{{ path('base_route_name_get_product_prices') }}
//or with admin
{{ admin.generateUrl('get_product_prices') }}
In routing.yml add the following:
main_project.admin.test:
pattern: /getProductPrices/
defaults: { _controller: IFI2MainProjectBundle:Test:getProductPrices,"_sonata_admin": "main_project.admin.cobrand" }