Yii2: $loadedModules does not show all modules - php

Created module "forum" - exactly as written here. Then created nested module "admin":
//"Module.php" in '#app/modules/forum'
namespace app\modules\forum;
class Module extends \yii\base\Module {
public function init() {
parent::init();
\Yii::configure($this, require(__DIR__ . '/config.php'));
$this->modules = [
'admin' => [
// here is my nested module
'class' => 'app\modules\forum\modules\admin\Module',
],
];
}
}
Also created a non-nested module "games" (in the same way) and wrote in "web.php" (main config-file):
'modules' => [
'forum' => [
'class' => 'app\modules\forum\Module',
],
'games' => [
'class' => 'app\modules\games\Module',
],
'admin' => [
'class' => 'app\modules\forum\modules\admin\Module',
],
],
But when I tried to output:
// codeline is written in application view, not in module view
var_dump(array_keys(\Yii::$app->loadedModules));
I saw only these modules:
array(4) {
string(19) "yii\web\Application"
string(16) "yii\debug\Module"
string(14) "yii\gii\Module"
string(24) "app\modules\forum\Module"
}
"Games" and nested "admin" modules are absent! Although doc says:
$loadedModules property keeps a list of loaded modules, including both direct children and nested ones, indexed by their class names.
But I could get only "forum" myself-created module. What am I understanding wrong?

As documentation says:
$loadedModules property keeps a list of loaded modules
which mean that it keeps modules that loaded in current request. E.g. if you are on module's page example.com/forum/ it will contain app\modules\forum\Module but if you on example.com/site where site is controller's name $loadedModules will contain only modules that are set in $bootstrap config property.
To get list of all modules call Yii::$app->modules. Note that $loadedModules contains app itself since it extends Module class. Yii::$app->modules contains all modules from app config modules property.

Related

How to add a custom function to smarty extension yii2

i have a little problem with smarty extension for yii2.
I've created a new smarty function, and i've added the code into this file:
backend/vendor/yiisoft/yii2-smarty/src/Extension.php
public function __construct($viewRenderer, $smarty)
{
//other code
/* CUSTOM FUNCTION REGISTER */
$smarty->registerPlugin('function', 'test', [$this, 'functionTest']);
}
//this is the custom function
public function functionTest($params, $template){
return "Test custom funcion";
}
And i can use this custom function into my template like this {test} and all works fine.
Today i have update the yii2 to the 2.0.20 version, and obviously the Extension.php file was replaced, so i can't access anymore to the custom function.
My question is: How i can add a custom function for smarty in yii2?
I'll set the config array in this way:
//this is in backend/config/main.php
'view' => [
'renderers' => [
'tpl' => [
'class' => 'yii\smarty\ViewRenderer',
'pluginDirs' => ['#backend/saSmartyPlugin'],
'widgets' =>[
'functions' => [['test' => 'test'], ],
],
//'cachePath' => '#runtime/Smarty/cache',
],
],
],
and the into saSmartyPlugin folder i insert my test.php file like this:
<?php
class Test{
function functionTest($params, $template){
return "Test custom funcion";
}
}
But i get this error:
Smarty: Undefined class 'test' in register template class
I agree with Muhammad Omer Aslam, you should extend from backend/vendor/yiisoft/yii2-smarty/src/Extension.php in order to create Any new methods and be able to use them after update. After that you just write in your config file path to your extended class.
I'll find a solution thinking about #MuhammadOmerAslam and #SergheiLeonenco suggest me.
I write this answer for anyone who has this problem.
First i create my php file Test.php and i extend the Extension class of Smarty
namespace common\components;
use yii\smarty\Extension;
class Test extends Extension{
public function __construct($viewRenderer, $smarty){
parent::__construct($viewRenderer, $smarty);// call parent construct
$smarty->registerPlugin('function', 'bread', [$this, 'functionBreadcrumbs']);//register my custom function
}
//My custom function
function functionTest($params, $template){
return "Test custom funcion";
}
And i save this file into common/components/
After that i have modified my config.php file
'view' => [
'renderers' => [
'tpl' => [
'class' => 'yii\smarty\ViewRenderer',
'extensionClass' => 'common\components\Test'
],
],
],
],

Loading Modules which has multiple module in Zend Framework 2

Issues
So I try to decouple my application to multiple module project ( each has its own composer.json ), then the real application will load all this project through composer
Each of this module project will have a user-interface accessible through browser and can start individually, so it's not just a simple library. This file will exist on each module project:
config/application.config.php
public/index.php
Example Module ( Dependency is what I write in module array in application.config.php ):
UIModule
Dependency : AssetManager, UIModule
CMSModule
Dependency : UIModule, CMSModule
AccountingModule:
Dependency : UIModule, AccountingModule
Now in my final application lets say MyApplication it need both CMSModule and AccountingModule, but I cannot write only just this two module in application.config.php. Instead I have to write:
AssetManager -> this should be load by UIModule
UIModule -> this should be load by CMS/Accounting Module
CMSModule
AccountingModule
I should only require to write this two in MyApplication
CMSModule
AccountingModule
Is this can be done ? which I think what this guy want to achieve in Loading Modules Dynamically in Zend Framework 2
Something like this, I add another additional module.
Based on our exchange in the comments and the question, you're going to need at least 3 applications. I'll give you a quick examples, you'll have to update your requirements for each application yourself. After the composer.json configs I'll give you a skeleton module to use as a theme module.
These config's are to be used as the root composer.json config files. Each of the required packages should have their own composer file listing requirements for the specific package.
For example, a "core" module would require various Zend Framework packages. A "theme" package could be requiring other ZF packages, such as zendframework/zend-view in order to be able to have a GUI layout.
Setting up 3 separate Zend Framework applications with overlapping requirements
composer.json for application 1
{
"name": "COMPANY_NAME/APPLICATION_1",
"require": {
"COMPANY_NAME/MODULE_1_THEME": "*",
"COMPANY_NAME/MODULE_2_CMS": "*"
},
"repositories": [
{
"type": "git",
"url": "git#github.com/COMPANY_NAME/MODULE_1_THEME.git"
},
{
"type": "git",
"url": "git#github.com/COMPANY_NAME/MODULE_2_CMS.git"
},
]
}
composer.json for application 2
{
"name": "COMPANY_NAME/APPLICATION_2",
"require": {
"COMPANY_NAME/MODULE_1_THEME": "*",
"COMPANY_NAME/MODULE_3_ACCOUNTING": "*"
},
"repositories": [
{
"type": "git",
"url": "git#github.com/COMPANY_NAME/MODULE_1_THEME.git"
},
{
"type": "git",
"url": "git#github.com/COMPANY_NAME/MODULE_3_ACCOUNTING.git"
},
]
}
composer.json for application 3 (has no theme)
{
"name": "COMPANY_NAME/APPLICATION_3",
"require": {
"COMPANY_NAME/MODULE_4_AUTH_MODULE": "*"
},
"repositories": [
{
"type": "git",
"url": "git#github.com/COMPANY_NAME/MODULE_4_AUTH_MODULE.git"
}
]
}
As you can see, the Applications 1 & 2 use the same MODULE_THEME package, as you outlined in the diagram in your question.
Now, the creation of a package for Zend Framework is pretty much the same for every package you create, so modify what follows to the requirements you have for each module (in a package).
Creating a theme module
This module basically replaces the Application module that you get by default when you install the Zend Framework (2 or 3) Skeleton Application.
I've recently upgraded everything I have with Zend Framework to Zend Framework 3, so I'll be giving you a setup tailored for ZF3. However, downgrading for ZF2 should not be too much of an issue.
Create config for what you need
A typical theme needs a few things, such as:
themes/layouts for different types of pages (e.g. login, normal theme, errors)
translations
showing errors (when in dev mode)
default "home" route
controller to handle default "home" route
Config for this could be (not limited to! Do with it what you wish!) as such in the module.config.php of the Theme module
namespace COMPANY_NAME\Theme;
use COMPANY_NAME\Theme\Controller\ThemeController;
use COMPANY_NAME\Theme\Factory\ThemeControllerFactory;
return [
'controllers' => [
'factories' => [
ThemeController::class => ThemeControllerFactory::class,
],
],
'router' => [
'routes' => [
'home' => [
'type' => Literal::class,
'may_terminate' => true,
'options' => [
'route' => '/',
'defaults' => [
'controller' => ThemeController::class,
'action' => 'index',
],
],
],
],
],
'route_layouts' => [
'*' => 'layout/layout',
'login' => 'layout/login',
'register' => 'layout/login',
'error*' => 'error/index',
'error/404' => 'error/404',
],
'translator' => [
'locale' => 'en_US',
'translation_file_patterns' => [
[
'type' => 'gettext',
'base_dir' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'language',
'pattern' => '%s.mo',
],
],
],
'view_manager' => [
// controller_map is optional, but depending on your composer package nesting, could be a great help. Have a look here for how to use: https://blog.alejandrocelaya.com/2015/08/14/working-with-sub-namespaced-modules-in-zend-framework-2-the-right-way/
'controller_map' => [
'COMPANY_NAME\Theme' => 'company_name_path_alias',
],
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => [
'layout/layout' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR .
'layout' . DIRECTORY_SEPARATOR . 'layout.phtml',
'layout/login' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR .
'layout' . DIRECTORY_SEPARATOR . 'login.phtml',
'error/404' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR .
'error' . DIRECTORY_SEPARATOR . '404.phtml',
'error/index' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR .
'error' . DIRECTORY_SEPARATOR . 'index.phtml',
],
'template_path_stack' => [
__DIR__ . DIRECTORY_SEPARATOR .'..' . DIRECTORY_SEPARATOR . 'view',
],
],
];
File/module structure based on config
The location of the package would be /vendor/COMPANY_NAME/THEME_MODULE_NAME (as you would've defined in the name property in the composer.json file for this package.
The folder/file structure would be:
/vendor/COMPANY_NAME/THEME_MODULE_NAME
config/
module.config.php
src/
Controller/
ThemeController.php
Factory/
ThemeControllerFactory.php
Module.php
view/
error/
index.phtml
404.phtml
layout/
index.phtml
login.phtml
register.phtml
composer.json
ThemeController & *Factory
These are very simple as the Controller is pretty much a clone of the original IndexController provided by the Skeleton Application. The Factory in this instance does nothing but return the Controller. As such you could replace the config for it with the FQCN to the InvokableFactory of Zend Framework 3 and not make the Factory class. However, if your ThemeController needs some requirements (such as a RegisterForm), you're going to need the Factory to provide these.
ThemeController
namespace COMPANY_NAME\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class ThemeController extends AbstractActionController
{
public function indexAction()
{
return [];
}
}
ThemeControllerFactory
namespace COMPANY_NAME\Factory;
use COMPANY_NAME\Controller\ThemeController;
use Zend\ServiceManager\Factory\FactoryInterface;
class ThemeControllerFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param array|null $options
* #return ThemeController
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return new ThemeController();
}
}
Theme composer requirements
Obviously your modules will not have the same requirements. Make sure you figure out what they are, per module.
For my own Theme module, I have the following Zend Framework requirements in my composer.json file:
{
"name": "COMPANY_NAME/THEME_MODULE_NAME",
"require": {
"zendframework/zend-di": "*",
"zendframework/zend-navigation": "*",
"zendframework/zend-view": "*",
}
}
In the require section I also have: "rwoverdijk/assetmanager": "^1.6",. This module is used to mash together all CSS, JS (any type really) of file to a determined location. I would advise you to have a look at it (here).
Notes on the answer
Replace COMPANY_NAME with the username of your Github account (or the identifying account name if your using Bitbucket or Gitlab)
Replace THEME_MODULE_NAME with the name of the repository
If/when possible, use explicit versions for required packages (e.g. "rwoverdijk/assetmanager": "^1.6"). Version locking can save you a lot of hassle in the future...
Additionally: using a package as a "Theme module" allows you to completely remove the module/ folder originally shipped with the Skeleton Application of Zend Framework. However, you're hereby advised to use the module/ folder for application specific modules. If you create a package for everything, you'll soon find yourself maintenance hell.
Yep your layout is what I came at the end
However, you're hereby advised to use the module/ folder for application specific modules.
Kind of, I end up putting inside a folder for every specific package ( zf2 style )
PACKAGE FOLDER
composer.json
Module.php (this for php unit test)
public (for UI Package I have this)
index.php
config
application.config.php (apparently need to write each version for each package)
tests
src
MODULE_NAME
asset
src
MODULE_NAME
Controller
Service
Model
{ Other ... }
config
view
Module.php
Thanks for your clarification and answer.

Yii2 REST API as Module route configuration

I have an existing Yii2 application and have been trying to implement a REST API as an additional module (Maybe a module isn't the correct way to go about this?) But I'm having some trouble configuring the route structures. It doesn't quite work and doesn't follow the expected results, based of the following guide.
I've built an additional module that looks like this:
module
api
controllers
UserController.php
Module.php
UserController.php
<?php
namespace app\modules\api\controllers;
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
}
Module.php
<?php
namespace app\modules\api;
/**
* onco module definition class
*/
class Module extends \yii\base\Module
{
public $defaultController = 'user';
/**
* #inheritdoc
*/
public $controllerNamespace = 'app\modules\api\controllers';
/**
* #inheritdoc
*/
public function init()
{
parent::init();
// custom initialization code goes here
}
}
In my config file I have the added following:
'request' => [
...
'parsers' => [
'application/json' => 'yii\web\JsonParser',
]
],
...
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'enableStrictParsing' => false, // have tried as true also
'rules' => [
...
['class' => 'yii\rest\UrlRule', 'controller' => '\app\modules\api\controllers\user'],
],
],
...
'modules' => [
...
'api' => [ // module for RESTful API
'class' => 'app\modules\api\Module',
],
]
When I run the following urls through postman I get the following:
http://localhost/site1/web/api/users -> 404
http://localhost/site1/web/api/users/index -> 404
http://localhost/site1/web/api/user/index -> returns json repsonse
http://localhost/site1/web/api/user/2 -> 404
I'm unsure as to why the predicted routes of noted in the docs as:
Trying it Out With the above minimal amount of effort, you have
already finished your task of creating the RESTful APIs for accessing
the user data. The APIs you have created include:
GET /users: list all users page by page;
HEAD /users: show the overview information of user listing;
POST /users: create a new user;
GET /users/123: return the details of the user 123;
HEAD /users/123: show the overview information of user 123;
PATCH /users/123 and PUT /users/123: update the user 123;
DELETE /users/123: delete the user 123;
OPTIONS /users: show the supported verbs regarding endpoint /users;
OPTIONS /users/123: show the supported verbs regarding endpoint /users/123
What have I likely done wrong in this setup? Is there a better way to implement an API into an existing website, whilst maintaining DRY practices?
try this:
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['api/user'],
]
]
],
...
'modules' => [
...
'api' => [
'basePath' => '#app/modules/api',
'class' => 'app\modules\api\Module',
],
]
Also be sure to implement prettyUrl's related server server configs.

Override translation path of module on yii2

Suppose I installed module Foo form a repository with composer. The module structure is like this:
- Foo
|- models
|- controllers
|- views
|- messages
|- config
Messages folder of Foo contains translation files of module. Now I want override some translation strings of Foo. From Yii2 i18n Documentation I tried to use fileMap property on configuration of translation component to map bar category to bar.php (instead of reading from app\modules\Foo\messages), but it does not have any effect on translations. My i18n component configuration is:
'i18n' => [
'translations' => [
'*' => [
'class' => 'yii\i18n\PhpMessageSource',
'fileMap' => [
'bar' => 'bar.php'
],
],
],
],
How do i achieve my goal?
If you are wanting to have translations for each module contained within the module, then you need to register the translations for that module. It can't be done simply from the config file. You probably already have this in your module file,, I just include for completeness. The code is copied from the documentation, and needs to be in your module file, so in app/modules/Foo.php
<?php
namespace app\modules\foo;
use Yii;
class Module extends \yii\base\Module
{
public $controllerNamespace = 'app\modules\foo\controllers';
public function init()
{
parent::init();
/** Register custom translations for this module **/
$this->registerTranslations();
}
public function registerTranslations()
{
/**This registers translations for the Foo module **/
Yii::$app->i18n->translations['modules/foo/*'] = [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en-US',
'basePath' => '#app/modules/foo/messages',
/**Tells yii where to find the translations for validation and form categories **/
'fileMap' => [
'modules/foo/validation' => 'validation.php',
'modules/foo/form' => 'form.php',
...
],
];
}
public static function t($category, $message, $params = [], $language = null)
{
return Yii::t('modules/users/' . $category, $message, $params, $language);
}
}
In your case it doesn't look like you need to provide file mapping.You could simply use this format for your files
[[basePath]]/LanguageID/CategoryName.php
Unfortunately I can't seem to find a list of the available categories.
If you then want to override some of the module translations you will need to specify the category to be used, like this in your config file. It specifically overrides the modules/foo/bar category.
'i18n' => [
'translations' => [
'modules/foo*' => [
'class' => 'yii\i18n\PhpMessageSource',
'basePath' => '#app/messages',
],
],
],
Your translation file needs to follow a folder structure like that in the translation description, so in the above example it would be
app/messages/ [language code] /modules/foo/bar.php
Otherise, you can use fileMap to map to different locations, like if your bar.php file is in app/messages/[language code]
'fileMap' => [
'modules/foo/bar' => 'bar.php'
]

Customizing AuthenticationService to ZfcUser

I have a custom implementation of AuthenticationService that I would like to use in ZfcUser Module but I am able to set this class into the module. The implementation seems to be fixed.
vendor\zf-commons\zfc-user\Module.php
'zfcuser_auth_service' => function ($sm) {
return new \Zend\Authentication\AuthenticationService(
$sm->get('ZfcUser\Authentication\Storage\Db'),
$sm->get('ZfcUser\Authentication\Adapter\AdapterChain')
);
}
The original requirement is to keep a unique active session per user that is implemented in my CustomAuthenticationService. Any ideas to solve this problem?
Your use case is unclear; normally the authentication adapter is the class that you would normally customise, rather than the actual authentication service.
Nevertheless, you can override the default service with your own providing you register the service with the same name and the module is loaded after the ZfcUser module.
Say your custom authentication service is in your own Auth namespace/module, with the class Auth\Service\CustomAuthenticationService.
Register the service in Auth\Module.php (or depending on the type of factory the module.config.php of that module).
class Module
{
public function getServiceConfig()
{
return [
'aliases' => [
'MyAuthenticationService' => 'zfcuser_auth_service',
],
'factories' => [
'zfcuser_auth_service' => function($sm) {
return new \Auth\Service\CustomAuthenticationService(
$sm->get('ZfcUser\Authentication\Storage\Db'),
$sm->get('ZfcUser\Authentication\Adapter\AdapterChain')
);
},
],
];
}
}
Lastly, ensure the module is loaded after ZfcUser in application.config.php.
return [
'modules' => [
//...
'ZfcUser',
'Auth',
// ...
],
];
The auth adapter is just trigged when the login action is performed. To handle each request you can override the storage adapter that allow validate the identify in every request. In your configuration file add a 'ZfcUser\Authentication\Storage\Db' attribute poiting to your custom storage class.
'service_manager' => array(
'invokables' => array(
'ZfcUser\Authentication\Storage\Db' => 'MyCustom\Authentication\Storage'),
...

Categories