Please, help me to solve the following problem in ZendFramework.
I'm new in ZF and PHP7.
For a few days I havent been able to use Doctrine EntityManager in controller.
I have:
My Controller
namespace Sonun\Controller;
use Zend\Mvc\Controller\AbstractActionController,
Doctrine\ORM\EntityManager;
class IndexController extends AbstractActionController
{
protected $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
Factory
namespace Sonun\Controller;
use Sonun\Controller\IndexController,
Zend\ServiceManager\FactoryInterface,
Zend\ServiceManager\ServiceLocatorInterface;
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $sm)
{
$entityManager = $sm->get("Doctrine\ORM\EntityManager");
return new IndexController($entityManager);
}
}
module.config.php
return [
"controllers" => [
"invokables" => [
"Sonun\Controller\IndexController" => "Sonun\Controller\IndexController"
]
],
"router" => [
"routes" => [
"sonun" => [
"type" => "segment",
"options" => [
"route" => "/sonun/[:action/][:id/]",
"constraints" => [
"action" => "[a-zA-Z0-9_-]*",
"id" => "[0-9]*"
],
"defaults" => [
"controller" => "Sonun\Controller\IndexController",
"action" => "index"
]
]
]
]
],
"view_manager" => [
"template_path_stack" => [
__DIR__."/../view"
]
],
"service_manager" => [
"factories" => [
"Sonun\Controller\IndexController" => "Sonun\Controller\IndexControllerFactory"
]
]
]
Error
Fatal error: Uncaught TypeError: Argument 1 passed to ZendDeveloperTools\Exception\SerializableException::__construct() must be an instance of Exception, instance of TypeError given, called in C:\xampp\htdocs\sonun\vendor\zendframework\zend-developer-tools\src\Collector\ExceptionCollector.php on line 45 and defined in C:\xampp\htdocs\sonun\vendor\zendframework\zend-developer-tools\src\Exception\SerializableException.php:26 Stack trace: #0 C:\xampp\htdocs\sonun\vendor\zendframework\zend-developer-tools\src\Collector\ExceptionCollector.php(45): ZendDeveloperTools\Exception\SerializableException->__construct(Object(TypeError)) #1 C:\xampp\htdocs\sonun\vendor\zendframework\zend-developer-tools\src\Profiler.php(210): ZendDeveloperTools\Collector\ExceptionCollector->collect(Object(Zend\Mvc\MvcEvent)) #2 C:\xampp\htdocs\sonun\vendor\zendframework\zend-developer-tools\src\Listener\ProfilerListener.php(93): ZendDeveloperTools\Profiler->collect(Object(Zend\Mvc\MvcEvent)) #3 C:\xampp\htdocs\sonun\vendor\zendframework\zend-eventmanag in C:\xampp\htdocs\sonun\vendor\zendframework\zend-developer-tools\src\Exception\SerializableException.php on line 26
I too am new to ZF3 (isn't everyone?) but I will take a shot. Your factory class needs to look something like
namespace Application\Controller\Factory;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Interop\Container\ContainerInterface;
use Application\Controller\IndexController;
class IndexControllerFactory implements FactoryInterface {
public function __invoke(ContainerInterface $container, $requestedName, Array $options = null) {
$entityManager = $container->get('doctrine.entitymanager.orm_default');
return new IndexController($entityManager);
}
}
Note that in ZF3 your factories now have to implement the Zend\ServiceManager\Factory\FactoryInterface, i.e., implement __invoke() rather than createService().
In your module.config.php, your Controller is not an invokable -- it has a hard dependency on that $entityManager, right? -- so you need to do away with that and replace with something like
'controllers' => [
'factories' => [
Controller\IndexController::class => Controller\Factory\IndexControllerFactory::class,
],
],
Good luck!
The particular error you are seeing is emitted by ZendDeveloperTools, and fixed in its 1.1.1 release. Run composer update zendframework/zend-developer-tools to get it.
However, it's not the root of the problem; that module is simply trying to report an exception, in this case a type error; you'll have to keep debugging from there.
Finally, the DoctrineModule (and related modules) are, to my knowledge, not yet compatible with the v3 releases of ZF. You may need to switch to v2 until they compete their migration.
Related
I have a custom validator:
class CustomValidator extends RecordExists
{
public function isValid($value, array $context = null)
{
// some behaviour
return parent::isValid($value);
}
}
This validator has a factory:
class CustomValidatorFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$validator = new CustomValidator();
// preparations for CustomValidator
return $validator;
}
}
CustomValidator and its factory are registered in module.config.php under
'validators' => [
'factories' => [
CustomValidator::class => CustomValidatorFactory::class,
],
],
CustomValidator is attached to a fieldset's input in getInputFilterSpecification() as
'validators' => [
['name' => CustomValidator::class],
]
The fieldset is called via init() method of the form, and the form is called in the controller factory as $container->get('FormElementManager')->get('TheNeededForm').
The problem is that for some reason, CustomValidator gets instantiated not through the CustomValidatorFactory, but getInputFilterSpecification() of the fieldset just creates an instance of new CustomValidator().
How to make the custom validator instantiation through the factory?
PS: I already surfed the internet, and found a similar problem, but the solution given there didn't help: it suggested adding 'abstract_factories' => [FormAbstractServiceFactory::class] in module.config.php, under 'form_elements' directive.
I already answered this question here
https://stackoverflow.com/a/61287283/4685379
Here it is:
In ZF3/Laminas, if a validator is registered as an invokable, you can call the validator in the getInputFilterSpecification() of your form, and no problem. If a validator is instantiated using a factory, you get into trouble. If I understand correctly, even if your form is registered like this
'form_elements' => [
'factories' => [
SomeForm::class => SomeFormFactory::class,
]
]
and your validator:
'validators' => [
'factories' => [
SomeValidator::class => SomeValidatorFactory::class,
]
]
you won't be instantiating the validator via factory. The reason is that the form factory (the one you get like $form->getFormFactory()) has an input filter factory and in there sits default validator chain. And this validator chain has no ValidatorManager attached. And without the ValidatorManager, the default chain cannot map the validator name to the validator factory.
To solve all this headache, in your controller factory do this:
$form->('FormElementManager')->get(SomeForm::class);
$form->getFormFactory()->getInputFilterFactory()
->getDefaultValidatorChain()->setPluginManager($container->get('ValidatorManager'));
and your troubles are over.
I am trying to get a ServiceManager instance in my controller to use a factory for Db\Adapter.
I added to module/Application/config/module.config.php:
'service_manager' => [
'factories' => [
Adapter::class => AdapterServiceFactory::class,
],
],
To config/autoload/local.php I added the following lines:
'db' => [
'driver' => 'Mysqli',
'database' => 'mydb',
'username' => 'myuser',
'password' => 'mypassword',
]
An now I want to access the ServiceManager in my module/Application/src/Controller/IndexController.php. How do I do that?
I tried $sm = $this->getPluginManager(); without success. If I run $serviceManager->get(Adapter::class) with the PluginManager it gives me an error:
Too few arguments to function Zend\Db\Adapter\Adapter::__construct(), 0 passed in (...)\vendor\zendframework\zend-servicemanager\src\Factory\InvokableFactory.php on line 30 and at least 1 expected
What can I do, to get a ServiceManager that will get my that Adapter object?
I changed the controller factory from
'controllers' => [
'factories' => [
Controller\IndexController::class => InvokableFactory::class,
],
],
to
'controllers' => [
'factories' => [
Controller\IndexController::class => function(ContainerInterface $serviceManager) {
return new Controller\IndexController($serviceManager);
},
],
],
I also added a getServiceConfig() method to the module.config.php and added a constructor to the IndexController, which receives the ServiceManager. Now I have access inside the controller.
But my question is now: is there a nicer, a more "zend like" way to achieve this?
Thanks to SO's great related topics I finally found the answer. ServiceManager in ZF3
It seems to be done by using Controller Factories, almost like I did.
I'm experienced with ZF1 and now I'm learning ZF3, I wanted to do a simple thing: set the DB configuration in the configuration file, and then get the db adapter at the controller. It took me awhile to figure it out as the official documents have millions of options for different customization. So I'm posting my answer to help anyone looking.
1- Add the db credentials in config/autoload/global.php or config/autoload/local.php, like this:
<?php
return [
'db' => [
'driver' => 'Pdo_Mysql',// can be "Mysqli" or "Pdo_Mysql" or other, refer to this link for the full list: https://docs.zendframework.com/zend-db/adapter/
'hostname' => 'localhost',// optional
'database' => 'my_test_db',
'username' => 'root',
'password' => 'root',
],
];
2- In module/YOUR_MODULE_NAME/config/module.config.php, add this under the controllers factories section:
return [
//...
'controllers' => [
'factories' => [
//...
// Add these lines
Controller\MycontrollernameController::class => function($container) {// $container is actually the service manager
return new Controller\MycontrollernameController(
$container->get(\Zend\Db\Adapter\Adapter::class)
);// this will pass the db adapter to the controller's constructor
},
//...
]
]
//...
];
3- Finally, in your controller module/YOUR_MODULE_NAME/src/Controller/MycontrollernameController, you can get and use the db adapter:
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Db\Adapter\Adapter;
class MycontrollernameController extends AbstractActionController
{
private $db;
public function __construct($db)
{
$this->db = $db;
}
public function indexAction()
{
$result = $this->db->query('SELECT * FROM `my_table`', Adapter::QUERY_MODE_EXECUTE);
echo $result->count();// output total result
return new ViewModel();
}
}
There is another way to achieve the same thing by creating a factory for your controller, and inside that factory pass the db adapter to the controller. For beginners trying out ZF3 at hello-world level like me, I think that's too much.
I am new to Zend Framework 3 and I am doing this tutorial:
I have a xampp, mysql setup.
I have done everything exactly like in this tutorial. Now I am at the point where you configure the database connection. Further I have set up the controller and view.
In the tutorial link above , they are using php to create a database and then in config/autoload/global.php.....the following code:
return [
'db' => [
'driver' => 'Pdo',
'dsn' => sprintf('sqlite:%s/data/zftutorial.db', realpath(getcwd())),
],
];
I have edited this to:
'db' => [
'driver' => 'Pdo_Mysql',
'dsn' => 'mysql:dbname=dbname;host=localhost;charset=utf8;username=myuser;password=mypassword',
],
When I call the url for the index view, there the following error:
Warning: Creating default object from empty value in C:\xampp\htdocs\zendtest\module\Album\src\Controller\AlbumController.php on line 15
Fatal error: Call to a member function fetchAll() on null in
C:\xampp\htdocs\zendtest\module\Album\src\Controller\AlbumController.php
on line 22
The AlbumController:
<?php
namespace Album\Controller;
use Album\Model\AlbumTable;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class AlbumController extends AbstractActionController {
private $table;
public function __construct(AlbumTable $table)
{
$this->table = $table;
}
public function indexAction()
{
return new ViewModel([
'albums' => $this->table->fetchAll(),
]);
}
}
I think that the connection doesn't work??
can you share your "AlbumControllerFactory.php" ?
if you have not yet created the factory you should do.
1 - Create AlbumControllerFactory that implements FactoryInterface
2 - Inside __invoke function use the Container to inject AlbumTable to your controller
3 - config your mapping in module.config.php
'controllers' => [
'factories' => [
Controller\AlbumController::class => Controller\Factory\AlbumControllerFactory::class,
All simple, you have mistake in key $this, you did write $htis instead )
I was reading Zend 3 documentation on Service Manager and i got this problem.
In documentation it says that if we have some DI in our controller we should update module.config.php file and add controllers key and invoke controller not with InvokableFactory::class but with custom factory class and add another key service_manager that contains array of classes that my first controller uses.
Ok so i do that:
module.config.php
'service_manager' => [
'factories' => [
Controller\Controller2::class => Factory\Controller2Factory::class,
Controller\Controller3::class => Factory\Controller3Factory::class,
],
],
'controllers' => [
'factories' => [
Controller\Controller1::class => Factory\Controller1Factory::class
],
]
Controller1Factory.php
class Controller1Factory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new Controller1(
$container->get(Controller2::class),
$container->get(Controller3::class),
);
}
}
But now i have error that Controller2 and Controller3 also have DI in their constuctors, so i make new custom factories and so on and so on...until i get to my models.
And Models also have Dependency that is injected in their controller which is zend native \Zend\Db\TableGateway\TableGatewayInterface and i now have to edit my conf file again and add TableGatewayInterface.
And that is wrong. I should never be forced to inject native zend classes and services this way.
So what am i doing wrong?
If your Controller has no dependency, it's the best way to declare it in module.config.php as you did.
But if it has dependecies, it's better to do it in Module.php. You first declare your services, then the controller (don't forget to remove it from module.config.php), injecting in it the services it depends :
public function getServiceConfig()
{
return [
'factories' => [
Model\MyObjectTable::class => function($container) {
$tableGateway = $container->get(Model\MyObjectTableGateway::class);
return new Model\MyObjectTable($tableGateway);
},
Model\MyObjectTableGateway::class => function($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\User());
return new TableGateway('myObject', $dbAdapter, null, $resultSetPrototype);
},
]
];
}
public function getControllerConfig()
{
return [
'factories' => [
Controller\MyObjectController::class => function($container) {
return new Controller\MyObjectController(
$container->get(Model\MyObjectTable::class)
);
},
]
];
}
And in your controller:
private $table;
public function __construct(MyObjectTable $table)
{
$this->table = $table ;
}
It is described in This ZF3 tutorial page and following.
Why do you still need to say:
use MyClass\Table\Facades\Table;
at the top of a laravel controller
even if you have specified it in app/config/app.php
'aliases' => [
'Table' => MyClass\Table\Facades\Table::class,
You don't?
'aliases' => [
'MyClass' => Some\Vendor\Something\Facades\MyClass::class,
]
Then you can do
use Myclass;
class MyController extends Controller {
public function fetch($id) {
return MyClass::find(1)
}