Taking the example from https://symfony.com/doc/current/event_dispatcher.html
class ExceptionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
// return the subscribed events, their methods and priorities
return [
KernelEvents::EXCEPTION => [
['processException', 10],
['logException', 0],
['notifyException', -10],
],
];
}
}
Is it correct to assume that this list can be changed during runtime?
E.g.
class ExceptionSubscriber implements EventSubscriberInterface
{
protected $someToggle = false;
public static function getSubscribedEvents()
{
if ($this->someToggle) {
return [KernelEvents::EXCEPTION => ['processException']]
}
return [
KernelEvents::EXCEPTION => [
['processException', 10],
['logException', 0],
['notifyException', -10],
],
]
}
}
Is this legit and unsubscribes logException and notifyException when I set $someToggle during runtime?
No, you cannot change dynamically what events a subscriber listen to by adding logic to the getSubscribedEvents():array method.
That method is run only during a compiler pass when the container is being built, so it will only be executed after cache is cleared.
Trying to change this at runtime will have no effect.
The practical way of doing this is to put this logic into the "work" part of the listener/subscriber:
public function processException(ExceptionEvent $event)
{
if (!$this->shouldProcessException()) {
return;
}
}
The performance hit would be very small or negligible, unless getting the value for shouldProcessException() was otherwise expensive.
Related
I'm working with Symfony and EasyAdmin.
Here is my render :
And here is the Indication code :
<?php
namespace App\Controller\Admin;
use App\Entity\Indication;
use Doctrine\ORM\EntityManagerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\ColorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class IndicationCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Indication::class;
}
public function configureFields(string $pageName): iterable
{
return [
AssociationField::new('deficiencies', 'Déficience')
->formatValue(function ($value, $entity) {
return implode(", ",$entity->getDeficiencies()->toArray());
})
->setTemplatePath('admin/associationTemplate.html.twig')
->setRequired(true),
AssociationField::new('underActivities', 'Sous-activité')
->formatValue(function ($value, $entity) {
return implode(", ",$entity->getUnderActivities()->toArray());
})
->setTemplatePath('admin/associationTemplate.html.twig')
->setRequired(true),
ChoiceField::new('indicationPreAdjusting', 'Indice pré-aménagement')
->setChoices([
'Vert' => 'Green',
'Orange' => 'Orange',
'Rouge' => 'Red',
])
->onlyOnForms(),
ColorField::new('indicationPreAdjusting', 'Indice pré-aménagement')->hideOnForm(),
AssociationField::new('adjustings', 'Aménagement')
->formatValue(function ($value, $entity) {
return implode(", ",$entity->getAdjustings()->toArray());
})
->setTemplatePath('admin/associationTemplate.html.twig'),
ChoiceField::new('indicationPostAdjusting', 'Indice post-aménagement')
->setChoices([
'Vert' => 'Green',
'Orange' => 'Orange',
'Rouge' => 'Red',
])
->onlyOnForms(),
ColorField::new('indicationPostAdjusting', 'Indice post-aménagement')->hideOnForm(),
];
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setSearchFields(['deficiencies.name', 'underActivities.name'])
->setEntityLabelInSingular('Indication')
->setEntityLabelInPlural('Indications');
}
}
I want if "adjustings" ("Aménagement", the penultimate column) is null, put the "indicationPostAdjusting" on null. Because now, I can put an indicationPostAdjusting even if there is no adjusting(s).
I know there is some solutions with EasyAdmin like createEntity, persistEntity and updateEntity, but I don't know how to use it in my case, do you have any ideas ?
Noé
-- EDIT --
I'm creating an EasyAdminSubscriber like :
<?php
namespace App\EventSubscriber;
use App\Entity\Indication;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class EasyAdminSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
BeforeEntityPersistedEvent::class => ['setIndicationPostAdjustingOnNullIfNoAdjustings'],
];
}
public function setIndicationPostAdjustingOnNullIfNoAdjustings(BeforeEntityPersistedEvent $event)
{
$entity = $event->getEntityInstance();
if (!($entity instanceof Indication)) {
return;
}
dump($entity->getAdjustings());
}
}
If Adjustings is empty, I got this :
Else :
My new question is : How can I check if my array "elements" is empty or not ?
How can I make it pls ?
Not sure to understand what you are trying to achieve but sound like something Event can do, like AfterEntityUpdatedEvent, in easyadmin. It's a function which will trigger each time you update a specific entity.
I am working on a Symfony 3.4 based web app project which uses JMSSerializer to serialize different custom classes to JSON to send this data to mobile apps.
How can I serialize/deserialize a custom class to/from to int?
Assume we have the following classes:
<?php
// AppBundle/Entity/...
class NotificationInfo {
public $date; // DateTime
public $priority; // Int 1-10
public $repeates; // Boolean
public function toInt() {
// Create a simple Int value
// date = 04/27/2020
// priority = 5
// repeats = true
// ==> int value = 4272020 5 1 = 427202051
}
public function __construnct($intValue) {
// ==> Split int value into date, priority and repeats...
}
}
class ToDoItem {
public $title;
public $tags;
public $notificationInfo;
}
// AppBundle/Resources/config/serializer/Entiy.ToDoItem.yml
AppBundle\Entity\ToDoItem:
exclusion_policy: ALL
properties:
title:
type: string
expose: true
tags:
type: string
expose: true
notificationInfo:
type: integer
expose: true
So the class NotificationInfo also has function to create it from int and to serialize it to in. How to tell the serializer that it should serialize the value of $notificationInfo to int?
I could use the following instead:
notificationInfo:
type: AppBundle\Entity\NotificationInfo
expose: true
However in this case I need to configure the serialization of NotificationInfo where I can only specify which property should serialized to which value...
EDIT:
This is the JSON I would like to create:
{
"title": "Something ToDO",
"tags": "some,tags",
"notificationInfo": 427202051
}
This is what I am NOT looking for:
{
"title": "Something ToDO",
"tags": "some,tags",
"notificationInfo": {
"intValue": 427202051
}
}
After a lot more digging I found the following solution for my problem: I added a custom serialization Handler which tells JMSSerializer how to handle my custom class:
class NotificationInfoHandler implements SubscribingHandlerInterface {
public static function getSubscribingMethods() {
return [
[
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => 'NotificationInfo',
'method' => 'serializeNotificationInfoToJson',
],
[
'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
'format' => 'json',
'type' => 'NotificationInfo',
'method' => 'deserializeNotificationInfoToJson',
],
;
public function serializeNotificationInfoToJson(JsonSerializationVisitor $visitor, NotificationInfo $info, array $type, Context $context) {
return $info->toInt();
}
public function deserializeNotificationInfoToJson(JsonDeserializationVisitor $visitor, $infoAsInt, array $type, Context $context) {
return (is_int($infoAsInt) ? NotificationInfo::fromInt($infoAsInt) : NotificationInfo::emptyInfo());
}
}
Thanks to autowire the handler is automatically added and can be used in the serializer metadata:
notificationInfo:
type: NotificationInfo
expose: true
you can use VirtualProperty method to add any method of you class
into json response
use JMS\Serializer\Annotation as Serializer;
class NotificationInfo
{
/**
* #return int
* #Serializer\VirtualProperty()
* #Serializer\SerializedName("formatedLocation")
*/
public function toInt()
{
return 4272020;
}
}
I'm trying to write a test for the following method:
/**
* #dataProvider attributesValuesProvider
*/
public function myFunction($entityObject, $diffArr, $prevArr)
{
....
....
if (is_a($entityObject, Customer::class)) {
$entityType = CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER;
} elseif (is_a($entityObject, Address::class)) {
$entityType = AddressMetadataInterface::ENTITY_TYPE_ADDRESS;
} else {
$entityType = null;
}
....
....
return $entityType;
}
I have defined the following data provider:
public function attributesValuesProvider()
{
return [
[null, [], []],
[Customer::class, [], []],
[Address::class, [], []],
];
}
I've twisted this on all sides and I still can't think of a way to write this test. I don't have relevant experience with unit tests so I might be on a wrong path.
Your data provider needs to provide the expected result as well as the method parameters. You can see a simple example in the PHPUnit documentation.
public function attributesValuesProvider()
{
return [
[null, [], [], null],
[new Customer, [], [], CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER],
[new Address, [], [], AddressMetadataInterface::ENTITY_TYPE_ADDRESS],
];
}
The test that uses the data provider will be executed once for each row in the provider, with all the values in the row passed as its arguments. So your test just needs to take all four arguments, call the method and verify that the expected result was returned.
/**
* #dataProvider attributesValuesProvider
*/
public function testMyFunction($object, $diff, $prev, $expected_result) {
$example = new YourClass();
// or maybe you already created this object in your setUp method?
$actual_result = $example->myFunction($object, $diff, $prev);
$this->assertSame($expected_result, $actual_result);
}
I'm trying to write tests for a custom module I've written on Drupal 8 and keep getting an error and at this point I'm out of ideas. Here is the error:
Error: Class 'Drupal\mypackage\Services\Config\MyClassServiceConfig' not found
The PhpUnit class is under
modules\custom\mypackage\tests\src\Unit\mypackageUserAuthTest
Here is the code
class mypackageUserAuthTest extends UnitTestCase
{
protected $user;
protected $loginService;
public function setUp()
{
parent::setUp();
$this->loginService = new LoginService();
$this->user = [
'username' => 'xxx',
'password' => 'xxx',
'deviceId' => 'xxx',
'some-token' => 'xxx'
];
}
/** #test */
public function that_we_can_authenticate_a_user()
{
$IsUserLoggedIn = $this->loginService->login($this->user['username'], $this->user['password']);
$this->assertTrue($IsUserLoggedIn);
}
Now the method login in loginService code
<?php
namespace Drupal\mypackage\Rest;
use GuzzleHttp\Exception\ClientException;
use Drupal\mypackage\Services\RestServiceFactory;
use Drupal\mypackage\Services\Config\MyClassServiceConfig;
class LoginService
{
public function login($username, $password)
{
$configs = new MyClassServiceConfig(null, "mobile", "v1");
$client = RestServiceFactory::create($configs);
try {
$response = $client->post('login', [
'json' => [
'username' => $username,
'password' => $password,
'deviceId' => 'onepiece',
],
]);
return json_decode($response->getBody(), true);
} catch (ClientException $exception) {
switch ($$exception->getResponse()->getStatusCode()) {
case 402: // This only applies to co members
throw new SubscriptionRequiredException();
case 403:
throw new BlockedAccountException();
case 409:
throw new DuplicateEmailException();
case 410:
throw new PasswordDoesNotExistException();
}
throw $exception;
}
}
}
pwd result on MyClassServiceConfig class directory
/var/www/cms/web/modules/custom/mypackage/src/Services/Config
But it seems to fail on the line $configs = new MyClassServiceConfig(null, "mobile", "v1"); with the previously mentioned error :
1) Drupal\Tests\mypackage\Unit\mypackageUserAuthTest::that_we_can_authenticate_a_user
Error: Class 'Drupal\mypackage\Services\Config\MyClassServiceConfig' not found
Btw, I'm using drupal-project structure (https://github.com/drupal-composer/drupal-project)
So I spent days checking the path but it seemed that the files were not loading so I ended up adding the custom module to autload-dev part composer.json.
"autoload": {
"classmap": [
"scripts/composer/ScriptHandler.php"
],
"files": ["load.environment.php"]
},
"autoload-dev": {
"psr-4": { "Drupal\\mypackage\\": "web/modules/custom/mypackage" }
},
Now at least it seems to load the module as I'm getting an other error related to Drupal Container
\Drupal::$container is not initialized yet. \Drupal::setContainer() must be called with a real container.
It is an old question, the same thing happened to me, as I managed to solve it in my case it was as follows:
In the comment of the class where the tests are carried out, something similar to this should go:
The #coversDefaultClass annotation must go with the namespace of the class to test.
/**
* #coversDefaultClass \Drupal\my_module\MyModuleClassName
* #group my_module
*/
class MyModuleCaseTest extends UnitTestCase {
}
Maybe it will serve someone else
I have set routing and display the page according to user roles. For this i am using guard on route. I am extracting userRole from service in Appcomponent class and using set and get method in main-service file. Now problem is that before i get role, routing takes place and it navigate to wrong url as it doesn't have role by then. Tough from next call, it works properly. Let me share the code:-
1.Here is guard class:-
export class HomeGuard implements CanActivate {
constructor(private _router: Router,private mainService: MainService) {
}
canActivate(): boolean {
let userRoles:any;
alert('HomeGuard');
userRoles = this.mainService.getSavedUserRole();
//userRoles = ['Profile Manager','Operations','Shipper'];
alert('userRoles are here'+userRoles);
console.log('here in homeguard');
if(userRoles) {
if(userRoles.some(x => x === 'Shipper') || userRoles.some(x => x === 'Admin'))
return true;
}
this._router.navigate(['/notfound']);
return false;
}
}
Here is AppComponent where i am extracting userRole from service:-
export class AppComponent {
savedUserRoles:any;
constructor(private translate: TranslateService,private mainService: MainService) {
console.log('Environment config', Config);
// this language will be used as a fallback when a translation isn't found in the current language
translate.setDefaultLang(AppSettings.LNG_TYPE);
// the lang to use, if the lang isn't available, it will use the current loader to get them
translate.use(AppSettings.LNG_TYPE);
this.mainService.getCurrentUser().subscribe(result => {
this.savedUserRoles = JSON.parse(JSON.parse(result._body).Data).Roles;
console.log('sdfghj'+this.savedUserRoles);
this.mainService.setSavedUserRole(this.savedUserRoles);
});
}
}
Here is main-service where i have defined set and get method:-
setSavedUserRole(name: any) {
console.log('main'+name);
this._userRoles = name;
}
getSavedUserRole() {
return this._userRoles;
}