My question was asked before. I also would like to access to my global configs (config/{,*.}{global,local}.php) located in my personal libraries (in the vendor directory). The closest answer that I think I found is here. I created function in my class
public function getServiceConfig()
{
return array(
'factories' => array(
'funcservice' => function(\Zend\ServiceManager\ServiceLocatorInterface $sm) {
$config = $sm->get('config');
}
)
);
}
And it works however I can't figure out how to get anything from the result.
$config = $this->getServiceConfig();
print_r($config);
gives me
Array
(
[factories] => Array
(
[funcservice] => Closure Object
(
[this] => Tools\Model\StandartFuncs Object
(
[eventIdentifier:protected] => Zend\Mvc\Controller\AbstractActionController
[plugins:protected] =>
[request:protected] =>
[response:protected] =>
[event:protected] =>
[events:protected] =>
[serviceLocator:protected] =>
)
[parameter] => Array
(
[$sm] => <required>
)
)
)
)
and from $config = $this->getServiceConfig()->get('locales'); I get
Fatal error: Call to a member function get() on a non-object
Let's assume you have a locales config file locales.local.php:
<?php
return array(
'hostname' => 'http://apachehost'
);
These global and local config files should be in the config/autoload folder.
Folder structure:
- root
- config
- autoload
- locales.global.php
- locales.local.php
- application.config.php
Then you load them using the following line in your application.config.php. Details on this advanced configuration you can read here in the ZF2 documentation
'module_listener_options' => array(
'config_glob_paths' => array(
'config/autoload/{{,*.}global,{,*.}local}.php',
),
)
Now you can access your config from your ServiceManager instance like this:
$config = $serviceManager->get('Config');
This $config variable is an array. So you cannot access anything with getters. You are supposed to use array notation:
$locales = $config['locales'];
If your really want to use getters then you have to make your configuration to an object. You can do this using the Zend\Config\Config class like this:
$config = new \Zend\Config\Config($config, false);
Now you can access like you wrote in your question:
$config->get('locales');
Update
If you want to load auto config files from a vendor module it is common practice to copy those *.local.php and/or *.global.php files that come with the module to the autoload folder and edit the copied files according to your needs.
I don't think you've quite understood the solution you're trying to implement. The factory you're adding to the service config needs to return an instance of your library class. The reason you're putting it in a factory is so that you can inject the config array into it. So your code should look more like this:
public function getServiceConfig()
{
return array(
'factories' => array(
'funcservice' => function(\Zend\ServiceManager\ServiceLocatorInterface $sm) {
$config = $sm->get('config');
return new \SomeLibrary($config);
}
)
);
}
(where SomeLibrary is the name of your library class in /vendor).
You will then need to use the service manager to instantiate your library class whenever you need to access it, e.g. in a controller:
public function someAction()
{
$yourLibrary = $this->getServiceLocator()->get('funcservice');
}
This will create an instance of your library class, passing the config array as the constructor to the first parameter. You should never need to call getServiceConfig() yourself, and this wouldn't achieve anything.
Related
I am listening to this talk here : https://www.youtube.com/watch?v=Vp7y65rnN98
At the 44:00 Module.php::getConfig() vs Module.php::getControllerConfig()
looking at an example module like ZFCampuses zf-apigility-admin module ...
I see BOTH config/module.config.php and Module.php::getControllerConfig() defining Controller Factories.
zf-apigility/config/module.config.php
'controllers' => array(
'aliases' => array(
'ZF\Apigility\Admin\Controller\HttpBasicAuthentication' => 'ZF\Apigility\Admin\Controller\Authentication',
'ZF\Apigility\Admin\Controller\HttpDigestAuthentication' => 'ZF\Apigility\Admin\Controller\Authentication',
'ZF\Apigility\Admin\Controller\OAuth2Authentication' => 'ZF\Apigility\Admin\Controller\Authentication',
),
'invokables' => array(
'ZF\Apigility\Admin\Controller\App' => 'ZF\Apigility\Admin\Controller\AppController',
'ZF\Apigility\Admin\Controller\CacheEnabled' => 'ZF\Apigility\Admin\Controller\CacheEnabledController',
'ZF\Apigility\Admin\Controller\FsPermissions' => 'ZF\Apigility\Admin\Controller\FsPermissionsController',
),
'factories' => array(
'ZF\Apigility\Admin\Controller\Documentation' => 'ZF\Apigility\Admin\Controller\DocumentationControllerFactory',
'ZF\Apigility\Admin\Controller\Filters' => 'ZF\Apigility\Admin\Controller\FiltersControllerFactory',
'ZF\Apigility\Admin\Controller\Hydrators' => 'ZF\Apigility\Admin\Controller\HydratorsControllerFactory',
'ZF\Apigility\Admin\Controller\Validators' => 'ZF\Apigility\Admin\Controller\ValidatorsControllerFactory',
'ZF\Apigility\Admin\Controller\InputFilter' => 'ZF\Apigility\Admin\Controller\InputFilterControllerFactory',
),
),
zf-apigility/Module.php
public function getControllerConfig()
{
return array('factories' => array(
'ZF\Apigility\Admin\Controller\Authentication' => function ($controllers) {
$services = $controllers->getServiceLocator();
$model = $services->get('ZF\Apigility\Admin\Model\AuthenticationModel');
return new Controller\AuthenticationController($model);
},
'ZF\Apigility\Admin\Controller\Authorization' => function ($controllers) {
$services = $controllers->getServiceLocator();
$factory = $services->get('ZF\Apigility\Admin\Model\AuthorizationModelFactory');
return new Controller\AuthorizationController($factory);
},
'ZF\Apigility\Admin\Controller\Config' => function ($controllers) {
$services = $controllers->getServiceLocator();
return new Controller\ConfigController($services->get('ZF\Configuration\ConfigResource'));
},
'ZF\Apigility\Admin\Controller\ModuleConfig' => function ($controllers) {
$services = $controllers->getServiceLocator();
return new Controller\ModuleConfigController($services->get('ZF\Configuration\ConfigResourceFactory'));
},
'ZF\Apigility\Admin\Controller\ModuleCreation' => function ($controllers) {
$services = $controllers->getServiceLocator();
$model = $services->get('ZF\Apigility\Admin\Model\ModuleModel');
return new Controller\ModuleCreationController($model);
},
'ZF\Apigility\Admin\Controller\Source' => function ($controllers) {
$services = $controllers->getServiceLocator();
$model = $services->get('ZF\Apigility\Admin\Model\ModuleModel');
return new Controller\SourceController($model);
},
'ZF\Apigility\Admin\Controller\Versioning' => function ($controllers) {
$services = $controllers->getServiceLocator();
$factory = $services->get('ZF\Apigility\Admin\Model\VersioningModelFactory');
return new Controller\VersioningController($factory);
},
));
}
My question is, why is this doubling up occurring here? What am I missing about the usage of Module.php::getControllerConfig() vs merely listing the Factories in Module.config.php ?
Edit : I think I found the answer to my question here https://stackoverflow.com/a/22374072/389976
You'll see that all the factories defined in Module.php use closures (AKA anonymous functions). The convention is for all config to go into module.config.php except for the closures.
ZF has built-in functionality (disabled by default) to cache the merged config files, but it can't do that if the config file contains closures, as these cannot be serialized, and thus are not cacheable. By keeping them separate you can enable config caching, which improves performance.
Edit: Perhaps we're talking at crossed purposes a little here. My answer above hopefully explains why the closures from Module.php shouldn't be moved to the module config file. You could of course create factory classes for each of the closures defined, and then list those in the module config. Some would consider this best practice (personally I rarely do this). At this point it's a trade off between ease of development and performance. It's much more convenient to create a four line closure than create a whole new class.
I use phtml templates rendered with the phpRenderer as the body of my emails. This was working fine until recently when I started to get an error with the basePath() view helper.
I think the problem started occurring after I upgraded my framework to 2.2.6, but I can't be sure.
Here's how I am configuring the view for the body of my email:
$paths = array(__DIR__ . '/../../../view/std-email/');
$resolver = new \Zend\View\Resolver\TemplatePathStack(array('script_paths' => $paths));
$renderer = new \Zend\View\Renderer\PhpRenderer();
$renderer->setResolver($resolver);
$view = new \Zend\View\Model\ViewModel(array(...));
$view->setTemplate($this->template)->setTerminal(true);
$this->mail->setBody($renderer->render($view));
In the view script I am simply echoing $this->basePath() and that's where the exception is occurring. It indicates that the base path is not set, even if I hard code a base path under the view_manager section of the module config.
When a view is assembled in the normal manner (i.e. by MVC listeners on controllers) the base path helper works correctly. It seems to me that when I manually instantiate a new renderer it is getting it's own instance of the view helper plugin manager, and this instance lacks the default configuration. So, a few questions here:
Has anyone else struck this problem?
Is it related to the version of the framework?
What's the best way to fix it?
I could manually set the base path each time I instantiate a renderer, but I think there must be a more elegant way. For example, can I share the existing view helper plugin manager instance?
Assuming your emails are being sent in or by a specific module you can set the default basePath in the view_manager key of the specific module.config like so:
module.config.php
return array
(
'controllers' => array(...),
'router' => array(...),
'view_manager' => array
(
'base_path' => 'http://local.yourproject.com',
'template_map' => array(...),
'template_path_stack' => array(...),
),
);
However, given Kim's comments, the issue may be one of what you're passing to the setBody method. You could try passing a MimeMessage object with parts that are MimePart objects. The contents of which would come from templates set in the Module and thus subject to the set base path:
$EmailTemplate = new EmailUtility();
$emailContent = $EmailTemplate->getEmailTemplate
(
'identifier-for-your-email-template',
$emailTemplateArrayOptions
);
$templateRenderer = $this->getServiceLocator()->get('ViewRenderer');
$emailHTMLContent = $templateRenderer->render
(
$emailContent['html'],
$emailContent['templateVariables']
);
$emailTextContent = $templateRenderer->render
(
$emailContent['text'],
$emailContent['templateVariables']
);
// Create HTML & Text Headers
$emailHTMLHeader = new MimePart($emailHTMLContent);
$emailHTMLHeader->type = "text/html";
$emailTextHeader = new MimePart($emailTextContent);
$emailTextHeader->type = "text/plain";
$emailBody = new MimeMessage();
$emailBody->setParts(array($emailTextHeader, $emailHTMLHeader,));
// instance mail
$mail = new Mail\Message();
$mail->setBody($emailBody);
The contents of EmailUtility->getEmailTemplate(...)
public function getEmailTemplate($emailIdentifier, $emailVariables)
{
switch($emailIdentifier)
{
case 'identifier-for-your-email-template' :
// logic for required template vars goes here
$emailTextLink = 'module/config/specific/mapping/text';
$emailHTMLLink = 'module/config/specific/mapping/html';
$emailSubject = 'Verify Your Email Address';
$templateVariables = array(...);
break;
}
return array
(
'text' => $emailTextLink,
'html' => $emailHTMLLink,
'subject' => $emailSubject,
'templateVariables' => $templateVariables
);
}
and then again in your module.config.php
return array
(
'controllers' => array(...),
'router' => array(...),
'view_manager' => array
(
'base_path' => 'http://local.yourproject.com',
'template_map' => array
(
// Email Templates
'module/config/specific/mapping/html' => __DIR__ . '/../view/module/config/specific/template-html.phtml',
'module/config/specific/mapping/text' => __DIR__ . '/../view/module/config/specific/template-text.phtml', ),
'template_path_stack' => array(...),
),
);
If you're using the full MVC Stack of Zend Framework 2 the basePath will be set automatically. If you don't however you'll need to set the helper manually like this:
$helper = new BasePath();
$helper->setBasePath('/my/base/path');
echo $helper;
I have been trying to configure our Module.php to use the Module Manager Listeners for configuration (i.e interfaces that are available under Zend\ModuleManager\Feature\*). Specifically, I want to be able to configure the routes of my module outside of the main module.config.php. I have not been able to find any actual examples of this.
What I have found, if I have read the documentation correctly, is that the method getRouteConfig() should merge in my routes into the array provided by getConfig()?
Module.php
class Module implements Feature\RouteProviderInterface
{
//...
public function getRouteConfig()
{
return include __DIR__ . '/config/route.config.php';
}
//...
}
/config/route.config.php
return array(
'route_manager' => array(
'router' => array (
'routes' => array(
//.. routes that were working correctly when added to module.config.php
),
),
),
);
I can see the array returned via getRouteConfig() so I know the method is being called correctly.
Perhaps I am misunderstanding the purpose of the above interface, or I have not provided the correct "key" (route_manager) for this to be merged correctly, as I'm getting 404 for my routes.
Any help would be appreciated!
I haven't done this in the way you mentioned yet, but the key route_manager is not required within the getRouteConfig() Method.
This is due to the fact that all of the get{$specificManager}Config()-Methods are called directly from their respective Manager-Classes. Therefore the initial key is not required. Using another terminology, when using getRouteConfig() you are already in the scope of route_manager. Same as when you use getServiceConfig() you're already in the scope of service_manager. However getConfig() is within the application-scope and therefore accessing configuration of application-parts, you need to address tose specificaly.
One thing to note is: the configuration of getConfig() can be cached to increase performance, whereas all the other get{$specificManager}Config() methods are not. Especially in the case of the RouteConfiguration I'd highly suggest to use the getConfig()-Method for your RouteConfig.
If you really need to separate the configuration, then I'd suggest the way that #Hendriq displayed for you.
Well I have it working but I only use the getConfig(). What is do is I use an array_merge in the getConfig().
public function getConfig()
{
return array_merge(
require_once 'path_to_config/module.config.php',
require_once 'path_to_config/routes.config.php'
);
}
My router.config.php looks then like:
return [
'router' => [
'routes' => [
// routes
]
]
];
This way I also got some other config files seperated (ACL).
Edit
Thanks to the article Understanding ZF2-Configuration, I got an idea. I think your array should not be:
return array(
'route_manager' => array(
'router' => array (
'routes' => array(
//.. routes that were working correctly when added to module.config.php
)
)
)
);
but rather be
return array(
'router' => array (
'routes' => array(
//.. routes that were working correctly when added to module.config.php
),
),
);
The getRouteConfig is similar to the other providers it is there so you're able to create some custom routes. I guess what you're trying to do is most appropiate through hendriq's method.
An example of getRouteConfigcan be found at http://zf2cheatsheet.com/
public function getRouteConfig()
{
return array(
'factories' => array(
'pageRoute' => function ($routePluginManager) {
$locator = $routePluginManager->getServiceLocator();
$params = array('defaults' => array('controller' => 'routeTest','action' => 'page','id' => 'pages'));
$route = Route\PageRoute::factory($params);
$route->setServiceManager($locator);
return $route;
},
),
);
}
In our Module\Route namespace we create the class PageRoute which implements Zend\Mvc\Http\RouteInterface and, in our specific case for the example, Zend\ServiceManager\ServiceManagerAwareInterface. Now just implement the functions of the interface... In the sample he uses Doctrine to load the pages from the database.
Finally we can add our new custom route to our module.config.php so it can be used:
'page' => array(
'type' => 'pageRoute',
),
As you can see in this last step we go back to Hendriq's solution as the intended use is not to load the routes into the router, but creating custom routes.
Hope this helps
A ZF2 application contains/nneds a lot of different config files: /config/application.config.php, /config/autoload/global.php, /config/autoload/local.php, /module/***/config/module.config.php.
Now I've written a module, that covers the caching functionality for the application, and need different values for the livetime of its items in my local/dev and the live environment. I also would like to be able to switch the cache type dependent on the environment.
Where should such stuff be sored? In /config/autoload/global.php and /config/autoload/local.php? If yes: should it first retrieved from these files in the Module class (e.g. in the onBootstrap() method) or used directly, where it's needed?
(It also would be great, if someone could show a primitive example for saving and getting such config data.)
The solution, I'm currently using is:
/config/autoload/global.php and/or /config/autoload/local.php
return array(
// DB credentials
'db' => array(
'username' => ...,
'password' => ...,
'dbname' => ...,
'host' => ...,
),
'cache_ttl' => 'global/local value for cache live time',
);
Cache Module class
class Module {
private $moduleConfig;
public function onBootstrap(MvcEvent $mvcEvent) {
$application = $mvcEvent->getParam('application');
$this->moduleConfig = $application->getConfig();
}
...
public function getServiceConfig() {
try {
return array (
'factories' => array(
...
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
'ttl' => $this->moduleConfig['cache_ttl'],
...
));
},
...
)
);
}
...
}
}
It works fine, but I'm pretty sure, that it's not the best practice / recomended way.
Your basic approach is the correct one.
For cache configuration stuff, keep your production values in the global file. That should live in your VCS. (EDIT: however, you should probably omit security sensitive configuration such as database passwords. Add that stuff to production via a local.php to keep it out of version control).
In your local environment, use the local file to override anything that needs to be overridden. IIRC the ZendSkeletonApplication has a .gitignore file that will ignore any local configs built in -- so your local configuration never makes it into git.
However, you don't need to mess around with loading the config on bootstrap like you are. You can just grab the config from the serviceManager inside your factory method:
public function getServiceConfig() {
try {
return array (
'factories' => array(
...
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions(array(
// you can just grab your config from the service-manager
'ttl' => $serviceManager->get('Config')['cache_ttl'],
...
));
},
...
)
);
}
...
}
Also - I wouldn't stick 'cache_ttl' as a top-level config key. Instead, try:
global.php
return array(
'cache' => array(
'ttl' => ...,
'servers' => ...,
...
)
);
That simplifies your factory to just something like:
'Zend\Cache\Adapter\MemcachedOptions' => function ($serviceManager) {
return new MemcachedOptions( $serviceManager->get('cache') );
},
and you can override whatever you want in your local.php config. If all you want to do is change the ttl (leaving all the other global configs):
local.php
return array(
'cache' => array('ttl'=>...)
);
My directory structure is like this:
c:\Workspaces\Zend
c:\Workspaces\Custom library
Custom library is a shared library, which is in use in other applications. It doesn't use namespaces, just old style underscores.
I downloaded the ZF2-Restful-Module-Skeleton which i intend to use as a restful server. In the InfoController I have this code:
namespace Main\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
class InfoController extends AbstractRestfulController
{
public function getList()
{
$data = array(
'phone' => '+30123456789',
'email' => 'email#domain',
);
$Res = new CL_Res_Chain_Mutable();
return $data;
}
}
Error message is:
Fatal error: Class 'Main\Controller\CL_Res_Chain_Mutable' not found in C:\Workspaces\Zend\module\Main\src\Main\Controller\InfoController.php
Obviously, I need to add this custom library to my Zend application, but Im "little" lost here, I really don't know how to do this. I have googled couple solutions, but none of them seem to be like this.
Also, I have another library in folder c:\Workspaces\Custom library 2, which has (among other files) file(class) D.php, which I have used like D::dump($data);
How can I get it to work in my Zend application like that?
You need to configure the StandardAutoloader to load your library classes. The easiest way is to modify the Application module's Module::getAutoloaderConfig() method so that it looks something like this:
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
'prefixes' => array(
'CL' => 'c:\\Workspaces\\Custom library/CL',
'D' => 'c:\\Workspaces\\Custom library 2/D',
),
),
);
}
I've added a prefixes key and then listed the prefix name and where to find it on disk. The Standard Autoloader documentation has more details.
If you are working with a Zend Skeleton Application you may also simply add these namespaces to your init_autoloader.php file.
The namespace of your class is Main\Controller. If you instanciate a new class here new CL_Res_Chain_Mutable() php will load it relative to the current namespace Main\Controller\CL_Res_Chain_Mutable. Your class is not a namespaced class so you need to load it from the root. Just put a \ in front new \CL_Res_Chain_Mutable().
By default your application will be using the Standard Autloader (PSR-0). This will find your files based on a namespaces, and a naming convension used by ZF2.
ZF2 will allow you to register multiple Autoloaders, so you can use different strategies, which is what you will need to do, here's an example:
Module.php
/**
* Get autoloader config
*
* #return array
*/
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
// File containing class map key/value pairs
__DIR__ . '/library/autoloader_classmap.php',
// Or provide an array with the class map instead...
array(
'Application\Bootstrap' => __DIR__ . '/application/Bootstrap.php',
'CL_Res_Chain_Mutable' => __DIR__ . '/library/pathhere/Mutable.php',
),
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
This setup will use tell ZF2 to check the class map first, if it can't find what it's looking for it will revert to the standard autoloader.
http://framework.zend.com/manual/2.0/en/modules/zend.loader.class-map-autoloader.html