How do I specify which implementation of an interface a specific class needs for setter injection?
I have a working eg for constructor injection but not for setters.
class Lister1 {
public $finder;
public function setFinder(Finder $finder){
$this->finder = $finder;
}
}
interface Finder {
public function findAllByName($name);
}
class FinderImpl1 implements Finder {
public function findAllByName($name) {}
}
Now for the above the following code works.
$di = new Di();
$di->instanceManager()->addTypePreference(
'Finder',
'FinderImpl1'
);
$lister = $di->get('Lister1');
But what if I have the following as well
class Lister2 extends Lister1{
}
class FinderImpl2 implements Finder {
public function findAllByName($name) {//assume a different impl}
}
So Lister1 needs FinderImpl1 injected & Lister2 needs FinderImpl2 injected.
Can we add a type preference on a per-class basis?
I had a look at the unit tests that ship with zf2 and nothing leaped out.
It looks like you've found Ralph's DI examples. Good. That was going to be my first suggestion. :)
I may be wrong (been a while since I used DI), but it might be simpler than you are thinking.
Using setter injection like so:
$di = new Zend\Di\Di;
$di->configure(new Zend\Di\Config(array(
'instance' => array(
'Lister1' => array(
'parameters' => array(
'finder' => 'FinderImpl1',
),
),
'Lister2' => array(
'parameters' => array(
'finder' => 'FinderImpl2',
),
),
)
)));
If you were going to define an instance of an interface, then you would need to worry about setting a "preference"; in which case if you had something tricky, you might consider using some aliases along with the preference definition.
Related
PHP-DI 6 provides multiple functions, that working in the definitions. Three of them seem to do the same in the definitions context: autowire(...), create(...), and get(...). E.g. I have following types:
FooServiceInterface
BarServiceInterface
FooAService implements FooServiceInterface
dependencies: BarServiceInterface $barService
FooBService implements FooServiceInterface
dependencies: BarServiceInterface $barService
BarService implements BarServiceInterface
dependencies: -
The FooServiceInterface gets injected into a Symfony controller (constructor injection).
Now my file with the definitions:
return [
FooServiceInterface::class => DI\autowire(FooBService::class),
BarServiceInterface::class => DI\autowire(BarService::class),
];
It works.
I also can set it up like this:
return [
FooServiceInterface::class => DI\get(FooBService::class),
BarServiceInterface::class => DI\get(BarService::class),
];
And it's still working.
This
return [
FooServiceInterface::class => DI\create(FooBService::class),
BarServiceInterface::class => DI\create(BarService::class),
];
doesn't work.
And this
return [
FooServiceInterface::class => DI\get(FooBService::class),
BarServiceInterface::class => DI\create(BarService::class),
];
does.
What is the difference between the three functions (in the context of definitions)? Which one is the recommended function to set up a common interface dependency definition (like SomeInterface::class => DI\recommendedFunction(SomeClass::class))?
I would say use get() only if you know why you would need it.
Then to choose between autowire() and create() is up to you: do you need autowiring or not?
Using simply create() is telling PHP-DI to just do new FooService() to create the service. If that's enough, then fine. If your service has dependencies, you can either rely on autowiring (using autowire()) or define the dependencies to inject manually using create(). It's up to you.
I'm trying to set multiple values for a select object with Zend Framework 2's form class but it's only passing one value. Here is my code:
public function addphotosAction()
{
$identity = $this->identity();
$files = array();
$album_name = array();
foreach (glob(getcwd() . '/public/images/profile/' . $identity . '/albums/*', GLOB_ONLYDIR) as $dir) {
$album_name = basename($dir);
$files[$album_name] = glob($dir . '/*.{jpg,png,gif,JPG,PNG,GIF}', GLOB_BRACE);
}
$form = new AddPhotosForm();
$form->get('copy-from-album')->setValueOptions(array($album_name));
return new ViewModel(array('form' => $form, 'files' => $files));
}
I know it has to do with $album_name but am at a loss about how to use it to grab all the directories (if I try to write to $album_name via []), I get an warning of
`Warning: Illegal offset type in C:\xampp\htdocs\module\Members\src\Members\Controller\ProfileController.php on line 197`
which is the $files[$album_name] = glob($dir . '/*.{jpg,png,gif,JPG,PNG,GIF}', GLOB_BRACE); line.
As I said, I am at a loss about how to edit this to grab all the directories.
Any help would be appreciated.
Thanks!
here is a screenshot of what I am trying to describe: http://imgur.com/OGifNG9
(there is more than one directory that exists but only one is being listed in the select menu).
I really recommend to do it with a factory. With a factory you 'll write this code once and can use it everywhere else in your code. For object orientated reasons, in which everything should be an object, I recommend using PHP 's own DirectoryIterator class instead of glob. The code in the controller should be kept as small as possible. Please have a look at the following example code.
The Form Factory with the Directory Iterator
The form factory intializes the form class with everything you need for the form instance for you, so this code won 't show up in the controller. You can re-use it for an inherited edit form for example.
<?php
namespace Application\Form\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Form\AddPhotosForm;
class AddPhotosFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $oServiceLocator)
{
$oParentLocator = $oServiceLocator->getServiceLocator();
// please adjust the dir path - this is only an example
$aDirectories = [];
$oIterator = new \DirectoryIterator(__DIR__);
// iterate and get all dirs existing in the path
foreach ($oIterator as $oFileinfo) {
if ($oFileinfo->isDir() && !$oFileinfo->isDot()) {
$aDirectories[$oFileinfo->key()] = $oFileinfo->getFilename();
}
}
// set option attribute for select element with key => value array of found dirs
$oForm = new AddPhotosForm();
$oForm->get('mySelectElement')
->setAttributes('options', $aDirectories);
return $oForm;
}
}
That 's all for the factory itself. The only thing you have to do is writing it down in your module.config.php file.
...
'form_elements' => [
'factories' => [
AddPhotosForm::class => AddPhotosFormFactory::class,
],
],
...
Using ::class not just cleans things up, it will lead to using fewer strings and this makes things easy to remember in an IDE with autocompletion for class names.
The Controller
With the factory we cleaned up the controller. In a controller code should be as small as possible. Using factories is the solution for many problems, which may happen in a later process of coding. So keep it always clean and simple.
...
public function indexAction()
{
$oForm = $this->getServiceManager()
->get('FormElementManager')
->get(AddPhotosForm::class);
return [
'form' => $oForm,
}
}
That 's all for the controller so far. Your select element was populated in the factory and your controller is easy to understand and as small as it should be.
How can I inject the "normal" ServiceManger into a custom validator used for REST calls (Use without Form). ZF 2.2.7 Used to inject an instance of external library into an validator.
I have tried the following, and nothing works:
Inject it with the ValidationPluginManager, service not found
Inject it via factory, factory will not be loaded in validator chain
Inject it via validator options, not possible because the "ServiceManager" is an instance of ValidationPluginManager with the asme result as mentioned in #1
Is there any concept how to solve this problem, or do i have to give up and link all libraries statically?
Not tested this and have never done with with ValidationPluginManager but works with ControllerManager, FormElementManager etc
// GetServiceLocator call should return Instance of ServiceManager
// Then retrieve the service, Yay!
$validationPluginManager->getServiceLocator()->get('SomeService')
There has been a discussion on github about a somewhat similar problem here. They suggested to use Zend\Form\FormAbstractServiceFactory and tinker with dependencies there (weierophinney before closing the topic).
In your post you mention you are not using a form did you mean you are not using the form in a classic kind of way or are you bypassing the whole form in particular?
It simply seems off to me to use a validator if there isn't a form present. Could you elaborate more on that?
EDIT: To my understanding zf2 requires that your input filters have form elements like 'inputs' etc. You did not post any code and I simply do not know if/or your able to bypass this somehow. I still do not understand why you'd still want to use validators in combination of input filters. I would simply skip the input filters and write the custom validator.
My personal preferences is to write factories instead of anonymous functions within module.php files. But this also could work the anonymous function way.
I would then simply resolve the dependencies within the customValidatorFactory and get the factory within my controller or whatever place I would need it.
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use CustomValidator;
class CustomValidatorFactory implements FactoryInterface
{
/**
* Create Service Factory
*
* #param ServiceLocatorInterface $serviceLocator
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$sm = $serviceLocator->getServiceLocator();
$customService = $sm->get('Application\Service\Geocoding');
$validator= new CustomValidator();
$validator->setCustomService($service);
return $validator;
}
}
// CustomValidator.php
class CustomValidator extends Zend\Validator\AbstractValidator
{
public function setCustomService($service)
{
$this->service = $service;
}
public function isValid($value)
{
$customService = $this->service;
if ($customService->customMethod() == true) {
return true;
}
return false;
}
}
//module-config.php
'service_manager' => array(
'factories' => array(
'custom\ValidatorFactory' => 'Namespace\To\CustomValidatorFactory',
),
),
//yourController or whatever.php will require access to the service manager
$customValidation = $sm->get('custom\ValidatorFactory');
// should return true or false now
$state = $customValidation->isValid($someValue);
I've got the code below from a sample app which uses structure map.
ObjectFactory.Initialize(x =>
{
x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.AssemblyContainingType<IAppointmentRepository>();
scan.AssemblyContainingType<SchedulingContext>();
scan.AssemblyContainingType<ScheduleRepository>();
scan.AssemblyContainingType<CrudContext>();
scan.WithDefaultConventions();
scan.ConnectImplementationsToTypesClosing(typeof(ClientPatientManagement.Core.Interfaces.IRepository<>));
scan.ConnectImplementationsToTypesClosing(typeof(IHandle<>));
});
};
I want to do the same thing in my own solution using autofac, especially the line that reads
scan.ConnectImplementationsToTypesClosing(typeof(IHandle<>));
I don't know if this does the same thing with the above code:
var builder = new ContainerBuilder();
builder
.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(p => p.IsClass)
.AsImplementedInterfaces();
The ConnectImplementationsToTypesClosing method of StructureMap connects implementations to open generic types.
Hence:
scan.ConnectImplementationsToTypesClosing(typeof(IHandle<>));
will register all implementations that close the open generic type IHandle<>
The equivalent in AutoFac would be:
builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
.AsClosedTypesOf(typeof(IHandle<>))
.AsImplementedInterfaces();
See the following links for more information
AutoFac Scanning
AutoFac Open Generics
I am new to DI concept and new to structuremap. I am trying to full fill a scenario where all my interfaces are in AssemblyA and all my implementations are in AssemblyB. I want to use Structuremap to inject instance of AssemblyB class in constructor which has dependency on interface from AssemblyA
public class Customer(ICustomerService)
{
}
ICustomerService is in AssemblyA and CustomerService class is in assemblyB. I want Structuremap to inject CustomerService instance in this constructor. I am assuming that if the name of class is same as the name of interface prefixed with and I. Structuremap will recognize it automatically.
I have written the following configuration.
x =>
{
x.Scan(scan =>
{
scan.Assembly("AssemblyA");
scan.Assembly("AssemblyB");
scan.TheCallingAssembly();
scan.WithDefaultConventions();
});
but it gives me error
StructureMap Exception Code: 202
No Default Instance defined for PluginFamily AssemblyA.ICustomerService, AssemblyA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
I want to use the default conventions and avoid registering each interface to a class.
Ok, I got it to work but I am even more confused now.
This code seems to work
IContainer container = new Container(c =>
{
c.Scan(x =>
{
x.Assembly("AssemblyA");
x.Assembly("AssemblyB");
x.IncludeNamespace("AssemblyA");
x.TheCallingAssembly();
x.WithDefaultConventions();
});
});
Here I have simple added x.IncludeNamespace("AssemblyA"); after the AssemblyB scan thinking that it needs this namespace and it has started working.
My problem is solved but I don't know what was wrong or if this is the right way to go. Any help will still be greatly appreciated.