How to inject Service to have a common instance to several clasess them in zend 3 - dependency-injection

In Zend 3, I cannot to figure out how should injections work, let me show some simple example.
Let's say, I have
class ToolCollector implements ToolCollectorInterface {
public function __construct(){}
public function registerTool(ToolInterface $tool){
$this->tools[] = $tool;
}
}
and also
class ToolA implements ToolInterface {
public function __construct(ToolCollectorInterface $mainCollector){$mainCollector->registerTool($this);}
}
so what I expect to have
class SomeController extends AbstractActionController {
public function __construct(ToolCollectorInterface $toolCollector){$this->collector=$toolCollector;}
public function indexAction(){
new ToolA; // <- just call Tool and it will put itself in ToolCollector, this is I want
new ToolB; // <- just call Tool and it will put itself in ToolCollector, this is I want
new ToolC; // <- just call Tool and it will put itself in ToolCollector, this is I want
return ViewModel(['toolNameList'=>$this->collector->getRegisteredToolNames();])
}
}
I tried to make it as shown in example, when i call new ToolA it shows me error 500 without any description.
// Next is works
class ToolControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
// Next is not reachable, And I do not know why...
class ToolFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
In module.config.php
return [
...
'service_manager' => [
'aliases' => [
ToolCollectorInterface::class => ToolCollector::class,
],
'factories' => [
Service\ToolCollector::class => InvokableFactory::class,
],
],
'controllers' => [
'factories' => [
Controller\App\SomeController ::class => ToolControllerFactory::class,
]
],
...
];
Is it possible to do it?
How to write factory to make it possilbe?

You must understand something very easy to spot using IoC Inversion of Control. The Container is handling the building process and a reference to the instances you need.
So it is cumbersome and useless, in fact, to make a new inside your indexAction().
You should (and, in fact, you must do it to follow best practices) make any new inside your factory for each dependency your SomeController has.
So you will have many factories. For your Controllers, for your Services, for your Repositories, etc. Each factory has its own logic to build the object you need.
If there is no deps, you can use the default InvokableFactory factory without to write yours. If there is at least one dep, then you must write your own factory with returning your Controller / Service / Repository / etc. class which is built as a ready to use object instance.
The code :
class ToolFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$pm = $container->get(ToolCollector::class);
return new $requestedName($pm);
}
}
I do not see any ToolFactory inside your config, only a ToolControllerFactory :
'controllers' => [
'factories' => [
Controller\App\SomeController ::class => ToolControllerFactory::class,
]
],
So it is normal that is sending you a http 500 error, because it does not know the factory your are trying to use to build your Controller.
Zend Framework / Laminas is very tied to configuration. You must follow principles and then you can build your own path to make the things work. But, much important thing, you must be structured enough to not lose yourself in the forest.
Go step by step without the will to change the world when you are starting to walk. Dreams are necessary. But you have to handle a learning curve firstly, as any of us !

Related

zf2 view helper factory / service locator parameters

I have a view helper that acts as a factory by returning an entity-specific renderer.
I would like the factory to implement the FactoryInterface and MutableCreationOptionsInterface, so i can return different renderers depending on the type of object passed to it, eg:
$serviceLocator->get('entityRenderer', ['entity' => $user]); // returns UserRenderer
$serviceLocator->get('entityRenderer', ['entity' => $admin]); // returns AdminRenderer
$serviceLocator->get('entityRenderer'); // returns DefaultRenderer
However, there is no access to the servicelocator from within a view, and the factory view helper i have created is called using it's __invoke method. This means the type check is occuring here and returning the specific renderer without using the service manager, which is not desirable. eg
class EntityRendererFactory extends AbstractHelper{
public function __invoke(Entity $entity){
if($entity instanceof User){
$renderer = new UserRenderer($entity);
$renderer->setView($this->view);
}
if($entity instanceof Admin){
$renderer = new AdminRenderer($entity);
$renderer->setView($this->view);
}
if($renderer){
return $renderer;
}
}
}
Note how this "factory" is having to extend AbstractHelper (view) simply just to pass on the instance of the current view.
My "ideal" would be something like this (proof of concept, not working code):
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\MutableCreationOptionsInterface;
class EntityRendererFactory implements FactoryInterface, MutableCreationOptionsInterface{
protected $options = [];
protected $renderers = [];
public function createService(ServiceLocatorInterface $serviceLocator){
$this->addRenderer($serviceLocator->get('ViewHelperManager')->get('UserRenderer'), User::class);
$this->addRenderer($serviceLocator->get('ViewHelperManager')->get('AdminRenderer'), Admin::class);
$this->addRenderer($serviceLocator->get('ViewHelperManager')->get('DefaultRenderer'), 'default');
if(!array_key_exists('entity', $this->options)){
return $this->getRenderer('default');
}
$entity = $this->options['entity'];
foreach($this->getRenderers() as $renderer){
if($renderer->canRender($entity)){
return $renderer;
}
}
//Alternatively, more specific hard-coding interface type check
if($entity instanceof User){
return $serviceLocator->get('ViewHelperManager')->get('UserRenderer');
}
//etc.
}
public function setCreationOptions(array $options){
$this->options = $options;
}
}
...but with the above demonstration, i would be unsure how to call it from within the view (as view helpers are typically called from their __invoke method and not from the service manager)?
(With an eye to migrating to ZF3, i do not want to use the ServiceLocatorAwareInterface).
You can declare your viewhelperin the factories section of module.config.php as :
return [
...
'view_helpers' => [
'factories' => [
'entityRenderer' => EntityRendererFactory::class
]
],
...
]
then use the following model :
class EntityRendererFactory extends AbstractHelper implement FactoryInterface
{
private $sm;
public function createService(ServiceLocatorInterface $serviceLocator){
$this->sm = $serviceLocator;
return $this;
}
public function _invoke() {
// your code
}
}
Personally, I start by creating a specific service manager containing only the necessary classes and it is this one that I record in the class.
$this->sm = $servicelocator->getServiceLocator()->get('mySpecificSM');
Incidentally, this model does not work under ZF3 where you need a factory class that builds the viewhelper class. A change not too complicated however.

Get FormField passed options within a FormEvent

In a Symfony project, I have a Form EventSubscriber acting on several forms.
It aims to disable each field which is already filled.
In the Subscriber when I use:
$childOptions = $child->getConfig()->getOptions();
I receive all resolved options for a child, I want to get only those passed during the form building. (Because form some FormTypes (i.o. DocumentType) it is not possible to reinject all resolved options, some of them causes troubles).
A FormType example :
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('entity',EntityType::class,array(
'class' => 'AppBundle:User',
'choice_label' => 'username',
))
->addEventSubscriber($this->changesSubscriber); // See next class
}
}
The Subscriber :
class ChangesSubscriber implements EventSubscriberInterface
{
// Disables filled inputs
public function postSetData(FormEvent $event)
{
$form = $event->getForm();
foreach($form->all() as $child)
{
$childName = $child->getName();
$childType = $child->getConfig()->getType()->getName();
// Here I receive all resolved options
// But I only want the options passed above during 'buildForm' ('class','choice_label') :
$childOptions = $child->getConfig()->getOptions();
if(!$child->isEmpty()){
$form->add($childName,$childType,array_merge($childOptions,array('disabled'=>true)));
}
}
}
}
This is one example of many use cases, another example could be :
Alsatian\FormBundle ExtensibleSubscriber
-> A formsuscriber to make AJAX submitted choices acceptacles for Choice/Entity/Document Types.
At this time, as you can see, I choosed to only take a couple of resolved options, but I'm not satisfied with this solution.
Sounds like you need to change your approach.
Maybe make a custom form type, and some of the options to it should be the options to create the original type, similar to how CollectionType works.
Maybe it looks a bit like this:
->add('entity', AjaxType::class,array(
'ajax_type' => EntityType:class,
'ajax_options' => [
'class' => 'AppBundle:User',
'choice_label' => 'username',
]
))
That type can add the event that listens for the data and decides what to do.

How can I set up Lazy Loading with ZF3 (no ServiceLocator pattern from anywhere)

I am writing a new ZF2 app. I have noticed that ServiceLocator usage pattern of calling services "from anywhere" has been deprecated from ZF3. I want to write code in mind for ZF3.
I was able to set up my Controller to call all dependencies at constructor time. But that means loading i.e. Doctrine object upfront before I need it.
Question
How do I set it up so that it is only loaded when I need it immediately? (lazy-loaded). I understand that ZF3 moves loading to Controller construction, which makes it not apparent as to how to load something Just-In-Time.
Old Code
class CommissionRepository
{
protected $em;
function getRepository()
{
//Initialize Doctrine ONLY when getRepository is called
//it is not always called, and Doctrine is not always set up
if (! $this->em)
$this->em = $this->serviceLocator->get('doctrine');
return $this->em;
}
}
Current Code after Refactor of ServiceLocator pattern
class CommissionRepository
{
protected $em;
function getRepository()
{
return $this->em;
}
function setRepository($em)
{
$this->em = $em;
}
function useRepository($id)
{
return $this->em->find($id);
}
}
class CommissionControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$parentLocator = $controllerManager->getServiceLocator();
// set up repository
$repository = new CommissionRepository();
$repository->setRepository($parentLocator->get('doctrine'));
// set up controller
$controller = new CommissionController($repository);
$controller->setRepository();
return $controller;
}
}
class CommissionController extends AbstractActionController
{
protected $repository;
public function setRepository(CommissionRepository $repository)
{
$this->repository = $repository;
}
public function indexAction()
{
//$this->repository already contains Doctrine but it should not
//I want it to be initialized upon use. How?
//Recall that it has been set up during Repository construction time
//and I cannot call it from "anywhere" any more in ZF3
//is there a lazy loading solution to this?
$this->repository->useRepository();
}
If you don't have any valid/strong reason to instantiate a custom entity repository, you should prefer extending of Doctrine\ORM\EntityRepository in your repositories like CommissionRepository. For example;
use Doctrine\ORM\EntityRepository;
class CommissionRepository extends EntityRepository
{
// No need to think about $em here. It will be automatically
// injected by doctrine when you call getRepository().
//
function fetchCommissionById($id)
{
// You can easily get the object manager directly (_em) or
// using getEntityManager() accessor method in a repository
return $this->_em->find($id);
}
}
By this way, entity manager will be automatically injected to the repository on construction when you call the $em->getRepository('App\Entity\Commission') method.
I assume that you already have a Commission entity in your app's Entity namespace:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repo\CommissionRepository")
* #ORM\Table
*/
class Commission
{
}
Then you can simplify the injecting process of the repository in your factory something like:
// ZF2 Way
class CommissionControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $services)
{
$em = $services->getServiceLocator()->get('doctrine');
$repository = $em->getRepository('App\Entity\Commission');
return new CommissionController($repository);
}
}
UPDATE - With the release of Service Manager V3, FactoryInterface has been moved to Zend\ServiceManager\Factory namespace (1), factories are literally invokables (2) and works with any container-interop compatible DIC (3) Updated factory would be like below:
// ZF3 Way
use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
use Doctrine\ORM\EntityManager;
class CommissionControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $dic, $name, array $options = null) {
$em = $dic->get(EntityManager::class);
$repository = $em->getRepository('App\Entity\Commission');
return new CommissionController($repository);
}
}
For the question; as of marcosh's said, Lazy Services are way to go to create services when need it immediately. ZF3 will use the zend-servicemanager 3.0 component when released. (Currently zend-expressive uses it) As of servicemanager v3 you can create some proxied services by defining lazy_services and delegators in your service configuration:
'factories' => [],
'invokables' => [],
'delegators' => [
FooService::class => [
FooServiceDelegatorFactory::class,
],
],
'lazy_services' => [
// map of service names and their relative class names - this
// is required since the service manager cannot know the
// class name of defined services up front
'class_map' => [
// 'foo' => 'MyApplication\Foo',
],
// directory where proxy classes will be written - default to system_get_tmp_dir()
'proxies_target_dir' => null,
// namespace of the generated proxies, default to "ProxyManagerGeneratedProxy"
'proxies_namespace' => null,
// whether the generated proxy classes should be written to disk or generated on-the-fly
'write_proxy_files' => false,
];
Also, starting with service manager v3 factories are compatible with the ContainerInterface. For the forward-compatibility, you may want to keep both __invoke() and createService() methods in your factories for a smooth migration.
In the end, your ZF3 compatible factory may look like:
class CommissionControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $name, array $options = null)
{
$em = $container->get('doctrine');
$repository = $em->getRepository('App\Entity\Commission');
return new CommissionController($repository);
}
public function createService(ServiceLocatorInterface $container, $name = null, $requestedName = null)
{
return $this($container, $requestedName, []);
}
}
Hope it helps.

Get unique instance of types inheriting from base class with StructureMap

I have a bunch of services for which I always want to get a unique instance with StructureMap. Now, I can get this working by configuring each type like:
ObjectFactory.Initialize(x =>
{
...
x.For<UserService>().AlwaysUnique();
...
});
But I don't really want to do that for every service type. The service types all inherit from ServiceBase - is there any way to configure StructureMap to use AlwaysUnique() for all types that inherit from ServiceBase?
I think you need to create a convention to achieve this:
using StructureMap.Pipeline;
using StructureMap.TypeRules;
using StructureMap.Configuration.DSL;
public class ConcreteLifecycleConvention : StructureMap.Graph.IRegistrationConvention
{
private readonly Type _baseType;
private readonly ILifecycle _lifecycle;
public ConcreteLifecycleConvention(Type baseType, ILifecycle lifecycle)
{
_baseType = baseType;
_lifecycle = lifecycle;
}
public void ScanTypes(TypeSet types, Registry registry)
{
foreach(var type in types.AllTypes())
{
if (type.IsAbstract || !type.CanBeCreated() || !type.CanBeCastTo(_baseType))
continue;
registry.For(type).LifecycleIs(_lifecycle);
}
}
}
Apply the convention in a scan:
ObjectFactory.Initialize(c =>
{
c.Scan(scan =>
{
scan.TheCallingAssembly();
scan.With(new ConcreteLifecycleConvention(typeof(ServiceBase),
new UniquePerRequestLifecycle()));
});
});
As a side note. With the built in conventions you can use the OnAddedPluginTypes to specify the lifecycle, but there is no built in convention that registers the concretes that is registered without an interface
scan.WithDefaultConventions().OnAddedPluginTypes(x =>
x.LifecycleIs(new UniquePerRequestLifecycle()));

Where can I initialize AutoMapper mappings in an Orchard module?

I am busy developing my first non-example Orchard module. It is a handful of controllers and views, with custom (EF) data access, and is largely independent of Orchard content types and parts. Normally I set up mappings in an Application_Start handler, but as the actions in this MVC module will be invoked in the context of the Orchard application, I no longer have that point of entry. My most obvious and immediate solution is to move mapping initialization into static constructors for mapped view models, e.g.
public class ApplicantPersonalDetailsModel : MappedViewModel<Applicant>
{
static ApplicantPersonalDetailsModel()
{
Mapper.CreateMap<Applicant, ApplicantPersonalDetailsModel>().Bidirectional();
}
....
}
How else can I do this? is there a better way to do this in MVC3/4 in general, or preferably, an event or hook I can grab in the Orchard application to also achieve this on applicaion startup?
The way I have done it is by implementing IOrchardShellEvents
public class MenuOrchardShellEvents : IOrchardShellEvents
{
public void Activated()
{
Mapper.CreateMap<YSRB.Menu.Models.Records.Customer, YSRB.Menu.Models.ViewModels.CustomerViewModel>()
.ForMember(c => c.CustomerType,
m => m.MapFrom(
x => (CustomerTypes)x.CustomerType
)
);
Mapper.CreateMap<YSRB.Menu.Models.ViewModels.CustomerViewModel, YSRB.Menu.Models.Records.Customer>()
.ForMember(c => c.CustomerType,
m => m.MapFrom(
x => (int)x.CustomerType
)
);
}
public void Terminating()
{
//Do nothing
}
}
Hope this helps.
The Handler is the best place for initializing your variables, even if you haven't defined any part inside your module you can define one without a driver but with handler.
public class InitPartHandler : ContentHandler
{
public InitPartHandler(IRepository<InitPartRecord> repository)
{
OnInitializing<InitPart>((context, part) =>
// do your initialization here
);
}
}
EDIT
InitPart and InitPartRecord would be
public class InitPart : ContentPart<InitPartRecord>
{
}
public class InitPartRecord : ContentPartRecord
{
}

Resources