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.
Related
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
I don't know maybe it is me or may be it is symfony that is lame, please why is symfony entity class not having utility functions like these:
//Assume product is an entity class
$product = Product::findById($id);
$productExample = new Product(array("price"=>20, "vendor"=>'friends'));
$products = Product::findByExample($productExample);
$Dql = "SELECT * FROM product p WHERE p.id IN (SELECT si.pid FROM solditem si WHERE si.sold_date BETWEEN 1-1-2017 AND 2-2-2017 ORDER BY si.price)";
$products = Product::findByDql($Dql) or Product::findBySql($sql);
$product = new Product(array('id' => $id)); // I should be able to get the entity with that Id
In other frameworks like Zend (even small and simple ones like cakePHP, codeIgniter) it is very easy to implement functions like these and they will be callable everywhere, you will not have to be running after one entityManagers or entityCEOs.
Is there an easy way to implement way to implement these in symfony, if there is please let me know, if not please give me why.
Thanks
In general I suggest you read a little bit more about the documentations of the frameworks you are comparing.
I don't know maybe it is me or may be it is symfony that is lame, please why is symfony entity class not having utility functions like these
To be honest in this case it is you ;-) ... no offense, but the systems are implementing different storage layers. Other frameworks like CodeIgniter, CakePHP and Laravel are implementing the Active Record Pattern. The latter e.g. by providing the Eloquent ORM
The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table.
Symfony on the other hand is using Doctrine 2 ORM as storage layer:
As the term ORM already hints at, Doctrine 2 aims to simplify the translation between database rows and the PHP object model. The primary use case for Doctrine are therefore applications that utilize the Object-Oriented Programming Paradigm. For applications that do not primarily work with objects Doctrine 2 is not suited very well.
You can make up your own mind which one you favour, but it is not an easy task to swap the storage layer in these frameworks. If you search for it I think you will find some discussions around this topic.
In Symfony Entity Repositories are usually the place where you define your desired functionality:
When you query for a particular type of object, you always use what's
known as its "repository". You can think of a repository as a PHP
class whose only job is to help you fetch entities of a certain class.
Create a custom repository class following the official documentation or take it a step further and set up your own repository class without using Doctrines EntityRepository as described in this highly recommended article.
There is no one preventing you from adding static functions to it, but the recommended way is to make a service out of your repository, e.g.
Repository
// src/AppBundle/Product/DoctrineBasedProductRepository.php
namespace AppBundle\Product;
use Doctrine\ORM\EntityManager;
class DoctrineBasedProductRepository implements ProductRepository
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function all()
{
// Create a basic DQL query to fetch all entities
return $this->entityManager->createQuery('SELECT p FROM '.Product::class.' p')
->getResult();
}
...
}
Service definition
# app/config/services.yml
services:
app.product_repository:
class: AppBundle\Product\DoctrineBasedProductRepository
arguments: ['#doctrine.orm.entity_manager']
Now you can use the repository e.g. in your controller:
public function listAction()
{
$products = $this->get'app.product_repository')->all();
// ... do something, like pass the $products object into a template
}
The latest update of zend-mvc has caused a break in compatibility due to phasing out ServiceLocatorAwareInterface. I use the servicelocator within a controller to dynamically load dependencies, eg:
class IndexController extends AbstractActionController
/**
*
* #return \UserManagement\Form\User\Details
*/
protected function getUserDetailsForm(){
return $this->getFormManager()->get('User\Details');
}
/**
*
* #return FormElementManager
*/
protected function getFormManager(){
return $this->getServiceLocator()->get('FormElementManager');
}
}
This is now raising an exception (E_USER_DEPRECEATED) with the following message:
You are retrieving the service locator from within the class
User\Controller\IndexController. Please be aware that
ServiceLocatorAwareInterface is deprecated and will be removed in
version 3.0, along with the ServiceLocatorAwareInitializer. You will
need to update your class to accept all dependencies at creation,
either via constructor arguments or setters, and use a factory to
perform the injections.
My question is, what is the best way of getting the forms into the controller? My service layer and other controller-specific dependencies are injected into the constructor, but i don't really want to polluate a constructor with all the forms that a controller may need, nor do i want the overhead of creating form objects that will not be used. The forms cannot be created in the controller ie $form = new Form() as they are also created dynamically eg:
Module.php
public function getFormElementConfig ()
{
return array(
'factories' => array(
'User\Details' => function($sm){
$userMapper = $sm->getServiceLocator()->get('Model\User\Mapper');
$form = new \User\Form\Details($userMapper);
return $form;
}
)
);
}
Have more and more specific controllers.
That way you can instantiate one controller and then have to inject exactly all the objects that are definitely needed to perform any task you may need.
There is no use to combine actions into one single controller class if all they share is a common URL path fragment. From the software design point of view, a class that does plenty of independent things in different methods is just doing too much. And doing too much is reflected in the number of dependencies you have to inject.
There are few solutions.
Make controllers smaller. Less methods, less dependencies. However, more factories.
Use the ZF2 proxy manager. It essentially replaces expensive object instantiation with proxy objects.
Another option would be to add a wrapper or container that lazy loads the forms via the form element manager. You can then inject this container into the controller or service layer. Using a service locator in this way isn't "ideal" as you loose the explicitly defined class dependencies.
I have a FormElementLazyProvider class and its associated factory that might be worth checking out.
For example.
$elementConfig = [
'create' => 'MyModule\Form\CreateForm',
'edit' => 'MyModule\Form\EditForm',
'delete' => 'MyModule\Form\DeleteForm',
];
$factory = function($serviceName, $elementName) use ($formElementManager) {
if ($formElementManager->has($serviceName)) {
return $formElementManager->get($serviceName);
}
return false;
};
$provider = new \ArpForm\Service\FormElementLazyProvider($factory, $elementConfig);
$provider->hasElement('create'); // true
$provider->getElement('edit'); // Callback executed and form object return
The crux of the problem is the service locator hiding the dependencies of the controller class.
The Zend framework is instructing you to move away from the ServiceRepository pattern and use proper dependency injection using DI containers or using constructor injection or setter injection. You can use a factory to inject the dependencies as well.
Please read about Service Repository which many see it as an anti-pattern.
In my job I am dealing with a legacy app running on ZF2. There is a model which is sending out a variety of different emails to difference addresses. The one thing they have in common is they all need to BCC to one particular address.
At first, in my head I was cursing the previous developer, for foolishly hard coding the email address 20 different times in one file. I assumed that it would be a piece of cake to grab an application config with a simple call $this->config->get('x') (like in Laravel) or something along them lines. Now I find myself feeling bad, because I understand why the previous dev did hard code the email addresses.
So to the question, how the hell do I grab a config item from application.config.php inside the model? I keep reading about how I need to implement the ServiceLocaterAware Interface. Is this really necessary? There must be a way to grab configs easily, surely?!?
How the hell do I grab a config item from application.config.php inside the model?
You shouldn't do so inside, do it 'outside'.
Register your model class as a service in module.config.php.
'service_manager' => [
'factories' => [
'Email\Model\EmailModel' => 'Email\Model\EmailModelFactory',
]
],
Then create the factory Email\Model\EmailModelFactory, this uses the service manager to fetch the 'email' config key and injects it into the model's constructor.
namespace Email\Model;
use Email\Model\EmailModel;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class EmailModelFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new EmailModel($this->getEmailOptions($serviceLocator));
}
// Return the email options
public function getEmailOptions(ServiceLocatorInterface $serviceLocator)
{
$options = $serviceLocator->get('config');
return $options['email'];
}
}
The issue you will now have is all calls to your model classes will have to use $serviceManager->get('Email\Model\EmailModel') (rather than new \Email\Model\EmailModel) in order for the configuration to be injected. Even without seeing any of your legacy application my guess is that this would be difficult.
The model should not be responsible for sending emails; you could replace it with an service class, e.g. 'EmailService' and repeat the injection example above just for this class.
EmailService::send(EmailModel $email, array $options);
This would keep your model independent and there would be no need to replace the calls to new Model etc.
You need the service locator / service manager
Within your controller:
public function xxxAction()
{
$sm = $this->getServiceLocator();
$config = $sm->get('config');
}
I have just started using ZF2 and am really enjoying it.
One thing that puzzles me a bit is the absence of a Registry component. I realise that the Service Manager makes the Registry obsolete in most cases. I rely on it heavily and its great.
But from time to time I find myself needing access to a 'global' object, and I don't have access to the Service Manager. For example, in my Domain\User object I need access to a Zend\Log.
I don't want to make the Service Manager available in my Domain objects, since they are beautiful and pristine, and unadulterated by such considerations. I could 'new' a log instance whenever required, but I do it so often I'd rather have a preconfigured instance to hand. I could wrap it in a singleton, but that seems like a backward step. I could create my own mini-registry, but if that was a good idea, I'm sure the Zend guys would have left such a component in place.
So, what other options are there?
EDIT:
So, could I use Zend DI perhaps? I see this question partially covers it, Configuring class alias for using with Zend\Di\Di
You are exactly addressing the problem of dependency injection. I have blogged about this topic before, how to refactor towards dependency injection and what to do if you have soft dependencies like a Logger.
The idea is you inject the pre-configured logger. When your Domain\User object is created, the logger is injected. This makes the Domain\User object only dependent on the logger without having the knowledge how to create the logger. It's even better if you rely on a Logger interface, so you can swap to any logger implementation you want.
Example
As an example, I assume you are using Zend\Log. You have a Logger like Zend\Log\Logger with various writers attached. The logger implements Zend\Log\LoggerInterface.
Your Domain\User class:
namespace Domain;
Zend\Log\LoggerInterface;
class User
{
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function doSomething()
{
$this->logger->info('Do Something');
//
}
}
For Zend Framework, you should work with factories to inject this logger into your objects. The best way is to define the Logger as a service first, as you could reuse the logger for other objects as well.
Note I use the Service Manager and not Zend\Di here. Zend\Di is obsolete and replaced by the faster and more flexible Zend\ServiceManager. Both can achieve dependency injection.
The factory for you logger:
namespace MyModule\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Log\Logger;
class LoggerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$logger = new Logger;
// do more with $logger here
return $logger;
}
}
Then register this factory to create the service "logger" for you. In your module.config.php:
'service_manager' => array(
'factories' => array(
'logger' => 'MyModule\Factory\LoggerFactory',
),
),
Now logger is available in your service manger. For your domain model, do the same. Create a factory first:
namespace MyModule\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Domain\User;
class UserFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$logger = $serviceLocator->get('logger');
$user = new User($logger);
return $user;
}
}
Then register this one too:
'service_manager' => array(
'factories' => array(
'logger' => 'MyModule\Factory\LoggerFactory',
'Domain\User' => 'MyModule\Factory\UserFactory',
),
),
If you have access to the service locator, you can get the Domain\User and the logger is automatically injected:
$user = $sl->get('Domain\User');