Access ZF2 module service from non-Zend application - php

I am building a Zend application with a number of modules. I would like to reuse some of these modules within other projects which are not using the Zend Framework.
I have used included the init_autoloader.php in a non-Zend test script and can access the Zend Framework classes as expected.
I can also set up my autoloader to load classes from my module directly.
What I'm aiming to do is to load the module and access its services from its Module class from the getServiceConfig() function.
Using ModuleManager, I have set up DefaultListeners and can load the module.
use Zend\ModuleManager\Listener;
use Zend\ModuleManager\ModuleManager;
chdir(dirname(__DIR__));
// Instantiate and configure the default listener aggregate
$listenerOptions = new Listener\ListenerOptions(array(
'module_paths' => array(
'./module',
'./vendor'
)
));
$defaultListeners = new Listener\DefaultListenerAggregate($listenerOptions);
// Instantiate the module manager
$moduleManager = new ModuleManager(array(
'Group',
));
// Attach the default listener aggregate and load the modules
$moduleManager->getEventManager()->attachAggregate($defaultListeners);
$moduleManager->loadModules();
What I would like to do is instantiate an instance of ServiceManager and be able to get the services from the module as I can within the application.
Any help would be much appreciated.
Thanks.

You can create just the service manager like so:
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
/* Insert your code to setup whatever autoloader you want */
$configuration = include 'config/application.config.php';
$serviceManager = new ServiceManager(new ServiceManagerConfig());
$serviceManager->setService('ApplicationConfig', $configuration);
$serviceManager->get('ModuleManager')->loadModules();
If you want to only use a specific module(s) then create a copy of your config/application.config.php that will only include the module(s) you want. Note, you can just place the config array directly into $configuration in the code above rather than including a file if you desire.

Related

ZF2 Model used in many modules using zend global config

I have a Class object which is used in many modules in my zend structure :
/module/
--|Aplication
--|MyClassModule
----|config
----|src
------|Factory
------|Model
---------|> MyObjectClass.php
----Module.php
--|AnotherModule
So my idea is to use this MyObjectClass.php in other modules so I can avoid duplication and have its own configuration. So far, for this is ok, however I want to get the variables set from my config/autoload files injected in this class but I don't know how.
How can I load this config data into my class model? Which is the best approach ? I can load it by accessing this directly but I don't think this is very elegant
e.g: $configArray = require './config/autoload/config.local.php';
I am not very experienced with zend so I dont know where to start with. I have seen many tutorials of how to do this via controllers, views.. etc but not in specific classes.
Thank you.
All config files are merged into one config, when your ZF2 application is bootstrapped. That includes local.php, global.php from config/autoload and all used modules' module.config.php. With a bit of more research, you can overwrite the standard loading, e.g. loading custom configs.
After bootstrapping, your are able to access the config from the ServiceManager. There are preserved keys for some ZF2-specific configs, service_manager, etc.
$serviceManager->get('config');
There is a "standard" service pattern in ZF2: Factory. This can be applied for Controllers, Services. What ever you want.
namespace Application\Factory;
use Application\Model\MyObjectClass;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MyObjectFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
// get some config parameter, inject it into model
$config = $serviceLocator->get('config');
$myObjectClass = new MyObjectClass();
// ... e.g. $myObjectClass->setConfig($config);
return $myObjectClass;
}
}
It should be clear, what this factory is made for: create and return an instance of your custom object ;) You may configure your instance with some config params. With ServiceLocator as method param, you are able to access the config, other services etc.
Further, you have to register your own service/factory in the factories section of service_manager config in your module's module.config.php:
return array(
'service_manager' => array(
'factories' => array(
'MyObjectFactory' => 'Application\Factory\MyObjectFactory',
),
),
);
Now you should be able to access your factory, e.g. in an ActionController or wherever you have access to ServiceManager. That means, you can also access this factory from different modules.
public function someCustomAction() {
$myObjectClass = $this->getServiceLocator()->get('MyObjectFactory');
$myObjectClass2 = $this->getServiceLocator()->get('MyObjectFactory');
var_dump($myObjectClass);
var_dump($myObjectClass2);
if ($myObjectClass === $myObjectClass2) {
echo '<br />equal';
}
$myObjectClass = new MyObjectClass();
$myObjectClass2 = new MyObjectClass();
var_dump($myObjectClass);
var_dump($myObjectClass2);
}
Note:
Be aware, that ServiceManager returns the same instance of your object. So, that seems like what you ask for? In contrast, creating a new instance will create different objects.
Note 2:
Tested with ZF2 v2.4.9

How to integrate Elasticsearch in Zend Framework 2 using Doctrine 2

I followed this tutorial for integrating doctrine with Zend Framework 2. Now, all is working fine, but I would like to integrate Elasticsearch to this project.
I found a lot of documentation about Elasticsearch, I downloaded the Elastica plugin for PHP, but I don't know where to get started.
I searched tutorials to integrate Elasticsearch with Doctrine, but they're all about Symfony.
Could someone please explain me (in a simple way) how to use ElasticSearch in Zend Framework 2, using Doctrine 2 as ORM to index and search through my objects?
There are no direct relation exists between Doctrine 2 and Elasticsearch. While primary concern of Doctrine ORM is persisting, updating and reading data on relational databases, elasticsearch mainly focuses on indexing and searching that data. Instead of thinking about "integrating elasticsearch with doctrine", think about "how can i use both doctrine and elasticsearch in same application".
Whenever you create or update a record in database, you would probably want to do more operations like indexing that data on Elasticsearch or Solr, caching or invalidating already cached version of same data on Memcached or Redis etc.. To do that properly (or zf2 way), you should carefully design a service layer which orchestrates both persistency operations and related post-processes like indexing on elasticsearch, caching, cache invalidating, logging etc..
Accomplishing some of these operations by firing some events via EventManager would be an appropriate decision.
Note: Don't utilize EventManager heavily for simple & straightforward tasks such as writing a log line. Events are not free, especially in ZF2. (Quite improved in ZF3, but still not free).
For question, here is the way to go with Zend Framework 2 while utilizing a 3rd party library which is ruflin/elastica:
A. Open your terminal and type
$ cd /path/to/your/project
$ php composer.phar selfupdate
$ php composer.phar require ruflin/elastica:dev-master
B. Create a factory for elastica client Application\Service\ElasticaClientFactory.php
<?php
namespace Application\Service;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class ElasticaClientFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $sl)
{
$config = $sl->get('Config');
$clientOptions = isset($config['elastica']) ? $config['elastica'] : array();
$client = new \Elastica\Client($clientOptions);
return $client;
}
}
C. Add elastica configuration and register new factory class to service locator in your module.config.php:
'elastica' => array(
'servers' => array(
array('host' => '127.0.0.1','port' => 9200),
// You can add more servers when necessary
// array('host' => '127.0.0.1','port' => 9200)
),
),
service_manager' => array(
'factories' => array(
'elastica-client' => 'Application\Service\ElasticaClientFactory'
),
)
At this point, in any controller (bad) or service (good) you can grab the elastica client instance like this:
$elastica = $this->getServiceLocator()->get('elastica-client');
Bonus: Using Service Initializers and Traits
If your PHP version is ≥ 5.4 you can use traits while automatically injecting the Elastica Client into your services with the help of service initializers.
D. Create a new interface named Application\Service\ElasticaAwareInterface.php
<?php
namespace Application\Service;
interface ElasticaAwareInterface
{
public function getElasticaClient();
public function setElasticaClient(\Elastica\Client $client);
}
E. Create a new trait named Application\Traits\ElasticaAwareTrait.php (Notice the path. Create the Traits folders if doesn't exists)
<?php
namespace Application\Traits;
trait ElasticaAwareTrait
{
protected $client = null;
public function getElasticaClient()
{
return $this->client;
}
public function setElasticaClient(\Elastica\Client $client)
{
$this->client = $client;
return $this;
}
}
F. Create a new initializer named Application\Initializers\ElasticaInitializer.php (Notice the path, again)
<?php
namespace Application\Initializers;
use Zend\ServiceManager\InitializerInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Service\ElasticaAwareInterface;
class ElasticaInitializer implements InitializerInterface
{
/**
* Initializer for the elastica-aware domain services.
* Properly creates a new elastica client and injects it into related service.
*/
public function initialize($service, ServiceLocatorInterface $serviceManager)
{
/**
* Beware: This if statement will be run for every service instance
* we grab from $serviceManager because the nature of initializers.
* This worth think about on it. With ZF3 this went further.
* We may load our services lazily using delegator factories.
*/
if ($service instanceof ElasticaAwareInterface) {
$service->setElasticaClient( $serviceManager->get('elastica-client') );
}
}
}
So far so good. Now, we can put together all parts. Say, we have a service named UserService which uses a Doctrine entity manger (or UserRepository better), also needs to use Elastica.
G. Register our service to service manager:
service_manager' => array(
'factories' => array(
'elastica-client' => 'Application\Service\ElasticaClientFactory'
),
'invokables' => array(
'user-service' => 'Application\Service\UserService'
)
)
Finally, the UserService signature:
<?php
namespace Application\Service;
use Application\Service\ElasticaAwareInterface;
class UserService implements ElasticaAwareInterface
{
// Here is our trait
use \Application\Traits\ElasticaAwareTrait;
public function fooMethod()
{
// Do some things with doctrine EM here..
// And grab the elastica client easily anywhere in UserService
$client = $this->getElasticaClient();
}
}
There are some (still under heavy development) modules out there that combine Doctrine2 and ElasticSearch. For example check out this module called OcraElasticSearch from the Doctrine2+ZF2 Master #Ocramius himself :)
Otherwise it seems that Symfony2 is a step ahead when it comes to integrating Doctrine and ElasticSearch.
I bet within one or two months you should be able to find more alternatives on GitHub.
Since we need similar functionality soon I might discover more later. If I do I will update my post.

Symfony :HOW TO Create Shared -General- (Helper) used in multiple bundles

I had many helper function -Grouped In Classes - for ( formatting Strings And Dates , URL Helpers ) that i want to use and share in several bundles , i need to know best practice about where i can put those helpers functions to be shared between bundles.
What came to my mind is to create a helper bundle and use this bundle in the other bundle that i have in my project or use the vendor helper.
So how i can do this and what is the best practice for Creating Shared Helper to be used in multiple bundles.
Please if there is any references i can look at please share it with me.
Thank you in advance.
Best practice would be to create a PHP library containing those classes. If you really need Symfony integration (eg. DIC configuration), then create bundle that depends on this library.
Every bundle that uses your bundle must list it among it's dependences in composer.json. So it will be installed autocratically every time you install bundle that depends on it.
There are plenty of great examples of libraries out there, that can be imported using composer and used even if they aren't bundles per se, take a look at Doctrine\Common for example.
Regardless, you can also create the bundle as you would any other bundle in Symfony, and structure the code as you see fit. You will notice with many of Doctrine's bundles will make use of the shared library Doctrine\Common.
If You have universal classes it should be grouped in one bundle ("Helper bundle" as You said) and if it is possible in Your case classes should be defined as services.
If You are using this bundle in more than one project and You want to upgrade that in the future, You should think about moving this bundle to separate repo and define it as a "standalone" bundle (so You can include that in your projects by composer and vendors directory.
I think Best practice is create helper Bundle and create service in helper bundle
Then you can use service in several bundle.
dummy example: in your service Helper.php
namespace HelperBundle\Services;
class Helper{
protected $url;
public function __construct(){
}
}
in ProfileTreeUserExtension.php in Dependency Injection folder
confirm that services configuration file which loaded is sevices.yml
namespace HelperBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class ProfileTreeLayoutExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
in service.yml
services:
helper.service:
class: HelperBundle\Services\Helper
Then you can call HelperService in several bundles Just
$helper = $this->container->get('helper.service');
you can also extends Helper class in another service
use HelperBundle\Services\Helper;
class AnotherService extends Helper{}
There good article about Service Container and Dependency Injection

How can I bootstrap my dependency injector services in Phalcon unit tests?

I went through the Phalcon docs and got PHPUnit set up and working. However, I'm having trouble bootstrapping my existing services into the unit testing framework.
Currently, I load my config, routes, and services in the /public/index.php. For example, I'm loading the session library:
$di->set(
'session',
function () use ( $config ) {
$session = new Phalcon\Session\Adapter\Redis(
... config ...
);
});
So now, in my application I'd have code that calls $this->session->get( 'user_id' ) to use the session library. I have many of these services -- one for SQL, MongoDB, cookies, etc.
The problem I'm having is correctly loading these services into the unit testing class. The Phalcon docs recommend loading my DI services in the UnitTestCase class (explained here) but I really do not want re-define the same services in this class loader. Additionally, I want to use the components in my application in the same ways; those components rely on the services being lazy-loaded.
So, to attempt this, I include the same /app/config/services.php file in my unit testing TestHelper.php init script thinking that I can just use the same $di object. This works in that my test cases can call $this->di->getSession()->get( 'var' ) but as soon as a component in my app tries to call $this->session->get( 'var' ) it throws an error:
1) Libraries\SessionTest::testGetSet
Undefined property: Libraries\SessionTest::$session
/home/mike/PhalconProject/app/library/Session.php:25
/home/mike/PhalconProject/tests/libraries/SessionTest.php:13
This error is telling me that my application session-management library Session.php is failing when accessing $this->session-> via the dependency injector.
Is there something I'm fundamentally doing wrong here? Do I need to redefine the services in my unit testing class? And if I do need to redefine them, will my application be able to load its services?
So the solution here seems to be structuring the application libraries to statically access the DI services.
The first step was to set the default DI in /public/index.php and /tests/bootstrap.php:
\Phalcon\DI::setDefault( $di );
Now, in the application libraries (like /app/Library/Auth.php or /app/Library/Session.php) the services are accessed statically (more info here in the Phalcon docs):
$session = \Phalcon\DI::getDefault()->getSession();
$session->get( 'user_id' )
...
To make this easier, I set up a base library that all of my application libraries extend. The base library has a method to simplify this call.
namespace Base;
use \Phalcon\DI as DI;
class Library extends \Phalcon\Mvc\User\Component
{
public function getService( $service )
{
$func = "get". ucfirst( $service );
return DI::getDefault()->$func();
}
}
Finally, /app/Library/Session.php would extend this \Base\Library class, and whenever I need the session service in a method I can just call:
$session = self::getService( 'session' );
I have injected all my Phalcon services (which i usually get by calling DI object) using the simple PHPUnit bootstrap.php file (take a look at --bootstrap option of PHPUnit) my bootstrap.php contents is:
<?php
require_once(__DIR__ . '/../app/config/local_config.php');
$config = include __DIR__ . "/../app/config/config.php";
/**
* Read auto-loader
*/
include __DIR__ . "/../app/config/loader.php";
/**
* Read services
*/
include __DIR__ . "/../app/config/services.php";
Thus, in my PHPUnit tests i just call $di->get('MyService');

Integrating ZF/Doctrine2: Where do i put my Models/Entities & Proxy classes

if i do integrate Zend Framework 1.10 with Doctrine 2 where do i put my Doctrine Models/Entities and Proxies? i thought of the /application or the /library directories. if i do put in the /library directory tho, will it interfere with ZF autoloading classes from there since the classes there will be using PHP 5.3 namespaces vs PEAR style namespaces.
I'm working on an application that integrates Doctrine 2 with ZF1.10 also.You don't need to use the Doctrine auto loader at all.
1) In your application.ini file add the following line (assuming you have Doctrine installed in your library folder (same as the Zend folder):
autoloadernamespaces.doctrine = "Doctrine"
2) Create a doctrine or entitymanager resource. In your ini file:
resources.entitymanager.db.driver = "pdo_mysql"
resources.entitymanager.db.user = "user"
resources.entitymanager.db.dbname = "db"
resources.entitymanager.db.host = "localhost"
resources.entitymanager.db.password = "pass"
resources.entitymanager.query.cache = "Doctrine\Common\Cache\ApcCache"
resources.entitymanager.metadata.cache = "Doctrine\Common\Cache\ApcCache"
resources.entitymanager.metadata.driver = "Doctrine\ORM\Mapping\Driver\AnnotationDriver"
resources.entitymanager.metadata.proxyDir = APPLICATION_PATH "/../data/proxies"
resources.entitymanager.metadata.entityDir[] = APPLICATION_PATH "/models/entity"
3) Next, you will need to bootstrap it. I added a resource class in my resources folder. Make sure you map to the folder in your ini file:
pluginPaths.Application_Resource_ = APPLICATION_PATH "/resources"
Then your resource class...
class Application_Resource_EntityManager
extends Zend_Application_Resource_ResourceAbstract
{
/**
* #var Doctrine\ORM\EntityManager
*/
protected $_em;
public function init()
{
$this->_em = $this->getEntityManager();
return $this->_em;
}
public function getEntityManager()
{
$options = $this->getOptions();
$config = new \Doctrine\ORM\Configuration();
$config->setProxyDir($options['metadata']['proxyDir']);
$config->setProxyNamespace('Proxy');
$config->setAutoGenerateProxyClasses((APPLICATION_ENV == 'development'));
$driverImpl = $config->newDefaultAnnotationDriver($options['metadata']['entityDir']);
$config->setMetadataDriverImpl($driverImpl);
$cache = new Doctrine\Common\Cache\ArrayCache();
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);
$evm = new Doctrine\Common\EventManager();
$em = Doctrine\ORM\EntityManager::create($options['db'],$config,$evm);
return $em;
}
}
The doctrine 2 entity manager is now available to your application. In your controller you can grab it like so:
$bootstrap = $this->getInvokeArg('bootstrap');
$em = $bootstrap->getResource('entitymanager');
I am sure this will help somebody :)
In theory, you could put then anywhere, as long as the namespaces resolve correct.
I would suggest this structure:
/application/models/MyApp/Entities
/application/models/MyApp/Proxies
Load the 'MyApp' using Doctrine's ClassLoader. I've had no conflicts using the Doctrine loader with the Zend Loader (if you have classes that use the PEAR convention inside your namespace folder, you will still need to use the Zend Loader).
Remember that 'models' can be more than just your Entity classes. My model layer consists of interfaces, factories, validators and service objects. To that end, anything that is application specific business logic should probably go in the model folder.
I would put the Models in the same directory where the "normal" Zend Framework models life. /models
You can tell Doctrine to generate the models at this place, and prefix them with "Default_Model" or whatever.
Check out one of John Lebenshold Screencasts about Zend Framework and Doctrine
Zend Screencasts

Categories