add service to my parent of BaseController - php

as you know controllers in symfony extends AbstractController i want to add some usual methods in base controller and simply use in my method in controller like $this->validate for example so i created a BaseController that extends AbstractController . the issue is how can i use my ValidationService in BaseController? i have to add construct in BaseController and so again pass parent Constructor. another solution is get my ValidationService in BaseController like $this->container->get("my.validation.service") that gives me error like this :
Service "my.validation.service" not found: even though it exists in the app's container, the container inside "App\Controller\VendorController" is a smaller service locator that only knows about the "form.factory", "http_kernel", "parameter_bag", "request_stack", "router", "security.authorization_checker", "security.csrf.token_manager", "security.token_storage", "serializer" and "twig" services. Try using dependency injection instead.
what is the best practice for this?

It sounds like you should be using constructor injection and standard PHP inheritance here (this uses property promotion from php 8)
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\ValidationService;
class MyCustomBaseController extends AbstractController
{
public function __construct(private readonly ValidationService $validationService)
{
}
}
namespace App\Controller;
use App\Controller\MyCustomBaseController;
class MyNormalController extends MyCustomBaseController
{
public function foo()
{
$this->validationService->foo();
}
}

Related

Overriding Default FOSUserBundle Controller in Symfony 3.4

I'm using the Fosuserbundle to manager members in my project { SF::3.4.8 },
when trying to override the controller of the registrationController by following the Symfony documentation
<?php
namespace TestUserBundle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use FOSUserBundle\Controller\RegistrationController as BaseController;
class RegistrationController extends BaseController {
public function registerAction(Request $request)
{
die("Hello");
}
}
but the system ignore that controller and still use The original controller, so if there any way to override my controller by
First, overriding the controller is probably not the best way to process. You should consider to hook into controller. Here is the related documentation: https://symfony.com/doc/master/bundles/FOSUserBundle/controller_events.html
Then if you still want to override the controller, you should act in the dependency injection. The service name of the controller is fos_user.registration.controller.
To replace the service you can simply use:
services:
fos_user.registration.controller:
class: YourController
arguments:
$eventDispatcher: '#event_dispatcher'
$formFactory: '#fos_user.registration.form.factory'
$userManager: '#fos_user.user_manager'
$tokenStorage: 'security.token_storage'
You can also override it in a CompilerPass. Which is probably the best solution for you because you do this inside another bundle.
Here is how it should look:
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ReplaceRegistrationController extends CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$container
->getDefinition('fos_user.registration.controller')
->setClass(YourController::class)
;
}
}
Don't forget to register it inside your bundle:
$container->addCompilerPass(new ReplaceRegistrationController());

Resolving abstract class' dependencies via dependency injection in Laravel

I encountered this issue using the repository pattern. Currently I use an interface, and a custom class to achieve it, then type-hint it into the controller's construct and because of Laravel, it will solve the repositories' dependencies automatically and recursively.
I also do this in a service provider:
$this->app->bind(path/to/repoInterface,path/to/implementationClass)
However, because of the way I coded these repositories, in order to avoid code duplication, I created an abstract class that has a common method to all these repositories. This class is as follows:
abstract class CommonRepo{
public function __construct(SomeModelClass model){}
public function commonMethod(){//Code here}
And my repositories have the following structure:
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI{
public function __construct(){
parent::__construct();
}
}
Laravel doesn't like this, so its giving this error:
Argument 1 passed to path/to/repo/CommonRepo::__construct() must be an instance of path/to/model/SomeModelClass, none given, called in...
So, obviously is not resolving the dependency of the class CommonRepo, but it does resolve the dependencies on the normal repositories.
I'd like, if it's possible, to use type-hinting (the Laravel way) without having to do anything related to the new operator
How can I, then, resolve that class's dependencies ?
PD: Using Laravel 5.2
Parent constructor is called like normal function without touching dependency resolver so you should do one of two possibilities:
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI
{
public function __construct(SomeModelClass $model){
parent::__construct($model);
}
}
or
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI
{
public function __construct(){
parent::__construct(App::make(SomeModelClass::class));
}
}
nice question. I did some tinkering, though I don't know if this is what you're looking for. But you can dynamically create an instance of Eloquent model required by your repository class.
Let's say you have your User model class stored in app\Models\User.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
//
}
You then create a base abstract class for all of your repository classes: app\Repositories\BaseRepository.php. This is where you place all common functionalities for your repository classes. But rather than injecting the Eloquent instance through the constructor, you may add a method named getModel() to dynamically create an instance of Eloquent model for your repository.
<?php
namespace App\Repositories;
use ReflectionClass;
use RuntimeException;
use Illuminate\Support\Str;
abstract class BaseRepository
{
protected $modelNamespace = 'App\\Models\\';
public function getById($id)
{
return $this->getModel()->find($id);
}
public function getModel()
{
$repositoryClassName = (new ReflectionClass($this))->getShortName();
$modelRepositoryClassName = $this->modelNamespace . Str::replaceLast('Repository', '', $repositoryClassName);
if (! class_exists($modelRepositoryClassName)) {
throw new RuntimeException("Class {$modelRepositoryClassName} does not exists.");
}
return new $modelRepositoryClassName;
}
}
Now let's say you want to create a repository for your User model, and this user's repository must implement the following interface: app\Repositories\UserRepositoryInterface.php
<?php
namespace App\Repositories;
interface UserRepositoryInterface
{
public function getByEmail($email);
}
You create app\Repositories\UserRepository.php class and simply extend it from the BaseRepository class. Also don't forget to implement all specific implementations defined on UserRepositoryInterface.
<?php
namespace App\Repositories;
use App\Repositories\BaseRepository;
use App\Repositories\UserRepositoryInterface;
class UserRepository extends BaseRepository implements UserRepositoryInterface
{
public function getByEmail($email)
{
return $this->getModel()->where('email', $email)->firstOrFail();
}
}
This way you can bind the UserRepositoryInterface to it's implementation like so:
$this->app->bind(\App\Repositories\UserRepositoryInterface::class, \App\Repositories\UserRepository::class);
Finally you can freely inject the UserRepositoryInterface to a controller's constructor or methods. You can also resolve it via service container like this:
$userRepository = App::make(App\Repositories\UserRepositoryInterface::class);
$userRepository->getByEmail('john#example.com');
Of course there's a catch to this approach. The repository class should be started with the associated model, so the InvoiceRepository.php is dedicated for Invoice.php model class.
Hope this help!
This might help. You can listen in for when an object resolves and set attributes.
$this->app->resolving(CommonRepo::class, function ($object, $app) {
// Called when container resolves object of any type...
$object->commonObject = app(CommonObject::class);
});
Docs: https://laravel.com/docs/5.4/container#container-events

Accessing container outside of controller but not able to inject a service

Due to the way I use certain classes I'm not able to do service injection, but I need to have access to the service container in them.
namespace DocumentsUploadSystem;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
class DocumentsUpload implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function getData()
{
var_dump($this->container);
}
}
If I call this method from controller like so.
use DocumentsUploadSystem\DocumentsUpload
...
$x = new DocumentsUpload($request)->getData();
and I get no errors but the container comes back as null. Is this a wrong way to implementing ContainerAwareInterface? I'm using Symfony 3.1
But in this example [https://symfony.com/doc/current/bundles/KnpMenuBundle/index.html] they use the same technique and access container fine, it works there, is the service called else where for that?
// src/AppBundle/Menu/Builder.php
namespace AppBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
class Builder implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function mainMenu(FactoryInterface $factory, array $options)
{
$em = $this->container->get('doctrine')->getManager();
}
}
Implementing an Interface doesn't do anything. An Interface is a contract between the builder of the class and the user of the class guaranteeing the existence of certain methods - that is all. In this case, the ContainerAwareInterface guarantees that the setContainer() method is available. You then must USE it in order to set your container.
// MyController extends Symfony\Bundle\FrameworkBundle\Controller\Controller
use DocumentsUploadSystem\DocumentsUpload
...
$x = new DocumentsUpload($request);
$x->setContainer($this->container);
$xData = $x->getData();
For the record, I don't recommend injecting the entire container - only the actual services you require to accomplish your task.
Libraries like KnpMenuBundle appear to be using this but in reality, they are automating the creation of the menu and injects the container for you. see https://github.com/KnpLabs/KnpMenuBundle/blob/master/Provider/BuilderAliasProvider.php#L122-L124
best wishes!

Error extending Laravel's abstract TestCase class (error is saying that my extended class must be abstract)

I'm hoping somebody out there can help me. I am using laravel 4 and I'm writing my first unit tests for a while but am running into trouble. I'm trying to extend the TestCase class but I'm getting the following error:
PHP Fatal error: Class registrationTest contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Illuminate\Foundation\Testing\TestCase::createApplication) in /home/john/www/projects/MyPainChart.com/app/tests/registrationTest.php on line 4
Now if I have this right then the error is referring to the fact that is a method is abstract then the class it's in must also be abstract. As you can see from below the TestCase class it is abstract. I have searched for this error but have drawn a blank.
Trying to follow this cast on Laracasts https://laracasts.com/lessons/tdd-by-example and although you have to be a subscriber to watch the video the file is underneath it and as you can see I am doing nothing different to Jeffrey Way.
My Test:
<?php
class registrationTests extends \Illuminate\Foundation\Testing\TestCase
{
/**
* Make sure the registration page loads
* #test
*/
public function make_sure_the_registration_page_loads_ok()
{
$this->assertTrue(true);
}
}
The beginning of the TestCase class:
<?php namespace Illuminate\Foundation\Testing;
use Illuminate\View\View;
use Illuminate\Auth\UserInterface;
abstract class TestCase extends \PHPUnit_Framework_TestCase {
By the way - the Laravel testing class is not autoloaded by default and so I have tried both the fully qualified class name and use Illuminate\Foundation\Testing and then just extending TestCase. I know it can see it aswhen I don't fully qualify the name it complains that the class cannot be found. I've also tried:
composer dump-autoload
and
composer update
Any help appreciated
According to your error message: Class registrationTest contains 1 abstract method the Base Class contains an abstract method and when a Child Class extends another class with abstract methods then the child class should implement the abstract methods available in Base class. So, registrationTest is child class and \Illuminate\Foundation\Testing\TestCase is the base class and it contains an abstract method:
An abstract method in \Illuminate\Foundation\Testing\TestCase:
abstract public function createApplication();
So, in your child class/registrationTest you must implement this method:
public function createApplication(){
//...
}
But, actually you don't need to directly extend the \Illuminate\Foundation\Testing\TestCase because in app/tests folder there is a class TestCase/TestCase.php and you can extend this class instead:
// In app/tests folder
class registrationTest extends TestCase {
//...
}
The TestCase.php class looks like this:
class TestCase extends Illuminate\Foundation\Testing\TestCase {
public function createApplication()
{
$unitTesting = true;
$testEnvironment = 'testing';
return require __DIR__.'/../../bootstrap/start.php';
}
}
Notice that, TestCase has implemented that abstract method createApplication so you don't need to extend it in your class. You should use TestCase class as base class to create test cases. So, create your tests in app/tests folder and create classes like:
class registrationTest extends TestCase {
public function testBasicExample()
{
$crawler = $this->client->request('GET', '/');
$this->assertTrue($this->client->getResponse()->isOk());
}
}
Read Class Abstraction.
Firstly go in composer.json and add
"scripts" : {"test" : "vendor/bin/phpunit"
}
Then run composer update
Then
The TestCase.php class in path /test/ should look like
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;
abstract class TestCase extends BaseTestCase {
use CreatesApplication;
}
Then your registrationTests class should look like this
<?php
namespace Tests\Feature;
use Tests\TestCase;
class registrationTests extends TestCase {}
Just intake dependancy at the top of your class as follows and you are good to go,
<?php
namespace YOUR_CLASS_PATH;
use Tests\TestCase;
class UserTest extends TestCase{
...//your business logic here
}
I hope this works.

Symfony2 dependecy injection doesn't work with controller

According to Symfony2 Cookbook I'm trying to secure controller via dependecy injection, but I'm getting error Catchable Fatal Error: Argument 1 passed to Acme\ExampleBundle\Controller\DefaultController::__construct() must implement interface Symfony\Component\Security\Core\SecurityContextInterface, none given, called in /var/www/example/app/cache/dev/classes.php on line 4706 and defined in /var/www/example/src/Acme/ExampleBundle/Controller/DefaultController.php line 13
Here is my services.yml
parameters:
acme_example.default.class: Acme\ExampleBundle\Controller\DefaultController
services:
acme_example.default:
class: %acme_example.default.class%
arguments: [#security.context]
and controller:
namespace Acme\ExampleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class DefaultController extends Controller {
public function __construct(SecurityContextInterface $securityContext)
{
if(false === $securityContext->isGranted('IS_AUTHENTICATED_FULLY'))
{
throw new AccessDeniedException();
}
}
public function indexAction()
{
return new Response('OK');
}
}
If you configure your controllers as services you need to use a slightly different syntax when referencing them in your routes. Instead of AcmeExampleBundle:Default:index you should use acme_example.default:indexAction.
Make sure you use Symfony\Component\Security\Core\SecurityContextInterface; in your controller. Without it, the SecurityContextInterface type hint in the constructor won't resolve.
Also, make sure your controller is actually being called as a service. The error you posted is complaining that nothing was sent to the constructor, which sounds to me like you're using your controller the 'default' way. See this cookbook page on how to setup a controller as a service.
The class Symfony\Bundle\FrameworkBundle\Controller\Controller extends ContainerAware base class. This class ha whole the container accessible via $container local property, so you should not inject any services to a controller service, because you can access SecurityContext via $this->container->get('security.context').

Categories