ZF2 - Possible to override a factory? - php

I've created a module that needs to send email, and have created a mail service in its getServiceConfig as a factory. Simple enough.
$transport = $this->getServiceLocator()->get('custom_module_mail_transport');
// use transport
In Module.php:
'custom_module_mail_transport' => function($instance, $sm) {
$b = new \Zend\Mail\Transport\Smtp();
$b->setOptions(
new \Zend\Mail\Transport\SmtpOptions(
array(
'host' => 'smtp.isp.com',
'connection_class' => 'login',
'port' => 25,
'connection_config' => array(
'username' => 'abc',
'password' => 'def',
)
)
)
);
return $b;
},
I would like for people to be able to roll their own though, implementing their own factories for that mail transport for the custom module. I mean, I could use config to pick up on password and such, but the plethora of transports would make this unfun.
Adding a 'custom_module_mail_transport' factory to the Application-level service config doesn't seem to do the trick. Goal is to let the user roll their own factory to override the one the module provides.
What's the best way to do this?
Thanks for helping me wrap my head around ZF2.

Someone may have a better solution, but I think you could use a factory that allows for a user-defined class type implementing a common interface that you expect.
Excuse the meta-overview, but the user could:
Configure in the module-specific configuration a value for "custom_module_mail_transport_class" => "FQD\Of\My_Mail_Transport"
Your service locator can use a factory which:
Checks if the user's class_exists
Creates a new $passed_module_config['custom_module_mail_transport_class']()
Type checks for instanceof mailtransport_interface (or whatever you may name it)
If it is not the correct implementaion, either creates your standard class and/or generates an exception.
But then, I haven't really worked much with ZF2 yet.

Related

Create a object with depenencies that have to be configured

I am creating a client using GuzzleHttp 5.3. I want use the same Config object to configure the Guzzle Client. So, the creation of Guzzle Client depends of the object Config.
For example:
$config = new Config([
'user' => 'github',
'password' => '1234',
'base_url' => 'http://test_or_production_url.com'
]);
new GithubClient($config, new \GuzzleHttp\Client());
And the constructor:
public function __construct(Config $config)
{
$this->authData = [
'uname' => $config->getUser(),
'upassword' => $config->getPassword(),
];
$this->config = $config;
$this->httpClient = new \GuzzleHttp\Client(['base_url' => $config->getBaseUrl());
// I have to setup default headers, too.
}
But, I have to inject the Guzzle Client because I have to do unit tests.
One option is to inject a Guzzle Client configured as my client requires it. But the build process is a little cumbersome (set base_url, default header). The user must to know using Guzzle, I do not like this.
Other option is to create a new object (GithubClientFactory for example) to create my client.
$client = GithubClientFactory::make($config);
So, I hide the Guzzle dependency and the setup process. But the user always have to use the client factory.
Then, Is there a better (pattern) design to this problem?

How to load eden with composer?

I want to write an application that uses parts of the mailer part of the eden library.
I have required it via composer.json:
"require": {
"eden/mail": "^1.0",
...
It seems though that the library isn't autloaded properly, as the call to the eden via eden('mail')->smtp(...) function leads to:
PHP Fatal error: Call to undefined function Kopernikus\MassMailer\Service\eden() in ~/src/massmailer/src/Kopernikus/MassMailer/Service/EdenMailerFactory.php on line 20
The quick setup guides only handles the case for the one-file approach via:
include('eden.php');
eden('debug')->output('Hello World'); //--> Hello World
I don't want to add a huge libary file and include it manually.
The autoloading seems to works fine, I just have to use the class directly instead of going with the eden() function:
use Eden\Mail\Smtp;
YourClass
{
$smtp = new Smtp(
$host,
$user,
$pass,
$port = NULL,
$ssl = false,
$tls = false
);
}
The autoloading seems to works fine basically, as I can use the class directly instead of going with the eden() function:
use Eden\Mail\Smtp;
YourClass
{
...
$smtp = new Smtp(
$host,
$user,
$pass,
$port,
$ssl,
$tls
);
}
Yet then I get weird errors like when trying to send a mail.
[Eden\Core\Exception]
Both Physical and Virtual method Eden\Mail\Smtp->_getPlainBody()
I want to go the composer route.
It seems I have to include a file via include-path option or add someting to the autoload, yet I am unsure.
How to load eden mailer component the composer way?
Not a solution to the question direclty, but I managed to circumvent the issue by switching to "nette/mail".
composer remove eden/mail
composer require nette/mail
The SMTP worked fine for me via:
$options = [
'host' => 'smtp.gmail.com',
'username' => 'YOUR_ACCOUNT',
'password' => 'YOUR_PASSWORD',
'secure' => 'ssl',
];
$mailer = new Nette\Mail\MessageSmtpMailer($options);
$message = new Nette\Mail\Message();
$message->setFrom('my.mail#gmail.com');
$message->setBody('fnord');
$message->setSubject('foo');
$message->addTo(sendTo#gmail.com);
$mailer->send($message);
From roughly looking at it, I do believe that any Eden component isn't meant to be used standalone.
Eden seems to consist of a factory function named eden(), that cannot be autoloaded (because it's a function), and that is part of the core of this framework - thus it isn't delivered by the mail component you were using. Even the tests of this component rely on the fact that the core is present, but also the SMTP class you wanted to use has a dependency that isn't resolved by default: use Eden\System\File;, as well as this: class \Eden\Mail\Smtp extends \Eden\Mail\Base together with class \Eden\Mail\Base extends \Eden\Core\Base.
This Eden framework is meant to be used either completely, or not at all. You cannot isolate components out of it.
If you are in need for a mailing library, I'd suggest https://packagist.org/packages/swiftmailer/swiftmailer. It's not a component of a framework, so you don't inherit these dependencies.
The calls like eden('debug') or eden('mail') or just shortcuts to create singletons for their appropriate Index classes. EG: eden('mail') translates to \Eden\Mail\Index();
Which results in:
eden('debug')->output('Hello World'); //--> Hello World
//or
$debug = new \Eden\Debug\Index();
$debug->output('Hello World');

How to use php variables in configuring a service i symfony2?

I'm trying to use the symfony2 service container.However when I construct each service I want to use some of the variables defined in my controller.I want to know whether that's possible and how to do it.or an alternative if the aforementioned is not possible.
p.s: I'm using the yml service definitions.
$_ws['configurator'] = cmfGetInitObject(); //returns an instance of cmlObj
$_ws['configurator']->setOptions(
array(
'configurationsFolderPath' => realpath(dirname(__FILE__) . '/../configurations'),
'server' => &$_SERVER,
'legacyFormatEnabled' => true
)
);
$_ws['packageManager'] =& $_ws['configurator']->packageManager;
$_ws = $_ws['configurator']->load($_ws); //$_ws['configurator'] again resolves to a cmlObj
this is the array containing the configurations I mentioned.
You can't. The service container is created at the beginning of the request flow, while the controller is executed almost at the end of the flow.
If you really have not access to the required config in the container build phase of the request, you can use setters to set the values in the container and maybe use default values when building the service.
Even better would be to move this out of the controller and inside an event listener.

Implementing login functionality in zend framework 2

I'm going to implement login functionality using zend framework 2.
My code as follows.
$dbAdapter = new DbAdapter(array(
'driver' => 'Mysqli',
'database' => 'db',
'username' => 'root',
'password' => 'password'
));
$authAdapter = new AuthAdapter($dbAdapter);
$authAdapter
->setTableName('admin_users')
->setIdentityColumn('user_name')
->setCredentialColumn('password')
;
$authAdapter
->setIdentity($username)
->setCredential($password)
;
$result = $authAdapter->authenticate();
It gives error message as follows.
The supplied parameters to DbTable failed to produce a valid sql statement, please check table and column names for validity.
I checked database configurations and column names. But they are correct. Can anyone give me a clue to check this out please.
Thanks.
There's no need to re-invent the wheel and struggle with this yourself. ZF2 has been created with modularity in mind so that plug in modules for this kind of thing can be written.
There are a few such modules available already at the ZF-Commons site. The one you are looking for is ZfcUser. Just follow the instructions to install and use it. There is even an excellent tutorial written by the author of the module that will take you through the whole process step by step. http://blog.evan.pro/getting-started-with-the-zf2-skeleton-and-zfcuser
There is also an excellent how-to page to tell you how to modify it for your own use.

ServiceLocatorAwareInterface confuses the DI container when running module tests

I have a new module for which I'm writing tests.
The module contains a class which implements ServiceLocatorAwareInterface because it needs to create other objects using the DI container. Everything works fine when running in the skeleton app, but when running module tests i get the following:
Zend\Di\Exception\RuntimeException: Invalid instantiator of type "NULL" for "Zend\ServiceManager\ServiceLocatorInterface"
Researching a little bit I find that the DI container tries to create a new object of type "ServiceLocatorAwareInterface", which is of course wrong.
Digging a little more in the tests bootstrap, I find that adding the following line solves the problem, as in the DI now knows what class to instantiate for that interface.
$di->instanceManager()->addTypePreference('Zend\ServiceManager\ServiceLocatorInterface', new \Zend\ServiceManager\ServiceManager());
I'm not sure whether this is the best solution to the problem, as the ServiceManager passed by me is a dummy one.
Does anyone have any other ideas?
Yes, you are going in the right direction. (See the preferences documentation)
Not many people are using DI these days in favor of the ServiceManager (myself included), but if the config for DI remains similar to how it was during the ZF2 betas, you should be able to add a "preferences" section to your DI config like so:
'di' => array(
'instance' => array(
'preferences' => array(
'My_Interface' => 'My_Implementation_Or_Alias',
)
)
)
This configuration block can replace your call to $di->instanceManager()->addTypePreference()
Looking through the current docs, and mimicking the example here, you may have success defining the DI config as shown below using the ZF2 official release:
$di = new Zend\Di\Di;
$di->configure(new Zend\Di\Config(array(
'instance' => array(
'preferences' => array(
'My_Interface' => 'My_Implementation_Or_Alias',
)
)
)));
What you can do in this case is the following.
In your bootstrap for the module unit tests create a dummy application that is configured with a configuration that will only load the module you're testing.
...//other code before this for autoloading stuff
// DON'T RUN THE application in your tests, just init it
$application = Zend\Mvc\Application::init(include 'config/test.application.config.for.module.php');
$fullyConfigedManager = $application->getServiceManager();
TestCases::setServiceManager( $fullyConfigedManager );
After the application has been boostrapped you can pull the ServiceManager from the application directly. This service manager should be fully configured with any factories, invokables, and configuration from your module.

Categories