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 )
Related
I'm using this to build zend application. http://github.com/zendframework/ZendSkeletonApplication
I'm trying to get the config data I put inside the config/autoload/global.php and config/local.php.dist with the bottom line but it returns
Zend\ServiceManager\Exception\ServiceNotFoundException
and also
A plugin by the name "getServiceLocator" was not found in the plugin manager Zend\Mvc\Controller\PluginManager
Any idea how I can get the config?
$config = $this->getServiceLocator()->get('config');
The Master branch of ZendSkeletonApplication at the moment using Zend Framework 3. And getServiceLocator() in controller had been remove in Zend Framework 3.
So, if you wanna pass some variables from service to controller, you should create a factory. And pass the variables when instantiate the controller in factory.
Example:
Your controller name is IndexController from Application Module. And the factory class is IndexControllerFactory.
Application\Controller\IndexControllerFactory
<?php
namespace Application\Controller;
use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
class IndexControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$config = $container->get("Config");
return new IndexController($config);
}
}
Application\Controller\IndexController
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController
{
private $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function indexAction()
{
// use $this->config here
}
}
and here the configuration in module.config.php
'controllers' => [
'factories' => [
Controller\IndexController::class => Controller\IndexControllerFactory::class
],
],
Hope this help
This is for clarification
In ZF3, if you are creating any classes that need in your application, make them serviceable, make them available in your application via ServiceManager. ServiceManager implements a container which stores registered services. So how is that? ZF uses a method called factory (in short, it creates object). It helps store services into container. We can then pull services from that container using ServiceManager. Let's see how?
ServiceManager is itself a service.
So using a factory let's make ServiceManager instance available in a controller (For example, IndexController). So that we can get any service using it.
Application\Controller\IndexControllerFactory
<?php
namespace Application\Controller;
// This is the container
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class IndexControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = NULL)
{
$serviceManager = $container->get('ServiceManager');
return new IndexController($serviceManager);
}
}
Let's register the IndexControllerFactory as a factory for IndexController so that we can use it. Make the following change in the module.config.php
'controllers' => [
'factories' => [
Controller\IndexController::class => Controller\IndexControllerFactory::class,
],
],
Once the IndexController is instantiated by IndexControllerFactory (by above configurations) the ServiceManager instance becomes available through IndexController's constructor.
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\ServiceManager\ServiceManager;
class IndexController extends AbstractActionController
{
protected $serviceManager;
public function __construct(ServiceManager $serviceManager)
{
// Here we set the service manager instance
$this->serviceManager = $serviceManager;
}
public function indexAction()
{
// Use this as you want
$config = $this->serviceManager->get('config');
return new ViewModel();
}
What if we need something from config service inside another class instead of the controller? For example, we want to upload images into a specific destination. So how would we fix the upload path? See the following example.
We will upload images through RenameUpload filter. It has an option named target which specifies the destination of upload path. Let's create another factory for upload filter.
Application\Controller\Form\Filter\UploadFilterFactory
<?php
namespace Application\Form\Filter;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Form\Filter\UploadFilter;
class UploadFilterFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = NULL)
{
$config = $container->get('config');
// Look! here we fix the upload path
$uploadPath = $config['module_config']['upload_path'];
// Here we're injecting that path
return new UploadFilter($uploadPath);
}
}
Do the same for the UploadForm if you need. This will be UploadFormFactory
Put the following two snippets in the module.config.php. This is for UploadFilterFactory.
'service_manager' => [
'factories' => [
// UploadForm::class => UploadFormFactory::class,
UploadFilter::class => UploadFilterFactory::class,
],
// Make an alias so that we can use it where we need
// it could be uploadAction() inside any controller
// $inputForm = $this->serviceManager->get('UploadForm');
// $inputFilter = $this->serviceManager->get('UploadFilter');
// $uploadForm->setInputFilter($inputFilter), for example
'aliases' => [
// 'UploadForm' => UploadForm::class,
'UploadFilter' => UploadFilter::class,
],
],
and this one for the upload path wherever you want to upload.
'module_config' => [
// Set the path as you want
'upload_path' => __DIR__ . '/../data/upload',
],
This is the Application\Form\Filter\UploadFilter.
<?php
namespace Application\Form\Filter;
use Zend\InputFilter\InputFilter;
use Zend\Filter\File\RenameUpload;
class UploadFilter extends InputFilter
{
protected $uploadPath;
public function __construct(string $uploadPath)
{
// We're assigning here so that we can use it
// on the filter section.
$this->uploadPath = $uploadPath;
$this->prepareFilters();
}
public function prepareFilters()
{
$this->add(array(
'name' => 'image',
'required' => true,
'filters' => array(
array(
'name' => RenameUpload::class,
'options' => array(
// Thus here we use it
'target' => $this->uploadPath,
'overwrite' => true,
'randomize' => true,
'use_upload_extension' => true,
),
),
),
'validators' => array(),
));
}
}
This is a one way of making things serviceable. So why is ServiceManager? This is for making scattered uses of objects stop. It removes hidden dependencies. This makes code clean and easier to understand. The principle is Good Design.
In order to do that you need to inject the config, as the getServiceLocator (and all other locators) have been removed from ZF3.
In your module configuration you have this:
'controllers' => [
'factories' => [
Controller\IndexController::class => InvokableFactory::class,
],
],
You can change the factory to create your own.
Controller\IndexController::class => Controller\IndexControllerFactory::class,
Here's the code:
final class IndexControllerFactory
{
public function __invoke(Container $container) : IndexController
{
$config = $container->get('config');
if (!isset($config['stuff']['stuff']) {
throw new \Exception('Please add the stuff.stuff parameter in the config');
}
$myParam = $config['stuff']['stuff'];
return new IndexController($myParam);
}
}
Container is a PSR container.
In your controller add a constructor to receive the config you need:
public function __construct(string $param)
{
$this->param = $param;
}
And here you have your config in your class, as an attribute.
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.
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)
}
So I followed his readme and I have done composer dump-autoload a million times, but still I receive an error.
The code:
'providers' => [
...
Thujohn\Twitter\TwitterServiceProvider::class,
],
'aliases' => [
...
'Twitter' => Thujohn\Twitter\Facades\Twitter::class,
],
In my controller:
class HomeController extends Controller {
public function index() {
$tweets = Twitter::getUserTimeline([
'screen_name' => 'xxxxxxx',
'count' => 10,
'format' => 'json'
]);
dd($tweets);
return view('home');
}
public function about() {
return view('about');
}
}
But I get the error:
FatalErrorException in HomeController.php line 10:
Class 'App\Http\Controllers\Twitter' not found
Um ..... What?
You used non-namespaced name when you refered to Twitter class, so PHP is looking for the class in current namespace. Change that reference to \Twitter or add the following use statement:
use Twitter;
I have a module calls API, and i want to load config file for it. The guide says that i have to use function \Yii::configure. I use it, but it doesn't apply any new configs. And i tried to use array instead config file, the result is same
class API extends \yii\base\Module
{
public $controllerNamespace = 'api\client\controllers';
public function init()
{
parent::init();
// \Yii::configure($this, require(__DIR__ . '/config/main.php'));
\yii::configure($this, [
'components' => [
'user' => [
'class' => 'yii\web\UserTest',
'identityClass' => 'api\client\models\User',
],
]
]);
echo \yii::$app->user->className();
die();
}
}
How I can override config in my module ?
UPDATE
You have to use setComponents method of Yii::$app
Yii::$app->setComponents(
[
'errorHandler'=>[
'errorAction'=>'forum/forum/error',
'class'=>'yii\web\ErrorHandler',
],
'user' => [
'class' => 'yii\web\User',
'identityClass' => 'app\modules\profile\models\User',
],
]
);
OLD ANSWER
Didn't it give you errors? Your casing are wrong and so instead of "yii" in small letters use "Yii" capitalized
class API extends \yii\base\Module
{
public $controllerNamespace = 'api\client\controllers';
public function init()
{
parent::init();
\Yii::configure($this, [
'components' => [
'user' => [
'class' => 'yii\web\UserTest',
'identityClass' => 'api\client\models\User',
],
]
]);
echo \Yii::$app->user->className();
die();
}
}
I see no reason to override the application components here. I'd use #StefanoMtangoo trick but to set the component to the Module itself instead of Yii::$app:
public function init()
{
parent::init();
$this->setComponents([
'db' => [
'class' => 'yii2tech\filedb\Connection',
'path' => '#app/builder/data',
]
]);
}
Then the tricky part is to differentiate between any app's components and your module's own components. For example if my Module had a model extending yii\db\ActiveRecord I'd override its getDB() as follow (original code here):
public static function getDb()
{
return Yii::$app->getModule('api')->get('db');
// instead of: return Yii::$app->getDb();
}
So whatever the app that is using my module has or hasn't a db component it won't matter.