I'm working on a zf2 project which needs to be integrated with a 3rd party library. The library is not compatible with php namespaces.
I found few articles describing the integration of namespace compatible libraries. According to them if the library is compatible with namespaces, then we can integrate it by adding a map into Module.php as follows.
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
'MyLibrary' => __DIR__ .'/path/to/library',
),
),
I tried with that and then I had to set the namespace for each and every file within the library to be able to work with the library. But it's not a good practice to modify someone else's library.
So, please help me to integrate a non-namespace compatible php library with the zf2
Here's how I did it:
In your Module.php add a ClassMapAutoloader to the getAutoloaderConfig function.
public function getAutoloaderConfig() {
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
);
}
Then create a autoload_classmap.php at the same level als Module.php that looks like this:
return array(
'MyClass' => __DIR__ . '/libs/MyClass.php',
);
Related
I'm currently working on translating an existing Zend Framework 2 project that is spread around multiple modules.
My understanding of the translate functionality of ZF2, is that you can have as many translation files, providing each are 'namespaced' to a different text_domain. This works fine in practice, with each module having the following in their module.config.php file:
...
'translator' => array (
'locale' => 'en_US',
'translation_file_patterns' => array (
array (
'type' => 'phparray',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s_default.php',
'text_domain' => 'ExampleModule'
),
),
),
...
Which adds a ../language/*_default.php file to the translation list with a text_domain of ExampleModule. All good so far.
Now, the translator itself needs to know which text_domain to pick a translation from and will use default if one isn't provided.
So, inside and at the top of all of my view *.phtml files, I have:
$this->plugin('translate')->setTranslatorTextDomain('ExampleModule');
$this->formLabel()->setTranslatorTextDomain('ExampleModule');
$this->formText()->setTranslatorTextDomain('ExampleModule');
Which tells all proceeding $this->translate() blocks and form elements which text_domain to use.
This is great, and works fine, but it doesn't sit well with the DRY principle in that I have similar code at the top of every view. I attempted to extend the ViewModel class so I can pick a different ViewModel class in the controller and have the above code already baked in, but the plugins aren't available at that stage.
How would I include the above code on every/most views without having to type it each time?
After searching endlessly, I found that the default renderer - PhpRenderer - can be accessed via the onBootstrap method of Module.php (reference).
As the view scripts are rendered by PhpRenderer the $this variable points to PhpRenderer (reference). This means that you can attach the code I needed to Module.php as below:
// Get the default ViewRenderer (PhpRenderer) and setup the correct text domain for derivative plugins
$viewRenderer = $e->getApplication()->getServiceManager()->get('ViewRenderer');
$viewRenderer->plugin('translate')->setTranslatorTextDomain('ExampleModule');
$viewRenderer->formLabel()->setTranslatorTextDomain('ExampleModule');
$viewRenderer->formText()->setTranslatorTextDomain('ExampleModule');
As the current namespace matches the text_domain I need, the above can be simplified by swapping 'ExampleModule' with __NAMESPACE__.
EDIT: If you're looking for a different text_domain per module; you'll need in just one Module.php:
$viewRenderer = $e->getApplication()->getServiceManager()->get('ViewRenderer');
$eventManager->getSharedManager()->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', function($e) use ($viewRenderer) {
$controller = $e->getTarget();
$controllerClass = get_class($controller);
$moduleNamespace = substr($controllerClass, 0, strpos($controllerClass, '\\'));
$viewRenderer->plugin('translate')->setTranslatorTextDomain($moduleNamespace);
$viewRenderer->formLabel()->setTranslatorTextDomain($moduleNamespace);
$viewRenderer->formText()->setTranslatorTextDomain($moduleNamespace);
}, 100);
I have created a Zend Framework Module which manages a set of DB and filesystem operations. I have managed to get a 100% Test Coverage (with mocked databases) and I would like to test the integration of this module into a testing environment.
I have a Service class in /modules/MyModule/src/MyModule/Service/MyModuleService.php which is currently not implementing any interfaces. The documentation doesn't hint that this is something I'd need to do.
class MyModuleService
{
public function doA(){...}
public function doB(){...}
.....
}
I have not changed anything in the application folder from the default application skeleton. I have coded up Model classes which do a lot of database and filesystem manipulation. The Service class is ultimately triggering these actions.
Having done the basic configuration according to the tutorial it seems like ZF doesn't register my classes when I try to access them outside of the ZF filesystem.
//dependencies for auto loader
require_once 'Zend/Loader/AutoloaderFactory.php';
use Zend\Loader\AutoloaderFactory;
//NEW registering the autoloader (as per Tomdarkness' response)
Zend\Loader\AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => TRUE,
'namespaces' => array(
'MyModule'=> '/path/to/zendframework/module/src/MyModule',
)
)
)
);
//what factories are auto-loaded?
var_export(AutoloaderFactory::getRegisteredAutoloaders());
The var_export now returns the following:
array(
'Zend\\Loader\\StandardAutoloader' => Zend\Loader\StandardAutoloader::__set_state(
array('namespaces' =>
array('Zend\\' => '/usr/lib/zendframework/library/Zend/',
'MyModule\\' => '/path/to/zendframework/module/src/MyModule/',
), 'prefixes' => array(),
'fallbackAutoloaderFlag' => FALSE,
)
)
);
In my module.php I do declare the service as follows:
/* Configure DB Service Managers */
public function getServiceConfig()
{
return array(
// ...
'invokables' => array(
'myModuleService'=> function ($sm){
$myModuleTable = $sm->get('MyModuleTable\Model\MyModuleTable');
return new MyModuleService($myModuleTable);
}
),
}
How can I now instantiate a MyModuleService from this autoloaded class?
You call:
var_dump(AutoloaderFactory::getRegisteredAutoloaders());
But you've not actually registered any autoloaders. To register the standard autoloader you need to run:
Zend\Loader\AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true
)
));
You can also register the Classmap autoloader via the factory method if you have generated classmaps.
Note, if you are using composer then you'll want to actually just include vendor/autoload.php in your file, rather than using the AutoloaderFactory. However, remember you need your module namespaces to be registered in the autoload section of composer.json if you use this method.
I am still struggling in instantiating a service from a ZF2 module outside of Zend Framework (in a blank .php).
I want to achieve:
Instantiate + invoke a ZF2 service method from outside ZF by the use of the ServiceManager and possibly DI.
What I have now: (UPDATED 4/10/2013)
Following up on the comments below I have done more research,particularly:
The quick guide
http://framework.zend.com/manual/2.0/en/modules/zend.service-manager.quick-start.html
RTD (Databases and models) http:
//zf2.readthedocs.org/en/latest/user-guide/database-and-models.html
Modules presentation (Very helpful) http://www.youtube.com/watch?v=Vp7y65rnN98#t=1200
Module source on github - https: //github.com/juriansluiman/SlmMail
I've opted to trim out all the DI and ModuleManager things and try to autoload (works fine now) and instantiate (does not) a service.
1 - Autoload the requested classes using a Classmap and instantiate servicemanager in a stand-alone .PHP file
// Autoload ZF and ProductImage module via classmap
Zend\Loader\AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => TRUE,
),
'Zend\Loader\ClassMapAutoloader' => array(
'/home/frequency/domains/scrftcdn/public_html/ft/shop/php/zendframework/module/ProductImage/autoload_classmap.php',
)
)
)
// Hard-coded servicemanager configuration (will come from $module->getConfig once this works)
$smc = new \Zend\ServiceManager\Config(
array(
'service_manager' => array(
'factories' => array(
'ProductImage\Model\ProductImage' => 'ProductImage\Factory\ProductImageFactory',
)
),
)
);
// Instantiate the service manager
$sm = new \Zend\ServiceManager\ServiceManager($smc);
//Load the service via the service manager
$service = $sm->get('ProductImage\Model\ProductImage'); // <throws exception
die();
2 - The exception
[error] [client 192.168.6.52] PHP Fatal error:
Uncaught exception 'Zend\\ServiceManager\\Exception\\ServiceNotFoundException' with message 'Zend\\ServiceManager\\ServiceManager::get was unable to fetch or create an instance for ProductImage\\Model\\ProductImage' in /usr/lib/zendframework/library/Zend/ServiceManager/ServiceManager.php:495
Stack trace:\n#0 /home/frequency/domains/wpfreqad/public_html/wp-content/themes/frequency/manage-product-images/functions.inc.php(48): Zend\\ServiceManager\\ServiceManager->get('ProductImage\\Mo...')
#1 /home/frequency/domains/wpfreqad/public_html/wp-content/themes/frequency/functions.inc.php(14): require_once('/home/frequency...')\n
#2 /home/frequency/domains/wpfreqad/public_html/wp-content/themes/frequency/functions.php(14): require_once('/home/frequency...')\n
#3 /home/frequency/domains/wpfreqad/public_html/wp-settings.php(293): include('/home/frequency...')\n
#4 /home/frequency/domains/wpfreqad/public_html/wp-config.php(90): require_once('/home/frequency...')\n
#5 /home/frequency/domains/wpfreqad/public_html/wp-load.php(29): require_onc in /usr/lib/zendframework/library/Zend/ServiceManager/ServiceManager.php on line 495
3 - ProductImage\autoload_classmap.php
<?php
// Generated by ZF2's ./bin/classmap_generator.php
return array(
'ProductImageTest\Service\ProductImageServiceTest' => __DIR__ . '/test/ProductImageTest/Service/ProductImageServiceTest.php',
'ProductImage\Module' => __DIR__ . '/Module.php',
'ProductImage\Factory\ProductImageFactory' => __DIR__ . '/src/ProductImage/Factory/ProductImageFactory.php',
'ProductImage\Model\ProductImage' => __DIR__ . '/src/ProductImage/Model/ProductImage.php',
);
4 - ProductImage\Module.php
class Module implements \Zend\ModuleManager\Feature\ConfigProviderInterface
{
/* Invoked by Module Manager */
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
}
5 - ProductImage\config\module.config.php
<?php
return array(
'service_manager' => array(
'factories' => array(
'ProductImage\Model\ProductImage' => 'ProductImage\Factory\ProductImageFactory',
),
),
);
I hope that's the right approach and not too far off the right way..
I've finally found a solution. Jurian's hints to using the actual application have put me on the right track! :)
1 - /zendframework/config/application.config.php.
Everything is default, just make sure the module is added. I commented the 'application' module as I don't see any use for it (as of now). I also had to change the path to the config files from './module' to __DIR__ . '../module' as it was looking in the wrong directory (took me a while to find that one).
<?php
return array(
// ...
'modules' => array(
'ProductImage', /* ProductImage module */
// 'Application',
),
// ...
'module_listener_options' => array(
'module_paths' => array(
__DIR__ . '/../module',
__DIR__ . '/../vendor',
),
2 - configuration
make sure the modules are configured right, and also that ZF2 Path is set up correctly. In my case, run through the quick start on RTD (http://zf2.readthedocs.org/en/latest/ref/installation.html). I had the ZF2_PATH exception and change the httpd.conf via WHM.
3 - Read more on RTD
In particular on how you can bootstrap the application: http://zf2.readthedocs.org/en/latest/modules/zend.mvc.intro.html#zend-mvc-intro
Which after very little debugging produced me the following code to access a neatly configured $sm instance.
//wherever the ZF2 application skeleton is, include the autoloader
require_once '/home/path/to/the/ZF2/application/directory/init_autoloader.php';
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Application;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
// setup autoloader
AutoloaderFactory::factory();
// get application stack configuration
$configuration = include '/home/path/to/the/ZF2/application/directory/config/application.config.php';
//var_export($configuration);
// The init() method does something very similar with the previous example.
$app = Application::init($configuration);
$sm = $app->getServiceManager();
$pi = $sm->get('ProductImage\Service\ProductImageService');
var_export($pi);
die();
I do not like the fact that the configuration needs to be specified in addition to the init_autoloader path. I avoid this implementation from being copied and pasted all over the place, I am considering integrating the $sm instantiation into the init_autoloader.php in the future so that the path of the configuration file does not have to be specified whenever a ProductImage service needs to be invoked.
I have stored a third party php library in /vendor/library folder. Now i need to import it to my Zend app and use it inside controller action.
require_once ('/vendor/library/client.php');
Is this correct ? Or there is other way to to handle this ?
Use the ZF autoloader, then forget about include/require.
http://framework.zend.com/manual/1.12/en/zend.loader.autoloader.html
It means though that your class names and file names have to follow their naming conventions - which may be more trouble than it is worth.
But if you are developing your own library to work within ZF, then it is a good idea.
Adding a Composer ready 3rd party library to a ZF2 instance
The correct way to add a 3rd party library is to use Composer.
E. g. if you wish to add ZfcUser to your Zend Framework 2 application use the following command:
composer require zf-commons/zfc-user:dev-master
This will download the code from github and you just need to add the module name to your: /config/application.config.php.
Adding other 3rd party library to a ZF2 instance
If your 3rd party library is not Composer ready, you can add it to your Zend Framework 2 instance by creating a Module for it.
Step 1
/vendor/MyModule/Module.php
<?php
namespace MyModule;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
class Module implements AutoloaderProviderInterface
{
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
}
What this basically provides is a way for you to register your 3rd party code within a file called autoload_classmap.php:
Step 2
/vendor/MyModule/autoload_classmap.php
<?php
return array(
'MyModule\VendorLibrary' => __DIR__ . '/src/MyModule/VendorLibrary.php',
);
Step 3
Your 3rd party code should reside in:
/vendor/MyModule/src/MyModule/VendorLibrary.php and could read something like this:
<?php
namespace MyModule;
class VendorLibrary
{
public function sayHi($name)
{
return "Hi there, $name!";
}
// your 3rd party code ...
}
Step 4
Add your new module to application.config.php:
/config/application.config.php
<?php
return array(
'modules' => array(
// your other modules here ...
'MyModule'
),
'module_listener_options' => array(
'config_glob_paths' => array(
'config/autoload/{,*.}{global,local}.php',
),
'module_paths' => array(
'./module',
'./vendor',
),
),
);
Usage
In your Controller you now use your vendor class like:
$vendor = new \MyModule\VendorLibrary();
$hi = $vendor->sayHi('John');
While it is a lot easier to use require_once(), it is not advisable because:
it does not provide predictability and structure of your class hierarchy and location
you also need to take care of include paths and make sure require_once is present in all controllers that need the 3rd party features
it does not allow for overriding classes (Magento-style)
etc.
Hope this helps!
I want to extend standard Zend\ModuleManager\ModuleManager, is it possible?
For example I want to load modules list from database and I want to add some methods for working with modules.
If I set factory to serviceManager:
'service_manager' => array(
'factories' => array(
'moduleManager' => 'Path/To/My/ModuleManager',
),
),
There is error "A service by the name or alias "modulemanager" already exists and cannot be overridden, please use an alternate name"
The module manager in Zend Framework 2 is created via a service factory class. ModuleManager is mapping to Zend\Mvc\Service\ModuleManagerFactory. I advice you to have a look on this ModuleManagerFactory and see what object are injected in the module manager on createService().
If you want to extend and use your own module manager you must create a class that extends ModuleManager but also create a service manager factory that overwrites the Zend\Mvc\Service\ModuleManagerFactory. You are on the right way with the following code but it is important to put this code in the /config/application.config.php file because this is the config file that Zend\Mvc uses to create the main services.
// config/application.config.php
'service_manager' => array(
'factories' => array(
'ModuleManager' => 'Path/To/My/ModuleManagerFactory', // <-- Path to MM factory
),
),
The link below will give you good information about what default services are run with \Zend\Mvc and how and where this is happening:
https://zf2.readthedocs.org/en/latest/modules/zend.mvc.services.html
Hope this helps, feedback will be appreciated :)
Stoyan