Declaration of ... must be compatible with - yii

I'm using this Yii2 plugin for my user model
https://yii2-usuario.readthedocs.io/en/latest/
In my main.php i have this.
'user' => [
'class' => Da\User\Module::class,
'enableEmailConfirmation' => true,
'classMap' => [
'User' => 'app\models\user\Model\User',
'SocialNetworkAccount' => 'app\models\user\Model\SocialNetworkAccount',
],
then in the directory models/user/Model i have this
namespace app\models\user\Model;
use Yii;
use app\models\user\Model\User;
use Da\User\Model\SocialNetworkAccount as BaseClass;
class SocialNetworkAccount extends BaseClass
{
public function connect(User $user)
{
return $this->updateAttributes(
[
'username' => null,
'firstname' => $user->firstname,
'lastname' => $user->lastname,
'email' => null,
'code' => null,
'user_id' => $user->id,
]
);
}
}
but when i go to my login page i get this error
Declaration of app\models\user\Model\SocialNetworkAccount::connect(app\models\user\Model\User $user) must be compatible with Da\User\Model\SocialNetworkAccount::connect(Da\User\Model\User $user)
i tried following the tutorial here to override classes, but no luck
https://yii2-usuario.readthedocs.io/en/latest/enhancing-and-overriding/overriding-classes/
what am i doing wrong here? thanks
UPDATE:
i tried this
public function connect(\Da\User\Model\User $user)
don't get the error anymore, but now i noticed it doesn't add firstname and lastname
in my user model rules i have this
public function rules()
{
return [
...
[['email', 'firstname', 'lastname'], 'safe'],
]
}

From what you've described it looks like usuario is using DI container to create the User class instance when calling the connect() method.
When overriding it you have to follow the rule that preconditions cannot be strengthened in a subtype. That means that you can't typehint the parametr with the subclass of what the original method uses as its parametr. Your connect method definition must look like what you've posted in your question:
public function connect(\Da\User\Model\User $user)
But because of that, the dependency injector is creating an instance of Da\User\Model\User when calling connect() method. So, what you've missed is setting the DI container to create instance of app\models\user\Model\User whenever it's supposed to create instance of Da\User\Model\User like this:
'container' => [
'definitions' => [
Da\User\Model\User::class => app\models\user\Model\User::class,
],
],
Of course, for this to work your app\models\user\Model\User class must extend the Da\User\Model\User class.
There is a complete example how to override usuario's User model in usuario's documentation

Related

Laravel 8: Extending UserCrudController from Package BackPack Permission-Manager

I want to add Department field in User entity, so I decided to extend the default UserCrudController from this package https://github.com/Laravel-Backpack/PermissionManager.
So I created a custom controller with this command php artisan make:controller Admin\UserController
In file \app\Providers\AppServiceProvider.php I also add this (as instructed)
public function register()
{
$this->app->bind(
\Backpack\PermissionManager\app\Http\Controllers\UserCrudController::class,
\App\Http\Controllers\Admin\UserController::class,
);
}
Then here is the content of \App\Http\Controllers\Admin\UserController
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Backpack\PermissionManager\app\Http\Controllers\UserCrudController;
use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;
class UserController extends UserCrudController
{
public function setupCreateOperation()
{
parent::setupCreateOperation();
$fields['departments'] = [
'label' => 'Departments',
'type' => 'select2_multiple',
'name' => 'tags',
'entity' => 'tags',
'attribute' => 'name',
'model' => "App\Models\Tag",
'pivot' => true,
'wrapper' => ['class' => 'form-group col-6 col-md-4'],
'options' => (function ($query) {
return $query->where('type', 'Department')->get();
}),
];
foreach($fields as $key=>$field) {
CRUD::addField($field);
}
}
public function setupUpdateOperation()
{
parent::setupUpdateOperation();
$this->setupCreateOperation();
}
}
All seems fine, I can see all default information such as: username, email, roles, permission and my custom department fields.
The problem is, when I try to modify an existing user adding some Departments, I got this error
The email has already been taken.
The password field is required.
Somehow it's treated as new user registration. What could be the problems?
Somehow, I solved it by copying setupCreateOperation to setupUpdateOperation.
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Backpack\PermissionManager\app\Http\Controllers\UserCrudController;
use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;
class UserController extends UserCrudController
{
...
public function setupUpdateOperation()
{
parent::setupUpdateOperation();
$fields['departments'] = [
'label' => 'Departments',
'type' => 'select2_multiple',
'name' => 'tags',
'entity' => 'tags',
'attribute' => 'name',
'model' => "App\Models\Tag",
'pivot' => true,
'wrapper' => ['class' => 'form-group col-6 col-md-4'],
'options' => (function ($query) {
return $query->where('type', 'Department')->get();
}),
];
foreach($fields as $key=>$field) {
CRUD::addField($field);
}
}
}
Hopefully there's a shorter way to reduce this duplication.

Laminas Config Module Routing

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.

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 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.

ZF2 / doctrine ORM authentication different Entity

in my application (ZF2 / ORM) i have 3 Entities (with Single Table inheritance)
User
Owner extends User
Agent extends User
i want to make one single Authentication (Login) for the 3 Entity using
doctrine.authenticationservice.orm_default
module.config.php
//other doctrine config
'authentication' => array(
'orm_default' => array(
'object_manager' => 'Doctrine\ORM\EntityManager',
'identity_class' => 'Application\Entity\User',
'identity_property' => 'email',
'credential_property' => 'password',
'credential_callable' => function(User $user, $passwordGiven) {
return $user->getPassword() == md5($passwordGiven);
},
),
),
and the process of login
//LoginController.php
// ..data validation
$this->authService = $this->getServiceLocator()->get('doctrine.authenticationservice.orm_default');
$AuthAdapter = $this->authService->getAdapter();
$AuthAdapter->setIdentity($this->request->getPost('email'));
$AuthAdapter->setCredential(md5($this->request->getPost('password')));
$result = $this->authService->authenticate();
if($result->isValid()){
$identity = $result->getIdentity();
//continue
}
how can i do this process without caring about object type,
when i try to login with email of an Agent, i get this error
Catchable fatal error: Argument 1 passed to Application\Module::{closure}() must be an instance of User, instance of Application\Entity\Owner given
The error you mention is due to the type hint on:
function(User $user) {
Which leads me to believe that you have a missing namespace declaration in your config file; in which case you can either add it or use the FQCN.
function(\Application\Entity\User $user) {
Nevertheless, I don't think it's actually the problem. You can only define one 'identity_class' with doctrine authentication (which the adapter will use to load the entity from the entity manager). If you have multiple entity classes there is no way to have each of these tested with one adapter.
However, the configuration is really just creating a new authentication adapter, specifically DoctrineModule\Authentication\Adapter\ObjectRepository. One solution would be to create multiple ObjectRepository adapters, each with the correct configuration for the different entities and then loop through each of them while calling authenticate() on the Zend\Authentication\AuthenticationService.
For example :
public function methodUsedToAutheticate($username, $password)
{
// Assume we have an array of configured adapters in an array
foreach($adapters as $adapter) {
$adapter->setIdentity($username);
$adapter->setCredential($password);
// Authenticate using the new adapter
$result = $authService->authenticate($adapter);
if ($result->isValid()) {
// auth success
break;
}
}
return $result; // auth failed
}
As previously mentioned is the doctrine config will not allow for more than one adapter, so you would need to create them manually and remove your current configuration.
Another example
public function getServiceConfig()
{
return [
'factories' => [
'MyServiceThatDoesTheAuthetication' => function($sm) {
$service = new MyServiceThatDoesTheAuthetication();
// Assume some kind of api to add multiple adapters
$service->addAuthAdapter($sm->get('AuthAdapterUser'));
$service->addAuthAdapter($sm->get('AuthAdapterOwner'));
$service->addAuthAdapter($sm->get('AuthAdapterAgent'));
return $service;
},
'AuthAdapterAgent' => function($sm) {
return new DoctrineModule\Authentication\Adapter\ObjectRepository(array(
'object_manager' => $sm->get('ObjectManager'),
'identity_class' => 'Application\Entity\Agent',
'identity_property' => 'email',
'credential_property' => 'password'
));
},
'AuthAdapterOwner' => function($sm) {
return new DoctrineModule\Authentication\Adapter\ObjectRepository(array(
'object_manager' => $sm->get('ObjectManager'),
'identity_class' => 'Application\Entity\Owner',
'identity_property' => 'email',
'credential_property' => 'password'
));
},
// etc...
],
];
}
Hopefully this gives you some ideas as to what is required.
Lastly, if you would consider other modules, ZfcUser already has a 'chainable adapter' which actually does the above (but uses the event manager) so It might be worth taking a look at even if you don't use it.