Zf 3 Introducing Services and the ServiceManager - php

I'm getting error:
Fatal error: Class Blog\Factory\ListControllerFactory contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Zend\ServiceManager\Factory\FactoryInterface::__invoke) in /d0/home/kgendig/www/Zend/module/Blog/src/Blog/Factory/ListControllerFactory.php on line 28
I'm doing all with
https://framework.zend.com/manual/2.4/en/in-depth-guide/services-and-servicemanager.html
What i have to change, my zend_version(); is 2.6.0
<?php
// Filename: /module/Blog/src/Blog/Factory/ListControllerFactory.php
namespace Blog\Factory;
use Blog\Controller\ListController;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class ListControllerFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
*
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$postService = $realServiceLocator->get('Blog\Service\PostServiceInterface');
return new ListController($postService);
}
}

Your ListControllerFactory class implements the FactoryInterface. So you have to define all abstract functions of this interface in your class. Here, your FactoryInterface needs the __invoke() method (check how you call it), so you have to define it in your ListControllerFactory.
It seems you are mixing ZF2/3 components. In ZF3 FactoryInterface code (see here), you have instructions for upgrading from V2 to V3:
If upgrading from v2, take the following steps:
rename the method createService() to __invoke(), and:
rename the $serviceLocator argument to $container, and change the typehint to Interop\Container\ContainerInterface
add the $requestedName as a second argument
add the optional array $options = null argument as a final argument
create a createService() method as defined in this interface, and have it proxy to __invoke().
This describe how to solve your problem, but maybe there are several similar issues in your code. Try not mixing ZF2.4 and ZF3 component. Don't use dev-master in your composer.json. I suggest you to use only ZF2 component and ZF2 tutorial, or, if you want to learn ZF3, only ZF3 components and the ZF3 tutorial.

Related

How to fix "Cannot autowire service: Argument references class but no such service exists" in Sylius?

In Sylius 1.11, after creating a new Campaign entity using the maker bundle, I get this error when trying to fetch a campaign using its repository:
Cannot autowire service "App\Repository\CampaignRepository": argument "$class" of method "Doctrine\ORM\EntityRepository::__construct()" references class "Doctrine\ORM\Mapping\ClassMetadata" but no such service exists.
This seems to be the code that trigger the error:
<?php
namespace App\Controller;
use App\Repository\CampaignRepository;
class CampaignController extends AbstractController {
protected CampaignRepository $repository;
public function __construct(CampaignRepository $repository) {
$this->repository = $repository;
}
public function details(string $id)
{
$campaign = $this->repository->find($id);
dd($campaign);
}
}
The App\Repository\CampaignRepository exists and is defined as follow, which is what the Sylius documentation recommends:
<?php
namespace App\Repository;
use App\Entity\Campaign;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
/**
* #extends ServiceEntityRepository<Campaign>
*
* #method Campaign|null find($id, $lockMode = null, $lockVersion = null)
* #method Campaign|null findOneBy(array $criteria, array $orderBy = null)
* #method Campaign[] findAll()
* #method Campaign[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class CampaignRepository extends EntityRepository
{
}
How to fix this error?
As a type-hint you can use any of the interfaces that the repository class implements, the base ones that can be used for any resource repository are Sylius\Component\Resource\Repository\RepositoryInterface and Doctrine\Persistence\ObjectRepository. For built-in Sylius resources, there are repository interfaces in the lower-level components and sometimes in the core component, like Sylius\Component\Order\Repository\OrderRepositoryInterface and Sylius\Component\Core\Repository\OrderRepositoryInterface.
Plugins usually also define interfaces.
For custom repositories in your projects, you should also define interfaces if you add custom methods.
The argument name also matters.
To check which type-hints are available, together with the argument name use the command bin/console debug:autowiring campaing.
TL;DR
It turns out Sylius is quite picky on both the type hinting and the variable name. This is due to how Symfony's dependency injection works and how Sylius chose to use it.
Change the constructor to:
public function __construct(
\Sylius\Component\Resource\Repository\RepositoryInterface $campaignRepository
) {
$this->repository = $campaignRepository;
}
How are you supposed to guess that?
You need to use the bin/console debug:container command. But if you try, you'll run into the same error message as when running you controller action.
What you need to do is:
comment any method that use the symfony's dependency injection feature to access an instance of your repository (in our case the App\Controller\CampaignController::__construct() method)
run the bin/console debug:container CampaignRepository
This will return you a list of services:
bin/console debug:container CampaignRepository
Select one of the following services to display its information:
[0] App\Repository\CampaignRepository
[1] Doctrine\Persistence\ObjectRepository $campaignRepository
[2] Doctrine\Common\Collections\Selectable $campaignRepository
[3] Sylius\Component\Resource\Repository\RepositoryInterface $campaignRepository
[4] Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository $campaignRepository
[5] Doctrine\ORM\EntityRepository $campaignRepository
chose a combination of class name & parameter to use as your constructor parameter (I went with Sylius\Component\Resource\Repository\RepositoryInterface $campaignRepository but any of the suggestions with a parameter name should work)
uncomment the method you commented on step 1 & update its signature
voilĂ 
If you're curious how this works, have a look here: https://symfony.com/doc/current/service_container/autowiring.html#dealing-with-multiple-implementations-of-the-same-type
you can add to the file services.yaml
your sercvice here -> the path of your file: autowire: true
or add the arguments manually
your sercvice here -> the path of your file: arguments:[....]

PHPStan doesn't use custom entity repository

I am using PHPStan with its Doctrine extension.
I have a custom entity repository called App\Repository\Doctrine\UserRepository with the #extends doc block:
/**
* #extends \Doctrine\ORM\EntityRepository<\App\Entity\User>
*/
class UserRepository extends EntityRepository implements IUserRepository
{
public function customRepositoryMethod()
{
// ...
}
}
In a controller, this code:
public function getUserMatches(EntityManager $em)
{
$userRepo = $em->getRepository(\App\Entity\User::class);
$userRepo->customRepositoryMethod();
}
...results in this PHPStan error:
Call to an undefined method Doctrine\ORM\EntityRepository<meQ\Entity\User>::customRepositoryMethod().
Thanks to phpstan-doctrine, static analysis knows that $em->getRepository(User::class) returns a EntityRepository<User>.
However, it does not know to consider the custom repository class UserRepository as the implementation of that generic type.
How do I DocBlock the UserRepository class, or otherwise configure PHPStan, so that it interprets UserRepository as the implementation of EntityRepository<User>?
What else I've tried
I've also tried this DocBlock on UserRepository to no avail:
/**
* #implements \Doctrine\ORM\EntityRepository<\App\Entity\User>
*/
PhpStan has no way of knowing EntityManager::getRepository() is going to return your custom method.
Either add a #var annotation right there:
/** #var UserRepository $userRepo */
$userRepo = $em->getRepository(\App\Entity\User::class);
Or better yet, just inject the UserRepository, not the whole EntityManager:
public function getUserMatches(UserRepository $userRepository)
{
$userRepository->customRepositoryMethod();
}
The above would work with PhpStan or any static analysis tool out of the box. (And, injecting the specific repository instead of the Entity Manager is a better practice in any case).
But if not, you can always try installing the Doctrine Extensions for PHPStan, which may help the tool understand the codebase in relationship to Doctrine ORM.
If you are already using the Doctrine Extensions, note that from what I gather in the docs it's only able to extract typing if your mapping configuration is provided via annotations. If you configurate ORM mappings via XML (or YAML, in older versions of Doctrine), then I think the Extensions won't be able to extract the typing data, and the above solutions will be your only way to go.

Interface-based DI configuration in Symfony

In my Symfony project I'll have lot of classes that have similar dependecies (however, the classes are not directly related to each other). For example, most of them requires access to EventBus.
In other framework I was able to specify an interface for the class, for example:
interface EventBusAwareInterface
{
public setEventBus(EventBus $bus);
public getEventBus() : EventBus
}
and then configure DI container to recognize such objects that implements this interface, and call their setEventBus() method with proper argument.
I wonder if there's a method to do the same in Symfony4.
You can use _instanceof directive in your services.yaml like that:
services:
_instanceof:
App\EventBusAwareInterface:
calls:
- method: setEventBus
arguments:
- '#event.bus.service'
My original comment was not quire correct. You can use #inject but it seems to require an additional jms bundle. Could have sworn the container supported it out of the box but I guess not.
However, autowire supports a #required annotation which seems to do the trick.
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
trait RouterTrait
{
/** #var RouterInterface */
protected $router;
/**
* #param RouterInterface $router
* #required
*/
public function setRouter(RouterInterface $router)
{
$this->router = $router;
}
// Copied directly from Symfony ControllerTrait
protected function generateUrl(
string $route,
array $parameters = array(),
int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
{
return $this->router->generate($route, $parameters, $referenceType);
}
protected function redirect($url, $status = 302) : RedirectResponse
{
return new RedirectResponse($url, $status);
}
protected function redirectToRoute($route, array $parameters = array(), $status = 302) : RedirectResponse
{
return $this->redirect($this->generateUrl($route, $parameters), $status);
}
}
Now, any autowired service that uses the RouterTrait will automatically get the router injected.
Yes, something even simpler is very possible. However, I would not encourage over-usage as it can very quickly introduce things like method name collisions and reduce code readability.
That said, Symfony introduced service auto-wiring concept starting with 3.3 (I think), which can be used to have dependency injection with zero-config. In PHP, interfaces cannot contain implementation, but, traits can. So, you could do something like this:
trait FooTraitHandler
{
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
And then:
class RealService
{
use FooTraitHandler;
public function multiply($a, $b)
{
$this->logger->log(LogLevel::ALERT, "Doing some basic math!");
return $a * $b;
}
}
And finally, for example, your controller could inject this RealService service and use multiply method as usual.
So, couple of things worth mentioning:
You do not need pair of getter/setter - trait's member is visible in class utilizing it.
You can utilize many traits, achieving just what you wanted with many interfaces
Finally, if some of utilized traits have methods with same name, you'll get a fatal error. As per official docs:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved. To resolve naming conflicts between Traits used in the same class, the insteadof operator needs to be used to choose exactly one of the conflicting methods.
But, in my opinion, doing so deteriorates code readability substantially.
Hope this helps...

zf2 controller factory serviceLocator

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.

How do I get access to the getServiceLocator in zend 2 from my own library

I have been developing a project in zend 1 but decided to move over to zend 2 to take advantages of things like events etc.
My initial problem is that I can't seem to find any tutorials on how to use models in the way I need to use them.
What I have is an Api controller which is routed to as /api/soap
this soap endpoint loads a class that has all the methods I want to expose via SOAP
namespace MyProject\Controller;
$view = new ViewModel();
$view->setTerminal(true);
$view->setTemplate('index');
$endpoint = new EndpointController();
$server = new Server(
null, array('uri' => 'http://api.infinity-mcm.co.uk/api/soap')
);
$server->setObject($endpoint);
$server->handle();
and my controller that contains all the functions is
namespace MyProject\Controller;
class EndpointController
{
public function addSimpleProducts($products)
{
}
}
Now what I want to be able to do is access my products model from inside this EndpointController.
So i've tried this:
protected function getProductsTable()
{
if (!$this->productsTable) {
$sm = $this->getServiceLocator();
$this->productsTable= $sm->get('MyProject\Model\ProductsTable');
}
return $this->productsTable;
}
When I run this I get the fatal error that EndpointController::getServiceLocator() is undefined.
I am very new to Zend 2 but in Zend 1 it feels like this would be a very minor step in my development and im getting to the point of sacking zend 2 off and going back to zend 1 or even switching to symfony 2 where its simple to use doctrine...
help?
If you want your controller to have access to the ServiceManager, then you need to inject a ServiceManager into it.
Within the MVC system, this happens pretty much automatically for you as the ServiceManager is used to create the instance of the Controller. This is not happening for you as you are creating your EndpointController using new.
You either need to create this controller via the MVC or instantiate and configure your own ServiceManager instance and pass it to EndpointController.
Alternatively, instantiate the dependencies, such as ProductTable and set them into your EndpointController.
To have access to the service locator you have to implement ServiceLocatorAwareInterface
So in any controller that will need this, you can do it like this:
namespace MyProject\Controller;
use Zend\ServiceManager\ServiceLocatorAwareInterface,
Zend\ServiceManager\ServiceLocatorInterface;
class EndpointController implements ServiceLocatorAwareInterface
{
protected $sm;
public function addSimpleProducts($products) {
}
/**
* Set service locator
*
* #param ServiceLocatorInterface $serviceLocator
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->sm = $serviceLocator;
}
/**
* Get service locator
*
* #return ServiceLocatorInterface
*/
public function getServiceLocator() {
return $this->sm;
}
}
Now the service manager will inject itself automagically. You can then use it like:
$someService = $this->sm->getServiceLocator()->get('someService');
If you are using PHP 5.4+ you can import the ServiceLocatorAwareTrait so you don't have to define the getters and setters yourself.
class EndpointController implements ServiceLocatorAwareInterface
{
use Zend\ServiceManager\ServiceLocatorInterface\ServiceLocatorAwareTrait

Categories