Laminas Config Module Routing - module

I have started the latest tutorial for Laminas.
The routing for a new module called Provider is not working
A 404 error occurred
Page not found.
The requested URL could not be matched by routing.
on looking at my Module.php code I see:
getConfig() is not called but
getServiceConfig() and getControllerConfig() are.
getConfig in the Application module is not called either
<?php
namespace Provider;
use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\TableGateway\TableGateway;
use Laminas\ModuleManager\Feature\AutoloaderProviderInterface;
use Laminas\ModuleManager\Feature\ConfigProviderInterface;
class Module implements ConfigProviderInterface, AutoloaderProviderInterface
{
public function getConfig()
{
die ("getConfig");
return include __DIR__ . '/../config/module.config.php';
}
public function getAutoloaderConfig()
{
//die ("getAutoloaderConfig");
//return array(
// 'Laminas\Loader\StandardAutoloader' => array(
// 'namespaces' => array(
// __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
// ),
// ),
//);
}
public function getServiceConfig()
{
//die ("getServiceConfig");
return [
'factories' => [
Model\ProviderTable::class => function($container) {
$tableGateway = $container->get(Provider\ProviderTableGateway::class);
return new Model\ProviderTable($tableGateway);
},
Model\ProviderTableGateway::class => function ($container) {
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Model\Album());
return new TableGateway('provider', $dbAdapter, null, $resultSetPrototype);
},
],
];
}
public function getControllerConfig()
{
//die ("getControllerConfig");
return [
'factories' => [
Controller\ProviderController::class => function($container) {
return new Controller\ProviderController(
$container->get(Model\ProviderTable::class)
);
},
],
];
}
}

You need to enable development mode. run composer development-enable to active development mode.

Maybe the composer json is not updated (my-application/composer.json)
"autoload": {
"psr-4": {
"Application\\": "module/Application/src/",
"Provider\\": "module/Provider/src/"
}
},
Update autoload classmap:
composer dump-autoload
https://docs.laminas.dev/tutorials/getting-started/modules/#autoloading

Have you added router configuration?
In your attached code above you have the following function :
public function getConfig()
{
//die ("getConfig"); // BE SURE YOU REMOVE THIS LINE
return include __DIR__ . '/../config/module.config.php';
}
it's include file for additional settings. in this file "/../config/module.config.php" you should add your router configuration. It should look like this:
return [
//... other setting
'router' => [
'routes' => [
// Literal route named "home"
'home' => [
'type' => 'literal',
'options' => [
'route' => '/',
'defaults' => [
'controller' => 'Application\Controller\IndexController',
'action' => 'index',
],
],
],
// Literal route named "contact"
'contact' => [
'type' => 'literal',
'options' => [
'route' => 'contact',
'defaults' => [
'controller' => 'Application\Controller\ContactController',
'action' => 'form',
],
],
],
],
],
];
more reading can be found https://docs.laminas.dev/laminas-router/routing/#simple-example-with-two-literal-routes

As mentioned before any time you add a custom module you will need to add an entry for the autoloader in composer.json and run the dump-autoload. You will also need to add an entry in the root level /config/modules.config.php file. Is there currently an entry for Application? If memory serves and your working from the examples the last two should be Application, then Album. Verify those are there and that the application is in development mode. You can check the current mode with "composer development-status". Just check composer.json in the top level and look for the "scripts" entry. The key is the command to pass to composer.
Also, be mindful of using the interfaces when configuring the application in the Module class. The Module feature methods are reserved for closures as they will not be cached when you disable development mode. Instead use the corresponding service manager array keys. that can be found here:
Service manager config:
https://docs.laminas.dev/laminas-servicemanager/configuring-the-service-manager/
Corresponding module manager feature config:
https://docs.laminas.dev/laminas-modulemanager/module-manager/
I suppose its worth mentioning that most if not all of the Feature interface methods map directly to a default pluginmanager implementation, ergo a specialized service manager.

Related

Magento2 module fields still visible in admin after module is disabled

I am experiencing an issue while trying to disable a Magento2 module causing custom values to still be visible in the customer edit page.
I would like to know what i have to do to completely get rid of a module and its data from a Magento2 system.
Magento version: 2.3.2
PHP version: 7.2.19
A custom(own) Magento2 module was installed by:
Copying code: app/code/VENDOR/MODULE
Running: magento module:enable VENDOR_MODULE
Running magento setup:upgrade
This module creates a couple of Customer EAV attributes that correctly show in the customer edit form.
I am able to populate/save/update values successfully.
I am disabling the module as such:
Running: magento module:disable VENDOR_MODULE
Running magento setup:upgrade
Completely removing the app/code/VENDOR/MODULE directory
When i navigate back to the customer edit page i can still see the attributes, visible and populated with previously entered data.
At this point i have tried the following:
Manually removing the entry in setup_module.
Including a Uninstall class.
A combination of magento cache:clean && magento setup:di:compile.
Classes attached:
InstallData.php
namespace VENDOR\MODULE\Setup;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Customer\Model\Customer;
use Magento\Customer\Setup\CustomerSetupFactory;
class InstallData implements InstallDataInterface {
private $customerSetupFactory;
/**
* Constructor
*
* #param \Magento\Customer\Setup\CustomerSetupFactory $customerSetupFactory
*/
public function __construct(CustomerSetupFactory $customerSetupFactory) {
$this->customerSetupFactory = $customerSetupFactory;
}
/**
* {#inheritdoc}
*/
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) {
$this->installModule1($setup, $context);
$this->installModule2($setup, $context);
}
private function installModule1(ModuleDataSetupInterface $setup, ModuleContextInterface $context) {
$customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
$customerSetup->addAttribute(\Magento\Customer\Model\Customer::ENTITY, 'module1', [
'type' => 'varchar',
'label' => 'Module1 label',
'input' => 'text',
'source' => '',
'required' => false,
'visible' => true,
'position' => 500,
'system' => false,
'backend' => ''
]);
$attribute = $customerSetup->getEavConfig()->getAttribute('customer', 'module1')
->addData(['used_in_forms' => [
'adminhtml_customer',
'adminhtml_checkout',
'customer_account_create',
'customer_account_edit'
]
]);
$attribute->save();
}
private function installModule1(ModuleDataSetupInterface $setup, ModuleContextInterface $context) {
$customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
$customerSetup->addAttribute(\Magento\Customer\Model\Customer::ENTITY, 'module2', [
'type' => 'varchar',
'label' => 'Module2 label',
'input' => 'text',
'source' => '',
'required' => false,
'visible' => true,
'position' => 500,
'system' => false,
'backend' => ''
]);
$attribute = $customerSetup->getEavConfig()->getAttribute('customer', 'module2')
->addData(['used_in_forms' => [
'adminhtml_customer',
'adminhtml_checkout',
'customer_account_create',
'customer_account_edit'
]
]);
$attribute->save();
}
}
Uninstall.php
namespace VENDOR\MODULE\Setup;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Db\Select;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\UninstallInterface;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
class Uninstall implements UninstallInterface {
private $_eavSetupFactory;
private $_mDSetup;
public function __construct(EavSetupFactory $eavSetupFactory, ModuleDataSetupInterface $mDSetup) {
$this->_eavSetupFactory = $eavSetupFactory;
$this->_mDSetup = $mDSetup;
}
public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context) {
$installer = $setup;
$eavSetup = $this->_eavSetupFactory->create(['setup' => $this->_mDSetup]);
$eavSetup->removeAttribute(\Magento\Catalog\Model\Customer::ENTITY, 'module1');
$eavSetup->removeAttribute(\Magento\Catalog\Model\Customer::ENTITY, 'module2');
}
}
For Customer attributes you need to delete the specific attributes entry from table "eav_attribute" you can search by "attribute_code" and delete that row, you have to delete attributes from the database because there is no functionality in admin to delete an attribute
This is the method by which the custom attribute can be removed as I also tried manually to delete that module attribute and it took more than 1 day to find this solution.
public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->removeAttribute(Customer::ENTITY, "<attribute name>");
}
You can remove multiple attribute too at same time by separating attribute name by commas.
After this just run -
bin/magento setup:upgrade && bin/magento setup:static-content:deploy -f

Standalone rbac setup for module yii2

I`ve got two modules in my yii2-basic application. For common users I have "user" table and for superadmin users I have the second "superadmin" table. There are two users with different sessions in my app - users and superadmins. I need to use different, standalone rbac for superadmin users table. Is it possible to setup two different rbacs in two modules? Now it is setup in web.php file globally. Maybe it is possible somehow to setup authManager in module separately, not in global web.php file?
Thanks in advance!
I figured it out! In your module.php file specify authManager with different table names, you are supposed to create them before. You also can specify rbac submodule of mdm\admin if you want to use it as well. Specify there another usertable - SuperAdmin in the code below.
public function init()
{
parent::init();
$this->modules = [
'rbac' => [
'class' => 'mdm\admin\Module',
'controllerMap' => [
'assignment' => [
'class' => 'mdm\admin\controllers\AssignmentController',
'userClassName' => 'app\models\SuperAdmin',
'idField' => 'id',
'usernameField' => 'username',
],
],
'layout' => 'left-menu',
'mainLayout' => '#app/views/layouts/rbac.php',
],
];
$config = [
'components' => [
'authManager' => [
'class' => 'yii\rbac\DbManager',
'itemTable' => 'superadmin_auth_item',
'assignmentTable' => 'superadmin_auth_assignment',
'itemChildTable' => 'superadmin_auth_item_child',
'ruleTable' => 'superadmin_auth_rule',
],
],
'as access' => [
'class' => 'app\components\SuperAdminAccessControl',//your overridden AccessControl class
'allowActions' => [
'admin/rbac/*',
'admin/default/login',
'admin/default/logout',
]
],
];
\Yii::$app->authManager->itemTable = 'superadmin_auth_item';
\Yii::$app->authManager->assignmentTable = 'superadmin_auth_assignment';
\Yii::$app->authManager->itemChildTable = 'superadmin_auth_item_child';
\Yii::$app->authManager->ruleTable = 'superadmin_auth_rule';
\Yii::configure(\Yii::$app, $config);
}
Overridden AccessControl class:
namespace app\components;
class SuperAdminAccessControl extends \mdm\admin\components\AccessControl
{
function __construct() {
$this->setUser('superadmin');//table name
}
}

How to login user using rest api in yii2

I new in yii2, I want login user using rest api but unable to do this.I have setup basic REST API From This blog:
budiirawan.com/setup-restful-api-yii2/
After that I have created :
api\modules\v1\controllers\SiteController.php
<?php
namespace api\modules\v1\controllers;
use Yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use common\models\LoginForm;
use yii\filters\VerbFilter;
use yii\rest\ActiveController;
/**
* Site controller
*/
class SiteController extends ActiveController
{
/**
* #inheritdoc
*/
public $modelClass = 'api\modules\v1\models\user';
public function actionIndex()
{
if (!\Yii::$app->user->isGuest) {
return $this->goHome();
}
$model = new LoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack();
} else {
return $this->render('login', [
'model' => $model,
]);
}
}
public function actionLogout()
{
Yii::$app->user->logout();
return $this->goHome();
}
}
And Created Model
RtWorkForce\api\modules\v1\models\User.php
<?php
namespace api\modules\v1\models;
use \yii\db\ActiveRecord;
/**
* User Model
*
*/
class User extends ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return '{{%user}}';
}
}
Here Is my main.php
<?php
$params = array_merge(
require(__DIR__ . '/../../common/config/params.php'),
require(__DIR__ . '/../../common/config/params-local.php'),
require(__DIR__ . '/params.php'),
require(__DIR__ . '/params-local.php')
);
return [
'id' => 'app-api',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'modules' => [
'v1' => [
'basePath' => '#app/modules/v1',
'class' => 'api\modules\v1\Module'
]
],
'components' => [
'request' => [
'parsers' => [
'application/json' => 'yii\web\JsonParser',
]
],
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => false,
],
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['v1/country','v1/user','v1/site'],
'tokens' => [
'{id}' => '<id:\\w+>'
]
]
],
]
],
'params' => $params,
];
But IT's not working i don't know where i am wrong ??
From Yii 2.0 REST Authentication docs :
Unlike Web applications, RESTful APIs are usually stateless, which
means sessions or cookies should not be used.
And from this other docs about the user class which implement yii\web\IdentityInterface :
if your application is a pure stateless RESTful application, you would
only need to implement findIdentityByAccessToken() and getId() while
leaving all other methods with an empty body.
RESTfull is about routing. If following a token based authentication, then, It should return a resources or a collections if the request sent to server is holding a valid token. Otherwise it should be rejected.
Login process in that case, is one request holding a username/password pair that will be exchanged with a valid token by server, that is the token you are going to include with all your next requests.
If session is disabled on server side by setting enableSession to false as described in Yii documentation (see links above) and as recommended by the stateless nature of REST, then \Yii::$app->user->isGuest should not provide info as your server won't have any session to get it from. (unless it verifies token validity instead of checking session)
When building a class extending yii\rest\ActiveController you can't render a html page like :
return $this->render('login', [
'model' => $model,
]);
or redirect to a different HTML page like :
return $this->goHome();
That will work with a yii\base\Controller when building a HTML based web app instead of yii\rest\ActiveController. with ActiveController you just return data which will be serialized to json or xml before output.
Please refer to Yii RESTful API framework documentations for more details. Then you may find useful information in this great tutorial on How to implement Yii2 REST Authentication :
http://blog.neattutorials.com/angularjs-and-yii2-part-2-authentication/

Access control of a Module in Yii2

I am having a trouble with login part.
I read this topic : http://www.yiiframework.com/wiki/771/rbac-super-simple-with-admin-and-user/ .
Then i follow its steps, but in step 6. it only configs for just one Controller. I have a Module called Admin with many controllers in it and i don't know how to apply this access control to the whole module. Can anyone help me ?
Sorry for my bad English.
You can create AdminController class, which will extends yii\web\Controller where you define your access rules in behaviors method and make other module controllers extend your AdminController and override behaviors method like this:
public function behaviors()
{
return \yii\helpers\ArrayHelper::merge(parent::behaviors(), [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['post'],
],
],
]);
}
Here parent::behaviors() are behaviors from AdminController which define default access rules, and you merge them with specific behaviors in your child controller. It gives you flexibility to override some access rules if you need.
I can propose a variation of the method from the article that you mentioned.
Make first 2 steps as it was described and then do the following:
1. Add the field role to User model and evaluate it with thevalue of one of the constants from the article's example (User::ROLE_ADMIN or User::ROLE_USER)
2. Override the yii\web\User->can()
public function can($permissionName, $params = [], $allowCaching = true)
{
/** #var \app\models\User $user */
$user = $this->identity;
$access = false;
do {
if (\Yii::$app->user->isGuest) {
break;
}
if ($user->role === \common\models\User::ROLE_ADMIN) {
$access = true;
break;
}
if (is_array($permissionName)) {
$access = in_array($user->role, $permissionName);
} else {
$access = $permissionName === $user->role;
}
} while (false);
return $access;
}
So now you can check user's role like this:
\Yii::$app->user->can(User::ROLE_USER)
3. You say:
i don't know how to apply this access control to the whole module.
Then open your module class and add the following to the behaviors() method:
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => [User::ROLE_ADMIN]
]
]
]
];
}
In this example we grant access to ROLE_ADMIN to all actions of all controllers of your module.
That's it.
Make a custom model AccessRules.php as shown below:
<?php
namespace app\models;
class AccessRules extends \yii\filters\AccessRule
{
/**
* #inheritdoc
*/
protected function matchRole($user)
{
if (empty($this->roles)) {
return true;
}
foreach ($this->roles as $role) {
if ($role === '?') {
if ($user->getIsGuest()) {
return true;
}
} elseif ($role === '#') {
if (!$user->getIsGuest()) {
return true;
}
// Check if the user is logged in, and the roles match
} elseif (!$user->getIsGuest() && (int)$role === $user->identity->user_role) {
return true;
}
}
return false;
}
}
?>
Now open your site controller and add the following code in fuction behavior part:
use app\models\AccessRules;
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
// We will override the default rule config with the new AccessRule class
'ruleConfig' => [
'class' => AccessRules::className(),
],
'only' => ['create', 'update', 'delete','index'],
'rules' => [
[
'actions' => ['create', 'update', 'delete','index'],
'allow' => true,
// Allow admin to create
'roles' => [
'1'
],
]
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['post'],
],
],
];
}
According to The Yii2 Guide
"ACF is an action filter that can be used in a controller or a module" in the same way.
Just add below code in controller which you want to restrict functionality
'access' => [
'class' => AccessControl::className(),
'rules' =>
[
[
'actions' => ['index','view'],
'allow' => true,
'roles' => ['#']
],
[
'actions' => ['create','update','delete'],
'allow' => true,
'roles' => ['#'],
'matchCallback' => function ($rule, $action)
{
return Admin::isUserAdmin(Yii::$app->user->identity->username);
}
],
],
],

How to use yii2-user as sub-module inside another module

I want to implement one module in an existing application.In that module I am trying to use yii2-user module.The yii2 docs say we can do that.
namespace app\modules\forum;
class Module extends \yii\base\Module
{
public function init()
{
parent::init();
$this->modules = [
'admin' => [
// you should consider using a shorter namespace here!
'class' => 'app\modules\forum\modules\admin\Module',
],
];
}
}
I have module code as
namespace app\modules\cdas;
class cdas extends \yii\base\Module
{
public $controllerNamespace = 'app\modules\cdas\controllers';
public function init()
{
parent::init();
// custom initialization code goes here
$this->modules = [
'user' => [
'class' => 'dektrium\user\Module',
'modelMap' => [
'Profile' => 'app\modules\cdas\models\users\Profile',
'User'=>'aapp\modules\cdas\models\users\User',
],
'controllerMap' => [
'settings' => 'app\modules\cdas\controllers\user\SettingsController',
'admin' => 'app\modules\cdas\controllers\user\AdminController',
'role' => 'app\modules\cdas\controllers\user\RoleController',
'security' => 'app\modules\cdas\controllers\user\SecurityController',
],
];
}
}
But when I use the above methodology and try to access
http://localhost/<app_name>/web/cdas/user/security/login
I get the following error
PHP Notice – yii\base\ErrorException
Trying to get property of non-object
in ....vendor\dektrium\yii2-user\views\_alert.php
/**
* #var $module dektrium\user\Module
*/
<?php if ($module->enableFlashMessages): ?>
Please suggest proper way to implement the sub-module.