How to extend Symfony\Component\HttpFoundation\Request and create my own Request correctly?
I tried to create my own request:
class ChangeRequest extends Request
But, when I used it as argument in controller action, I get an error
Argument #1 ($request) must be of type App\Request\ChangeRequest, Symfony\Component\HttpFoundation\Request given
I want to create several requests extends Symfony\Component\HttpFoundation\Request, and use this requests in different controller actions. Every request contains JSON data and has its own validate rules. Now I use ArgumentValueResolverInterface for resolving this requests.
class RequestResolver implements ArgumentValueResolverInterface
{
/**
* #inheritDoc
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
$type = $argument->getType();
return class_exists($type) && str_starts_with($type, 'App\Request');
}
/**
* #inheritDoc
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield new ($argument->getType())($request);
}
}
After that I edited services.yaml and the error disappeared:
App\Resolver\RequestResolver:
tags:
- { name: controller.argument_value_resolver, priority: 50 }
Is this the right way?
Related
I cannot find the problem using
$routes->resource
Please help me figure out what is the problem.
This is how I put my routes resource in config routes :
$routes->resource('ApiManageBanner', ['controller' =>'App\Controllers\ApiData\ApiManageBanner']); // get, put, create, delete
Recently I just move all my project to the newest codeigniter 4 version 4.2.6 from the previous version 4.1.2
This is my controllers :
<?php
namespace App\Controllers\ApiData;
use App\Controllers\BaseController;
use CodeIgniter\RESTful\ResourceController;
use Codeigniter\API\ResponseTrait;
class ApiManageBanner extends ResourceController
{
use ResponseTrait;
function __construct()
{
}
// equal to get
public function index() {
echo "Test";
}
// equal to post
public function create() {
}
// equal to get
public function show($id = null) {
}
// equal to put
public function update($id = null) {
}
// equal to delete
public function delete($id = null) {
}
}
I just try a simple to echo "Test".
But I got this error :
I search everywhere but cannot find the problem related to the error.
If I change the routes name to 'ApiManageBanners' using 's' :
$routes->resource('ApiManageBanners', ['controller' =>'App\Controllers\ApiData\ApiManageBanner']); // get, put, create, delete
It is working.
But I cannot change my routes name because my application is reading
'ApiManageBanner' not 'ApiManageBanners'
I am very curious what cause the problem. It is not working for almost all my resources api controller routes.
I found the problem. According to the error it is related to session. When I check all my file. I found that I always init the :
$this->Session = \Config\Services::session();
In all my controller and model __construct();
function __construct()
{
$this->Session = \Config\Services::session();
}
So I remove it and init globaly in BaseController.php
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
* Class BaseController
*
* BaseController provides a convenient place for loading components
* and performing functions that are needed by all your controllers.
* Extend this class in any new controllers:
* class Home extends BaseController
*
* For security be sure to declare any new methods as protected or private.
*/
abstract class BaseController extends Controller
{
/**
* Instance of the main Request object.
*
* #var CLIRequest|IncomingRequest
*/
protected $request;
/**
* An array of helpers to be loaded automatically upon
* class instantiation. These helpers will be available
* to all other controllers that extend BaseController.
*
* #var array
*/
protected $helpers = [''];
/**
* Constructor.
*/
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
// Do Not Edit This Line
parent::initController($request, $response, $logger);
// Preload any models, libraries, etc, here.
// E.g.: $this->session = \Config\Services::session();
$this->session = \Config\Services::session();
$this->language = \Config\Services::language();
$this->language->setLocale($this->session->lang);
}
}
Then the error is gone.
I have a models with custom rules of validation, In every model I have variable $rules:
public static $rules = [...];
https://medium.com/#konafets/a-better-place-for-your-validation-rules-in-laravel-f5e3f5b7cc
I want use these rules in custom request:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class ModelRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return Auth::check() ? true : false;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return static::$rules;
}
}
I get error:
Access to undeclared static property: App\Http\Requests\ModelRequest::$rules
In controller:
public function store(ModelRequest $request)
{
...
}
This is globally. I need get instance of model, but how?
If you've put the $rules variable in your model you can't access it like this, because static refers to the class you are currently in.
Try this out:
Notice: I assume that your models are under the App name space
// In your model class
class YourModel extends Model{
const RULES=[];
}
//Then in your request class
class ModelRequest extends FormRequest
{
public function rules()
{
return App\YourModel::RULES;
}
}
i get this error
[enter image description here][1]
i have in my project 2 bundles the first one is working just fine the seconde is called DemandeBundle i get the error
namespace DemandeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DefaultController extends Controller
{
/**
* #return \Symfony\Component\HttpFoundation\Response
* #route("/homedemande",name="homedemande")
*/
public function indexAction()
{
return $this->render('#DemandeBundle/Default/index.html.twig');
}
/**
* #return \Symfony\Component\HttpFoundation\Response
* #route("/demande_create",name="demande_create")
*/
public function demande_create()
{
return $this->render('#DemandeBundle/Default/demande_create.html.twig');
}
}
i suspect i have a problem in routing.yml
demande_create:
path: /demande_create
defaults: { _controller: DemandeBundle:Default:demande_create}
your action needs to be suffixed with Action keyword to be callable that is probably the first issue here
function definition should be something like
public function demande_createAction()
I have a Symfony2 controller as follows:
/**
* #Security("is_granted('my_permission')")
*/
class MyController extends Controller
{
/**
* #Security("is_granted('another_permission')")
*/
public function myAction()
{
// ...
}
}
It appears the #Security annotation on the myAction() method overrides/ignores the parent #Security annotation on the MyController class. Is there any way to make these stack, to avoid having to do:
/**
* #Security("is_granted('my_permission') and is_granted('another_permission')")
*/
public function myAction()
{
// ...
}
on every action method in the controller?
It appears the #Security annotation on the myAction method overrides/ignores the parent #Security annotation on the MyController class.
Indeed, Sensio\Bundle\FrameworkExtraBundle\Configuration\Security annotation doesn't allows nested configuration (see allowArray() method). So method configuration overrides class configuration for #Security annotation.
Is there any way to make these stack...
Not in a simple way, you need create three class and one trick to not reimplement the whole parent code:
Security.php
namespace AppBundle\Configuration;
/**
* #Annotation
*/
class Security extends \Sensio\Bundle\FrameworkExtraBundle\Configuration\Security
{
public function getAliasName()
{
return 'app_security';
}
public function allowArray()
{
// allow nested configuration (class/method).
return true;
}
}
SecurityConfiguration.php
This class allow you compound the final security expression through all security configurations (class/method).
namespace AppBundle\Configuration;
class SecurityConfiguration
{
/**
* #var Security[]
*/
private $configurations;
public function __construct(array $configurations)
{
$this->configurations = $configurations;
}
public function getExpression()
{
$expressions = [];
foreach ($this->configurations as $configuration) {
$expressions[] = $configuration->getExpression();
}
return implode(' and ', $expressions);
}
}
SecurityListener.php
namespace AppBundle\EventListener;
use AppBundle\Configuration\SecurityConfiguration;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SecurityListener extends \Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener
{
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_app_security')) {
return;
}
// trick to simulate one security configuration (all in one class/method).
$request->attributes->set('_security', new SecurityConfiguration($configuration));
parent::onKernelController($event);
}
public static function getSubscribedEvents()
{
// this listener must be called after Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener.
return array(KernelEvents::CONTROLLER => array('onKernelController', -1));
}
}
services.yml
services:
app.security.listener:
class: AppBundle\EventListener\SecurityListener
parent: sensio_framework_extra.security.listener
tags:
- { name: kernel.event_subscriber }
Finally, just use your #AppBundle\Configuration\Security annotation instead the standard one.
Here's my try:
Using, in app/config/security.yml, this role hierarchy:
role_hierarchy:
ROLE_CLASS: ROLE_CLASS
ROLE_METHOD: [ROLE_CLASS, ROLE_METHOD]
And if I have two users: user1 with ROLE_CLASS, and user2 with ROLE_METHOD (which means this user has both roles), then the first user can see all the pages created inside the controller, except the ones that have additional restrictions.
Controller example:
/**
* #Security("is_granted('ROLE_CLASS')")
*/
class SomeController extends Controller
{
/**
* #Route("/page1", name="page1")
* #Security("is_granted('ROLE_METHOD')")
*/
public function page1()
{
return $this->render('default/page1.html.twig');
}
/**
* #Route("/page2", name="page2")
*/
public function page2()
{
return $this->render('default/page2.html.twig');
}
}
So because user1 has ROLE_CLASS, he is able to see just /page2, but not /page1, as he will receive a 403 Expression "is_granted('ROLE_METHOD')" denied access. error (for dev obviously).
On the other hand, user2, having ROLE_METHOD (and ROLE_CLASS), he is able to see both pages.
I was wondering if there is a way to compare old and new values in a validator within an entity prior to a flush.
I have a Server entity which renders to a form fine. The entity has a relationship to status (N->1) which, when the status is changed from Unracked to Racked, needs to check for SSH and FTP access to the server. If access is not achieved, the validator should fail.
I have mapped a validator callback to the method isServerValid() within the Server entity as described here
http://symfony.com/doc/current/reference/constraints/Callback.html. I can obviously access the 'new' values via $this->status, but how can I get the original value?
In pseudo code, something like this:
public function isAuthorValid(ExecutionContextInterface $context)
{
$original = ... ; // get old values
if( $this->status !== $original->status && $this->status === 'Racked' && $original->status === 'Unracked' )
{
// check ftp and ssh connection
// $context->addViolationAt('status', 'Unable to connect etc etc');
}
}
Thanks in advance!
A complete example for Symfony 2.5 (http://symfony.com/doc/current/cookbook/validation/custom_constraint.html)
In this example, the new value for the field "integerField" of the entity "NoDecreasingInteger" must be higher of the stored value.
Creating the constraint:
// src/Acme/AcmeBundle/Validator/Constraints/IncrementOnly.php;
<?php
namespace Acme\AcmeBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class IncrementOnly extends Constraint
{
public $message = 'The new value %new% is least than the old %old%';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
return 'increment_only';
}
}
Creating the constraint validator:
// src/Acme/AcmeBundle/Validator/Constraints/IncrementOnlyValidator.php
<?php
namespace Acme\AcmeBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManager;
class IncrementOnlyValidator extends ConstraintValidator
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function validate($object, Constraint $constraint)
{
$new_value = $object->getIntegerField();
$old_data = $this->em
->getUnitOfWork()
->getOriginalEntityData($object);
// $old_data is empty if we create a new NoDecreasingInteger object.
if (is_array($old_data) and !empty($old_data))
{
$old_value = $old_data['integerField'];
if ($new_value < $old_value)
{
$this->context->buildViolation($constraint->message)
->setParameter("%new%", $new_value)
->setParameter('%old%', $old_value)
->addViolation();
}
}
}
}
Binding the validator to entity:
// src/Acme/AcmeBundle/Resources/config/validator.yml
Acme\AcmeBundle\Entity\NoDecreasingInteger:
constraints:
- Acme\AcmeBundle\Validator\Constraints\IncrementOnly: ~
Injecting the EntityManager to IncrementOnlyValidator:
// src/Acme/AcmeBundle/Resources/config/services.yml
services:
validator.increment_only:
class: Acme\AcmeBundle\Validator\Constraints\IncrementOnlyValidator
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: validator.constraint_validator, alias: increment_only }
Accessing the EntityManager inside a custom validator in symfony2
you could check for the previous value inside your controller action ... but that would not really be a clean solution!
normal form-validation will only access the data bound to the form ... no "previous" data accessible by default.
The callback constraint you're trying to use does not have access to the container or any other service ... therefore you cant easily access the entity-manager (or whatever previous-data provider) to check for the previous value.
What you need is a custom validator on class level. class-level is needed because you need to access the whole object not only a single value if you want to fetch the entity.
The validator itself might look like this:
namespace Vendor\YourBundle\Validation\Constraints;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class StatusValidator extends ConstraintValidator
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function validate($status, Constraint $constraint)
{
$em = $this->container->get('doctrine')->getEntityManager('default');
$previousStatus = $em->getRepository('YourBundle:Status')->findOneBy(array('id' => $status->getId()));
// ... do something with the previous status here
if ( $previousStatus->getValue() != $status->getValue() ) {
$this->context->addViolationAt('whatever', $constraint->message, array(), null);
}
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
return 'previous_value';
}
}
... afterwards register the validator as a service and tag it as validator
services:
validator.previous_value:
class: Vendor\YourBundle\Validation\Constraints\StatusValidator
# example! better inject only the services you need ...
# i.e. ... #doctrine.orm.entity_manager
arguments: [ #service_container ]
tags:
- { name: validator.constraint_validator, alias: previous_value }
finally use the constraint for your status entity ( i.e. using annotations )
use Vendor\YourBundle\Validation\Constraints as MyValidation;
/**
* #MyValidation\StatusValidator
*/
class Status
{
For the record, here is the way to do it with Symfony5.
First, you need to inject your EntityManagerInterface service in the constructor of your validator.
Then, use it to retrieve the original entity.
/** #var EntityManagerInterface */
private $entityManager;
/**
* MyValidator constructor.
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #param string $value
* #param Constraint $constraint
*/
public function validate($value, Constraint $constraint)
{
$originalEntity = $this->entityManager
->getUnitOfWork()
->getOriginalEntityData($this->context->getObject());
// ...
}
Previous answers are perfectly valid, and may fit your use case.
For "simple" use case, it may fill heavy though.
In the case of an entity editable through (only) a form, you can simply add the constraint on the FormBuilder:
<?php
namespace AppBundle\Form\Type;
// ...
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
/**
* Class MyFormType
*/
class MyFormType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fooField', IntegerType::class, [
'constraints' => [
new GreaterThanOrEqual(['value' => $builder->getData()->getFooField()])
]
])
;
}
}
This is valid for any Symfony 2+ version.