I got this problem I have a method that is repetitive in all the Repositories, for example this method.
function getAllOrderedBy($column) {
$qb = $this->createQueryBuilder('ac')
->select('ac')
->orderBy('ac.' . $column);
return $qb->getQuery()->getResult();
}
I want to extract it in another superclass, OrderedRepository for example and use it as the base class for all the other repositories.
Now the problem is how to do that ?
I tried to instantiate EntityRepository in the constructor of the OrderedRepository,
something like this, but also instantiating there all the internal objects, needed for other stuff, but it didn't really worked, and I felt it is the wrong path to follow.
function __construct() {
parent::__construct();
$this->blabla_option = "instantiated";
}
Could you please give an example of correct extending of EntityRepository so than this extended class could serve as a base class for other repositories ?
P.S. I'm a begginer in PHP so please excuse me if I hurt your feelings with my unawareness.
This is more a Doctrine2 thing.
Assuming you are using annotations for your doctrine mapping, you have to declare which repository class you are using in the Entity:
/**
* #ORM\Entity(repositoryClass="Fully\Qualified\Namespace\To\MyRepository")
*/
class MyEntity { }
as explained here: http://symfony.com/doc/2.0/book/doctrine.html#custom-repository-classes .
Then, you can code this custom MyRepository class, using standard class inheritance.
You could imagine something like that:
class OrderedRepository extends EntityRepository
{
// some extra methods...
}
class MyRepository extends OrderedRespository {}
Finally, if you want to override the __constructor of your repository, you have to initailize the parent constructor with the same arguments:
public function __construct($em, Mapping\ClassMetadata $class)
{
parent::__construct($em, $class);
// some extra stuff
}
Related
The Problem
I have a lot of classes that are children of a parent class.
The factory that I'm using to instantiate these children takes one parameter, a string that's the class name of the child so the factory can know what to build.
The Hope
I would really like to be able to solve autowiring for all of these children in one fell swoop. I know in services.yaml I can define autowiring configuration for classes recursively via a wildcard resource e.g.:
...
Namespace\:
resource: '..namespace/foo/*'
# configuration for all classes in the 'foo' directory
autowire: true
public: true
However, for my directory of children that need to be instantiated with a factory method that accepts their class name this doesn't seem apparently possible.
What I would love to be able to do is something akin to this where "$1" is equivalent to the string that matched the wildcard like many regex-based solutions allow for (including some available through Symfony itself, like routes):
...
Domain\Children\:
resource: '../domain/children/*'
factory: ['Domain\Factory', 'getChild']
arguments: 'Domain\Children\$1'
However, this understandably does not work.
I know there are some dynamic solutions in the form of "expressions", and !tagged_iterator (seen here), so I suspect there may be something out there that can do what I'm looking for because the documentation on these two examples are fairly scattered. I haven't been able to find a way to dynamically get the name of a class as part of it's service definition, hoping it exists but I just can't find it.
Another thing I tried was giving the child class a function that would instantiate it as expected, accepting a parameter of the factory class, but I couldn't find a way to define a relative function to be used in place of a factory. It seems the only way to use anything other than the class's constructor is to explicitly declare a class and function pair.
Clarifications
I am not accepting any answers that involve refactoring how these classes are being built in relation to the factory I'm referring to here. This question is not "was this design a good idea", we're well past that -- this is the design I'm stuck with.
Question Summary
Is there any way to accomplish my goal of dynamically referring to the name of the class (not with an explicit string in the yaml) when defining generic autowiring configurations?
I don't know how to do this sort of thing using yaml but a compile pass is easy enough and probably more understandable. I developed this with a new Symfony 6 app but it should work fine on earlier versions. No changes at all to the default services.yaml file.
Start with the domain objects just to be sure we are on the same page.
namespace App\Domain;
abstract class MyParent {}
class Child1 extends MyParent {}
class Child2 extends MyParent {}
class MyFactory
{
public function getChild(string $childClass) : MyParent
{
// Just ignore the 'something' for now
return new $childClass('something');
}
}
At this point if you run bin/console debug:container Child1 you will see there is a Child1 service but it's not using MyFactory.
Now we add the compile pass:
# src/Kernel.php
# implement CompilerPassInterface
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
// Tag all the child classes
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(MyParent::class)
->addTag('my.child');
}
public function process(ContainerBuilder $container)
{
$factoryReference = new Reference(MyFactory::class);
// Grab all the child services
$childClasses = array_keys($container->findTaggedServiceIds('my.child'));
foreach ($childClasses as $childClass) {
// And replace with a factory service
$definition = new Definition($childClass);
$definition->setFactory([$factoryReference,'getChild'])
->setArgument(0,$childClass);
$container->setDefinition($childClass,$definition);
}
//dump($childClasses);
}
}
Now when we check the Child1 service we can see that it calls MyFactory::getChild like we want. I made a command to test injection:
class ChildCommand extends Command
{
public function __construct(private Child1 $child1, private Child2 $child2)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->success('Child1 ' . get_class($this->child1));
$io->success('Child2 ' . get_class($this->child2));
return Command::SUCCESS;
}
}
And it all seems to work as expected.
I was a little bit worried about constructor arguments. I'm assuming your child classes might have some and I was not sure if autowire would cause issues. So I added one:
class Child2 extends MyParent
{
public function __construct(private string $something)
{
}
}
And once again it all seemed to work. Of course it will be up to to your factory class to inject any necessary dependencies.
Enjoy
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
I currently have an abstract class which i am extending to other controllers. I have a abstract function within the abstract class which takes the value and places it in the __construct.
abstract class Controller extends BaseController {
abstract public function something();
public function __construct(Request $request) {
if (!is_null($this->something())){
$this->global_constructor_usse = $this->something();
}
}
}
My problem is that, on controllers that don't require this abstract function, I am having to place in the empty function.
class ControllerExample extends Controller {
public function something(){
return 'somethinghere';
}
}
Is there anyway to making the abstract function optional, or have a default value?
class EmptyControllerExample extends Controller {
public function something(){}
}
It is not possible to have a abstract method optional, as it is implied in PHP that all abstract methods must have an implementation.
There are legit use cases for optional abstract methods, yes: event handlers, metadata describers, etc. Unfortunately, you'll need to use regular, non-abstract methods with an empty body, and indicate in PHPDoc that they will do nothing unless extended.
Be wary, though: this can very quickly turn into code smell by diffusing a class responsability with their children. If you're dealing with generic events, you can look into Laravel's own event system, or the Observer pattern instead.
Abstract functions in a parent class, should only be used if its required by your application to implement the following method in all controllers who inherits from it, clearly it is not the case.
In this case i would make a trait. Here you create a trait which can be implemented by the classes who needs it. Notice the use keyword usage, use somethingTrait;
trait SomethingTrait
{
public function something()
{
echo "something called";
}
}
class Controller
{
use SomethingTrait;
public function run()
{
$this->something();
}
}
phpfiddle link
Another aproach could be doing a class inheritance structure, if the controllers you want to implement the methods has something in common. Where you would implement your special method in CrmController, where you still would be able to create shared methods in the abstract controller.
AbstractController
|
CrmController
|
CompanyController
For your question, 'Is there anyway to making the abstract function optional or have a default value?' No, and you are down the wrong path if you are trying to make abstract function optional. Hope my suggestions can help.
I'm trying to inject the service manager into a controller.
Actual Error:
\vendor\zendframework\zend-servicemanager\src\Exception\ServiceLocatorUsageException.php:34
Service "Project\Service\ProjectServiceInterface" has been requested to plugin manager of type "Zend\Mvc\Controller\ControllerManager", but couldn't be retrieved.
A previous exception of type "Zend\ServiceManager\Exception\ServiceNotFoundException" has been raised in the process.
By the way, a service with the name "Project\Service\ProjectServiceInterface" has been found in the parent service locator "Zend\ServiceManager\ServiceManager": did you forget to use $parentLocator = $serviceLocator->getServiceLocator() in your factory code?
The process goes:
class BaseController extends AbstractActionController implements ServiceLocatorAwareInterface
{
public function __construct(\Zend\ServiceManager\ServiceLocatorInterface $sl)
{
$this->serviceLocator = $sl;
}
}
Create controller and use constructor method
Extend this BaseController to AdminController
Setup Routes to AdminController => /admin
use Module.php
public function getControllerConfig()
Use closer as factory to create controller object injecting the serviceLocator
'Project\Controller\Project' => function($sm) {
$serviceLocator = $sm->getServiceLocator();
return new \Project\Controller\ProjectController($serviceLocator);
},
try to use $this->getServiceLocator()->get('service_name')
Exception found for missing service.....
Now the problem is this:
/**
*
* #param ServiceLocatorInterface $sl
*/
public function __construct(\Zend\ServiceManager\ServiceLocatorInterface $sl)
{
$rtn = $sl->has('Project\Service\ProjectServiceInterface');
echo '<br />in Constructor: '.__FILE__;var_dump($rtn);
$this->serviceLocator = $sl;
}
public function getServiceLocator()
{
$rtn = $this->serviceLocator->has('Project\Service\ProjectServiceInterface');
echo '<br />in getServiceLocator: '.__FILE__;var_dump($rtn);
return $this->serviceLocator;
}
Within the __constructor() the service IS FOUND. Within the getServiceLocator() method the service with the same name IS NOT FOUND....
in Constructor: Project\Controller\BaseController.php
bool(true)
in getServiceLocator: Project\Controller\BaseController.php
bool(false)
Am I missing something? Is the SharedServiceManager doing something here?
The entire purpose of this exercise was due to this message:
Deprecated: ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. ...
If you really need the ServiceLocator, you have to inject it with a factory
Something like this
Controller:
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\ServiceManager\ServiceLocatorInterface;
class BaseController extends AbstractActionController
{
protected $serviceLocator = null;
public function __construct(ServiceLocatorInterface $serviceLocator)
{
$this->setServiceLocator($serviceLocator);
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
}
Factory:
<?php
namespace Application\Controller\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Controller\BaseController;
class BaseControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator);
{
$controller = new BaseController($serviceLocator->getServicelocator());
return $controller;
}
}
?>
in module.config.php
<?php
// ...
'controllers' => [
'factories' => [
'Application\Controller\BaseController' => 'Application\Controller\Factory\BaseControllerFactory',
// ...
],
// ...
In Zend Framework 2 there are multiple service locators (docs here), one general (mainly used for your own services), one for controllers, one for view helpers, one for validators, ... The specific ones are also called plugin managers.
The error message you are receiving is just telling you that you are using the wrong service locator, the ones that retrieves controllers and not the general one. It is also suggesting you how to solve your problem:
did you forget to use $parentLocator = $serviceLocator->getServiceLocator() in your factory code
What is probably happening (not 100% sure about this) is that in the constructor you are passing in an instance of the general service manager, and everything works fine with it. Then, since the controller implements the ServiceLocatorAwareInterface, the controller service locator is injected into your controller, overriding the one that you defided before.
Moreover, I think that the idea beyound the decision of removing ServiceLocatorAwareInterface in version 3 is that you don't inject the service locator inside your controller, but instead you inject directly the controller dependencies.
You should try to prevent injecting the service manager or service locator in the controller. It would be much better to inject the actual dependencies (in your case 'Project\Service\ProjectServiceInterface') directly into the __construct method of your class. Constructor injection (the dependencies are provided through a class constructor) is considered best practice in ZF2.
This pattern prevents the controller from ever being instantiated without your dependencies (it will throw an error).
If you inject a ServiceLocator or ServiceManager from which you will resolve the actual dependencies in the class, then it is not clear what the class actually needs. You can end up in a class instance with missing dependencies that should never have been created in the first place. You need to do custom checking inside the class to see if the actual dependency is available and throw an error if it is missing. You can prevent writing all this custom code by using the constructor dependency pattern.
Another issue is that it is harder to unit-test your class since you cannot set mocks for your individual dependencies so easily.
Read more on how to inject your dependencies in my answer to a similar question.
UPDATE
About the issue you encountered. Controller classes implement a ServiceLocatorAwareInterface and during construction of your controller classes the ControllerManager injects a ServiceLocator inside the class. This happens here in the injectServiceLocator method at line 208 in ControllerManager.php. Like #marcosh already mentioned in his answer, this might be a different service locator then you injected. In this injectServiceLocator method you also find the deprecation notice you mentioned in your question.
Yours is available in the __construct method because at that time (just after constructing the class) the variable is not yet overwritten. Later when you try to access it in your getServiceLocator method it is overwritten.
This is a follow-up to my previous question about resolving the diamond issue in php.
As I state in that question, I resolve my problem by using traits and passing the instance of the class to the method of the trait. Such as:
trait SecurityTrait
{
public function beforeExecuteRouteTrait($controller, Dispatcher $dispatcher)
{
// Do something that makes use of methods/members of the controller
}
}
class AppController extends Controller
{
use SecurityTrait;
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
return $this->beforeExecuteRouteTrait($this, $dispatcher);
}
}
However, I am still uncomfortable with this as I don't think this is how traits are really supposed to be used. In my reading I haven't found any way in which to access class members in traits (make $this inside a trait refer to the class using it). Is this possible? Or is there another way to implement a similar behaviour?
After reading some of the answers...
Previously I thought I had received errors when using $this->... inside the trait and this led me to believe the trait could not access anything to do with the underlying class. After reading the answers I tried altering my code to use $this->... inside a trait again and it works - which means a typo several weeks ago has given me far too much headache...
The example given previously now looks like this
trait SecurityTrait
{
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
// Do something that makes use of methods/members of the controller
}
}
class AppController extends Controller
{
use SecurityTrait;
}
Much cleaner and more easily understandable but provides the same functionality.
If you use a trait inside a class then that trait has full access to all class's members and vice versa - you can call private trait methods from the class itself.
Think of traits as code that literally gets copy/pasted into the class body.
For example:
trait Helper
{
public function getName()
{
return $this->name;
}
private function getClassName()
{
return get_class($this);
}
}
class Example
{
use Helper;
private $name = 'example';
public function callPrivateMethod()
{
// call a private method on a trait
return $this->getClassName();
}
}
$e = new Example();
print $e->getName(); // results in "example"
print $e->callPrivateMethod(); // results in "Example"
In my view referencing classes in traits is not the best way to use them but there's nothing stopping anyone from doing it.
No, that's exactly what Traits are for. Your class already extends a class so you can't inherit the methods and variables of any other classes.
Think of a Trait like copy/paste for code execution. When a class includes a Trait, it's just as if you had written all that code into the class itself.