The zend console is deprecated. I will use symfony/console (as suggested): composer require symfony/console
With the ZF 2 & 3 I have some years of experience. With Symfony I have done different things, even several console commands.
My skeleton for a symfony console command:
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class InstallCommand extends Command
{
/**
* #var string The name of the command (the part after "bin/console")
*/
protected static $defaultName = 'app:modulename-install';
protected function configure()
{
$this->addArgument('test', InputArgument::REQUIRED, 'Only a test.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$text = 'your argument: ' . $input->getArgument('test');
$output->writeln($text);
}
}
1. problem: symfony console has none *Action()'s in classes *Command. How can i configure a route?
2 problem: there is no /vendor/bin/console file.
How can I implement symfony 4.x console command in Zend Framework 3?
...the internet is silent on this topic
According to Symfony documentation each console command is a separate class inherited from Symfony\Component\Console\Command\Command and you need to implement your command runtime code inside execute() method. Because of this there is no routing is involved. Actually you can think about your console command class itself as an action because Symfony's console Application includes routing.
Question about bin/console file is a bit tricky. Normally when you're working with Symfony 4 itsef you're usually using Symfony Flex. Flex itself connects Composer packages with so called "recipes" that resides into separate service. Flex uses these recipes to allow automatic configuration of packages that are installed for Symfony application through Composer. Because of this fact bin/console command is not a part of Composer package, but instead part of Flex recipe for symfony/console package. You can fine it out by yourself inside official recipes repository.
I have almost zero knowledge about ZF3, but in a case if you're using console as a standalone component you may decide either to deal with Symfony's DI container and store all services (including commands) inside it or to manually register every command in console application by using Application::add() method.
It's been a realllllyyy long time since I created/updated this, so not too sure how this all fit together anymore.
But! I can give you an example.
This is this no longer maintained module on Github which I found quite handy in the days of ZF2, and I've sort of upgraded it in a few projects of mine where I wanted it, but never into a fully blown ZF3 module. But it works :)
Purpose: run fixtures (yea I know, php bin/console doc:fix:load -n in SF4 :p)
If I remember right, you could use this with: ./vendor/bin/doctrine-module data-fixture:import from the project root and have your Fixture classes registered like so:
...,
'doctrine' => [
...,
'fixture' => [
FQCN::class,
FQCN2::class,
],
],
So, here it goes (if you copy this, mind the namespaces for folder structure, based on ZF3 with all in src/ folder):
A Module.php class:
<?php
namespace DoctrineFixture;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use DoctrineFixture\Command\ImportCommand;
use DoctrineFixture\Service\FixtureFactory;
use Interop\Container\ContainerInterface;
use Zend\EventManager\EventInterface;
use Zend\ModuleManager\Feature\ServiceProviderInterface;
use Zend\ModuleManager\ModuleManager;
class Module implements ServiceProviderInterface
{
public function init(ModuleManager $e)
{
$events = $e->getEventManager()->getSharedManager();
// Attach to helper set event and load the entity manager helper.
$events->attach(
'doctrine',
'loadCli.post',
function (EventInterface $e) {
/* #var $cli \Symfony\Component\Console\Application */
$cli = $e->getTarget();
/* #var $sm ContainerInterface */
$sm = $e->getParam('ServiceManager');
$em = $sm->get(EntityManager::class);
$paths = $sm->get('doctrine.configuration.fixtures');
$importCommand = new ImportCommand();
$importCommand->setObjectManager($em);
$importCommand->setPaths($paths);
ConsoleRunner::addCommands($cli);
$cli->addCommands(
[
$importCommand,
]
);
}
);
}
public function getServiceConfig()
{
return [
'factories' => [
'doctrine.configuration.fixtures' => new FixtureFactory(),
],
];
}
public function getConfig()
{
$config = [];
foreach (glob(__DIR__ . '/config/*.php') as $filename) {
$config = array_merge_recursive($config, include $filename);
}
return $config;
}
public function getAutoloaderConfig()
{
return [
'Zend\Loader\StandardAutoloader' => [
'namespaces' => [
__NAMESPACE__ => __DIR__ . DIRECTORY_SEPARATOR . 'src',
],
],
];
}
}
Class ImportCommand
<?php
namespace DoctrineFixture\Command;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Command for generate migration classes by comparing your current database schema
* to your mapping information.
*/
class ImportCommand extends Command
{
const PURGE_MODE_TRUNCATE = 0; // 1 = DELETE FROM, 2 = TRUNCATE
/** #var array */
protected $paths;
/** #var ObjectManager|EntityManager */
protected $objectManager;
/**
* #return int|null|void
*/
public function execute(InputInterface $input, OutputInterface $output)
{
$loader = new Loader();
$purger = new ORMPurger();
if ($input->getOption('purge-with-truncate')) {
$purger->setPurgeMode(self::PURGE_MODE_TRUNCATE);
}
$executor = new ORMExecutor($this->getObjectManager(), $purger);
foreach ($this->getPaths() as $key => $value) {
$loader->loadFromDirectory($value);
}
$executor->execute($loader->getFixtures(), $input->getOption('append'));
}
public function getPaths(): array
{
return $this->paths;
}
public function setPaths(array $paths): ImportCommand
{
$this->paths = $paths;
return $this;
}
/**
* #return ObjectManager|EntityManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* #param ObjectManager|EntityManager $objectManager
*/
public function setObjectManager($objectManager): ImportCommand
{
$this->objectManager = $objectManager;
return $this;
}
/**
* Register command
*/
protected function configure(): void
{
parent::configure();
$this->setName('data-fixture:import')
->setDescription('Import Data Fixtures')
->setHelp(
<<<EOT
The import command Imports data-fixtures
EOT
)
->addOption('append', null, InputOption::VALUE_NONE, 'Append data to existing data.')
->addOption('purge-with-truncate', null, InputOption::VALUE_NONE, 'Truncate tables before inserting data');
}
}
And a class FixtureFactory to tie it all together:
<?php
namespace DoctrineFixture\Service;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class FixtureFactory implements FactoryInterface
{
/**
* #return array|object
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$options = $container->get('config');
if (! isset($options['doctrine']['fixture'])) {
return [];
}
return $options['doctrine']['fixture'];
}
}
Related
i'm trying to load translations from database in Symfony 4. The Translator instance doesn't call the custom loader i wrote using this tutorial (https://medium.com/#andrew72ru/store-translation-messages-in-database-in-symfony-3f12e579df74).
I created dummy files in the /translation folder (messages.it.db) to trigger the loader but it doesn't get called.
services.yaml
parameters:
locales: ['it','en']
db_i18n.entity: App\Entity\Translation
services:
translation.loader.db:
class: App\Loader\DbLoader
arguments:
- '#service_container'
- '#doctrine.orm.entity_manager'
tags:
- { name: translation.loader, alias: db}
DbLoader.php
namespace App\Loader;
use Creative\DbI18nBundle\Interfaces\EntityInterface;
use Creative\DbI18nBundle\Interfaces\TranslationRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\MessageCatalogue;
class DbLoader implements LoaderInterface
{
/**
* #var EntityManagerInterface
*/
private $doctrine;
/**
* #var string
*/
private $entityClass;
public function __construct(ContainerInterface $container, EntityManagerInterface $doctrine)
{
$this->doctrine = $doctrine;
$this->entityClass = $container->getParameter('db_i18n.entity');
}
public function load($resource, $locale, $domain = 'messages')
{
$messages = $this->getRepository()->findByDomainAndLocale($domain, $locale);
$values = array_map(static function (EntityInterface $entity) {
return $entity->getTranslation();
}, $messages);
$catalogue = new MessageCatalogue($locale, [
$domain => $values
]);
return $catalogue;
}
public function getRepository(): TranslationRepositoryInterface
{
return $this->doctrine->getRepository($this->entityClass);
}
}
Here's my translation table
Here is the test code i'm using to call the Translator
TestController.php
class TestController extends AbstractController
{
/**
* #Route("/test", name="test")
*/
public function index(TranslatorInterface $translator)
{
$translator->trans('prova', [], 'messages', 'it');
return new Response();
}
}
The result is supposed to be "prova it" but I get "prova" instead, which is the key of the translation. I tried to put a dd() on the DbLoader constructor and it's never been called.
I also have in my project Api Platform, but i don't think it's causing this problem.
I resolved my issue.
By using dd() on my Translator instance i discovered that Symfony wasn't loading my translation files correctly. Looking through the properties i noticed the path of my translation files were not correct.
I placed them in src/Resources/translations instead and then it worked!
I am new at ZF3 and I need your help.
In ZF3 there is no service Locator any more. So I created a Factory class to replace the service Locator in my code.
Here is my code with service Locator in my AbstractController.php file:
protected function getService()
{
return $this->getServiceLocator()->get($service); //remove from ZF3
return $this->service = $service;
}
Now I replaced the service Locator in AbstractController.php:
protected function getService($service)
{
$service = $this->service;
return $this->service = $service;
}
And in Module.Config.php I added the following lines:
return [
'controllers' => [
'factories' => [
Controller\AbstactController::class => Controller\AbstactControllerFactory::class,
],
],
And I created a AbstractControllerFactory file with the following lines:
<?php
namespace Application\Controller;
use Application\Controller\AbstractController;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class AbstractControllerFactory implements FactoryInterface
{
protected function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new AbstractController($container->get(service::class));
}
}
I need to know if this is a correct migration from ZF2 to ZF3?
In ZF3 the first thing you do is create your Service and a Factory for it. Let's take in this example UserManager.php in Services folder.
So we have in folder Service -> UserManager.php and in Service -> Factory -> UserManagerFactory.php
UserManagerFactory.php:
<?php
namespace User\Service\Factory;
use Interop\Container\ContainerInterface;
use User\Service\UserManager;
/**
* This is the factory class for UserManager service. The purpose of the factory
* is to instantiate the service and pass it dependencies (inject dependencies).
*/
class UserManagerFactory
{
/**
* This method creates the UserManager service and returns its instance.
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$entityManager = $container->get('doctrine.entitymanager.orm_default');
return new UserManager($entityManager);
}
}
UserManager.php:
<?php
namespace User\Service;
use User\Entity\User;
/**
* This service is responsible for adding/editing users
* and changing user password.
*/
class UserManager
{
/**
* Doctrine entity manager.
* #var Doctrine\ORM\EntityManager
*/
private $entityManager;
/**
* Constructs the service.
*/
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
// REST OF YOUR CODE
}
Now that we have our service, we go into User\config\modules.config.php:
'service_manager' => [
'factories' => [
Service\UserManager::class => Service\Factory\UserManagerFactory::class,
],
],
Well that's basically it, we can inject the service in our controller and job done:
<?php
namespace User\Controller;
use Zend\Mvc\Controller\AbstractActionController;
/**
* This controller is responsible for user management (adding, editing,
* viewing users and changing user's password).
*/
class UserController extends AbstractActionController
{
/**
* User manager.
* #var User\Service\UserManager
*/
private $userManager;
/**
* Constructor.
*/
public function __construct($userManager)
{
$this->userManager = $userManager;
}
// REST OF YOUR CODE
}
I really hope this helps you understand how to use Service in ZF3.
Good Luck!
I have App\Http\NotificationComposer.php:
namespace App\Http\ViewComposers;
use Illuminate\View\View;
class NotificationComposer
{
public $notifications;
public function __construct(){
$this->notifications = json_decode(\Auth::user()->notifications, true);
}
public function compose (View $view)
{
dd($this->notifications);
$view->with('notifications');
}
}
I also have a App\Providers\ComposerServiceProvider.php:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
view()->composer(
'app',
'App\Http\ViewComposers\NotificationComposer'
);
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
And in my config\app.php:
'providers' => [
...
App\Providers\ComposerServiceProvider::class,
...
],
];
I believe I have set up the view composer correctly, however everytime layouts\app.blade.php is loaded (the default bootstrap bar in laravel) it isn't rendering any notifications, even though there are some in the database, I have attempted to dd them as you can see in the view composer.
Does anyone have any ideas why this might be happening or what I haven't done properly?
Thanks,
Ok I've made a workaround instead. This wasn't registering for some reason so I place this inside of the AppServiceProivder.php
view()->composer(
'layouts.app', function ($view){
$view->with('notifications', json_decode(\Auth::user()->notifications, true));
});
It's not as clean but for something this small, I guess it's ok.
I'd like to retrieve my module configuration from a controller in Zend Framework 3.
I searched, and it seems that the standard way to do this in ZF2 is to use
$this->getServiceLocator()
to access the configuration in module.config.php.
However, this won't work in ZF3, as there is no getServiceLocator() method.
What is the standard way to achieve this?
Don't know if you found an answer, as there are different solutions as tasmaniski wrote. Just in case, let me share one that would have helped me a lot when I started to play with ZF3:
MyControllerFactory.php
<?php
namespace My\Namespace;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use DependencyNamespace\...\ControllerDependencyClass; // this is not a real one of course!
class MyControllerFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param null|array $options
* #return AuthAdapter
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// Get config.
$config = $container->get('configuration');
// Get what I'm interested in config.
$myStuff = $config['the-array-i-am-interested-in']
// Do something with it.
$controllerDepency = dummyFunction($myStuff);
/*...the rest of your code here... */
// Inject dependency.
return $controllerDepency;
}
}
MyController.php
<?php
namespace My\Namespace;
use Zend\Mvc\Controller\AbstractActionController;
use DependencyNamespace\...\DependencyClass;
class MyController extends AbstractActionController
{
private $controllerDepency;
public function __construct(DependencyClass $controllerDepency)
{
$this->controllerDepency = $controllerDepency;
}
/*...the rest of your class here... */
}
You need to inject your dependencies through service manager.
Basicly you need to create 2 class Controller and ControllerFactory that will create Controller with all dependencies.
This question can be viewed through a prism of ZF2 + Doctrine + MVC programming practices, or it can be viewed through just an OOP perspective.
My concern is about Separation of Concerns, and on removing dependencies.
I am using code in my controllers that goes something like this:
class MyController
{
private $em; //entityManager
function __construct()
{
$this->em = DoctrineConnector::getEntityManager();
}
function indexAction()
{
//Input
$inputParameter = filter_input(...);
//request for Data
$queryBuilder = $this->em->createQuery(...)
->setParameter('param', $inputParameter);
$query = $queryBuilder->getQuery();
//$services is the user-defined data type requested
$services = $query->getResult();
//use data to produce a view model
$view = new ViewModel();
$view->setVariables(array('services' => $services));
return $view;
}
}
I am not entirely comfortable with the above and wanted a second opinion. For one, my EntityManager is part of the class, so my class is cognizant of the entity manager construct, when I think it should not be a part of the controller. Do I perhaps use a Factory or Builder design pattern to help me create MyController class?
If I do, I can move my em (entityManager) construct into the Factory pattern and create and populate my MyController inside the Factory. Then, the MyController can have a private variable $services instead.
i.e.
class MyController
{
private $services;
function setServices($services)
{
$this->services = $services;
}
function indexAction()
{
//use data to produce a view model
$view = new ViewModel();
$view->setVariables(array('services' => $this->services));
return $view;
}
}
class MyFactoryMethod
{
function createMyController()
{
//Input
$inputParameter = filter_input(INPUT_GET...);
//request for Data
$queryBuilder = $this->em->createQuery(...)
->setParameter('param', $inputParameter);
$query = $queryBuilder->getQuery();
//$services is the user-defined data type requested
$services = $query->getResult();
//create and return MyController instance
$controller = new MyController();
$controller->setServices($services);
return $controller;
}
}
I typically tried to do this PHP's mysql extension to remove dependency on data out of my various objects. I am using Doctrine2 now which is an ORM, and wondering if I should keep doing the same thing (namely preferring 2nd example rather than the first...
Question:
I can write code both ways. It works essentially the same. My question is -- is the code, as it is written in my 2nd example preferred more than the code as it is written in my first?
Notes / Clarifications:
In my case variable $services is a domain-specific variable (not ZF2's ServiceLocator). i.e. think of MyController as a controller for business-specific "services".
I am not harnessing full power of ZF2 with configs, routers, events, and everything. I am using ZF2 modules on an existing legacy codebase on as-needed basis.
When your controller has hard dependencies I would suggest to use the common ZF2 solution by creating the controller and injecting the dependency in a factory instance and registering the controller under the 'factories' key in your 'controllers' config array.
In your module.config.php
'controllers' => array(
'factories' => array(
'Application\Controller\MyController' => 'Application\Controller\MyControllerFactory'
)
)
In your controller I would set hard dependency in the __construct method. Like this you prevent the controller from ever being instantiated without your dependencies (it will throw an exception).
Never inject something like $services (if this is a ServiceLocator) from which you will pull the actual dependencies since it is not clear what the class actually needs. It will be harder to understand for other developers and it is also hard to test since you cannot set mocks for your individual dependencies so easily.
Your Controller class:
<?php
namespace Application\Controller;
use Doctrine\ORM\EntityManager;
class MyController
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
*
*/
function indexAction()
{
//Do stuff
$entityManager = $this->getEntityManager();
}
/**
* #return EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
}
Your Factory:
<?php
namespace Application\Controller;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Doctrine\ORM\EntityManager;
class MyControllerFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return MyController
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** #var EntityManager $entityManager */
$serviceManager = $serviceLocator->getServiceLocator()
$entityManager = $serviceManager->get('doctrine.entitymanager.orm_default');
$myController = new MyController($entityManager);
return $myController;
}
}
There are two different approaches to this problem that are provided by ZF2.
Use the ServiceLocator to retrieve the EntityManager via a Factory.
In Module.php, add an anonymous function or Factory.
public function getServiceConfig()
{
return [
'factories' => [
'Doctrine\ORM\EntityManager' => function (ServiceManager $sm) {
$entityManager = $sm->get('doctrine.entitymanager.orm_default');
return $entityManager;
}
],
],
}
In your Controller
$em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
Create an Initializer and AwareInterface to inject the EntityManger into your controllers.
The AwareInterface can be added to any class which is initialized by the ServiceManager.
interface EntityManagerAwareInterface
{
/**
* Set EntityManager locator
*
* #param EntityManager $entityManager
*/
public function setEntityManager(EntityManager $entityManager);
/**
* Get service locator
*
* #return EntityManager
*/
public function getServiceLocator();
}
The Initializer is run when services are initialized by the ServiceManager. A check is performed to so if $instance is a EntityManagerAwareInterface.
use Application\EntityManager\EntityManagerAwareInterface;
use Zend\ServiceManager\InitializerInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class EntityManagerInitializer implements InitializerInterface
{
/**
* Initialize
*
* #param $instance
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function initialize($instance, ServiceLocatorInterface $serviceLocator)
{
if ($instance instanceof EntityManagerAwareInterface) {
$entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
$instance->setEntityManager($entityManager);
}
}
}
Next add the Initializer to Module.php
public function getServiceConfig()
{
return [
'initializers' => [
'entityManager' => new EntityManagerInitializer(),
],
],
}
The advantage of going the Initializer route is there is a one time setup. Any class that implements the EntityManagerAwareInterface will have the EntityManager injected when the class is initialized.