CakePHP 4.1 User entity as authorization identity associated fields - cakephp

I have just created a very minimal project in CakePHP 4.1, mostly mimicking the CMS tutorial, and want to implement a fairly straightforward piece of logic.
Using the Authorization module I want to allow a user A to be able to view a user B if 1) they are actually the same user (A = B) OR 2) if A is an admin.
There are two DB tables - users and user_types. users has a foreign key user_type_id to user_types.
This relationship is reflected in code as:
##### in UsersTable.php #####
class UsersTable extends Table {
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->belongsTo('UserTypes');
$this->addBehavior('Timestamp');
}
//...
}
##### in UserTypesTable.php #####
class UserTypesTable extends Table {
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('user_types');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->hasMany('Users');
}
//...
}
In UsersController.php I have:
public function view($id = null)
{
$user = $this->Users->get($id, [
'contain' => ['UserTypes'],
]);
$this->Authorization->authorize($user);
$this->set(compact('user'));
}
And in UserPolicy.php:
use App\Model\Entity\User;
class UserPolicy
{
public function canView(User $user, User $resource)
{
// TODO: allow view if $user and $resource are the same User or if $user is an admin
//
// My problem is that here $user->user_type is NULL
// while $resource->user_type is populated correctly
}
}
The code comment in the above excerpt shows where my problem is.
I do not know how to get $user to have its user_type field populated in order to check whether they're an admin.
As a part of my efforts, I have set the User class to be the authorization identity, following this article: https://book.cakephp.org/authorization/2/en/middleware.html#using-your-user-class-as-the-identity.
Code-wise this looks like:
##### relevant part of Application.php #####
$middlewareQueue
->add(new AuthenticationMiddleware($this))
->add(new AuthorizationMiddleware($this, [
'identityDecorator' => function(\Authorization\AuthorizationServiceInterface $auth, \Authentication\IdentityInterface $user) {
return $user->getOriginalData()->setAuthorization($auth);
}
]));
##### User.php #####
namespace App\Model\Entity;
use Authentication\PasswordHasher\DefaultPasswordHasher;
use Authorization\AuthorizationServiceInterface;
use Authorization\Policy\ResultInterface;
use Cake\ORM\Entity;
/**
* User Entity
*
* #property int $id
* #property string $email
* #property string $password
* #property string|null $name
* #property \App\Model\Entity\UserType $user_type
* #property \Cake\I18n\FrozenTime|null $created
* #property \Cake\I18n\FrozenTime|null $modified
* #property \Authorization\AuthorizationServiceInterface $authorization
*/
class User extends Entity implements \Authorization\IdentityInterface, \Authentication\IdentityInterface
{
protected $_accessible = [
'email' => true,
'password' => true,
'name' => true,
'created' => true,
'modified' => true,
];
/**
protected $_hidden = [
'password',
];
protected function _setPassword(string $password) : ?string
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher())->hash($password);
}
}
/**
* #inheritDoc
*/
public function can(string $action, $resource): bool
{
return $this->authorization->can($this, $action, $resource);
}
/**
* #inheritDoc
*/
public function canResult(string $action, $resource): ResultInterface
{
return $this->authorization->canResult($this, $action, $resource);
}
/**
* #inheritDoc
*/
public function applyScope(string $action, $resource)
{
return $this->authorization->applyScope($this, $action, $resource);
}
/**
* #inheritDoc
*/
public function getOriginalData()
{
return $this;
}
/**
* Setter to be used by the middleware.
* #param AuthorizationServiceInterface $service
* #return User
*/
public function setAuthorization(AuthorizationServiceInterface $service)
{
$this->authorization = $service;
return $this;
}
/**
* #inheritDoc
*/
public function getIdentifier()
{
return $this->id;
}
}
However, I have not been able to get the identity User in the UserPolicy.php file to have the user_type field populated.
Some under-the-hood magic seems to happen when I call $this->Authorization->authorize() from the controller where I explicitly pass the resource together with its user type (since I have constructed it with 'contain' => ['UserTypes'] BUT the identity user is populated automatically by the Authorization module.
Could someone please help me to find a way to bring associated tables data into the identity user of an authorization policy?
NOTE:
I have fudged the code to make it work like this:
##### in UserPolicy.php #####
use App\Model\Entity\User;
class UserPolicy
{
public function canView(User $user, User $resource)
{
$user = \Cake\Datasource\FactoryLocator::get('Table')->get('Users')->get($user->id, ['contain' => ['UserTypes']]);
// Now both $user->user_type and $resource->user_type are correctly populated
}
}
HOWEVER, this feels awfully "hacky" and not the way it's supposed to be, so my original question still stands.

The identity is being obtained by the resolver of the involved identifier. In case of the CMS tutorial that's the Password identifier which by default uses the ORM resolver.
The ORM resolver can be configured to use a custom finder in case you need to control the query for obtaining the user, that's where you should add the containment for your UserTypes association.
In your UsersTable add a finder like this:
public function findForAuthentication(\Cake\ORM\Query $query, array $options): \Cake\ORM\Query
{
return $query->contain('UserTypes');
}
and configure the identifier's resolver to use that finder like this:
$service->loadIdentifier('Authentication.Password', [
'resolver' => [
'className' => 'Authentication.Orm',
'finder' => 'forAuthentication',
],
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
You need to specify the resolver class name too when overriding the resolver option, as by default it is just a string, not an array that would merge with the new config!
See also
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods
Authentication Cookbook > Identifiers

Related

CakePHP Tutorial Error: The request to /users did not apply any authorization checks

I'm getting the error:
The request to /users did not apply any authorization checks.
When I try to run http://localhost:8765/users
I thought I followed the tutorial step by step correctly, but I most likely missed something somewhere. What's a good way to start debugging this error?
My UsersController.php file looks like this:
declare(strict_types=1);
namespace App\Controller;
/**
* Users Controller
*
* #property \App\Model\Table\UsersTable $Users
* #method \App\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
*/
class UsersController extends AppController
{
public function beforeFilter(\Cake\Event\EventInterface $event)
{
parent::beforeFilter($event);
// Configure the login action to not require authentication, preventing
// the infinite redirect loop issue
$this->Authentication->addUnauthenticatedActions(['login', 'add']);
}
public function login()
{
$this->request->allowMethod(['get', 'post']);
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result->isValid()) {
// redirect to /articles after login success
$redirect = $this->request->getQuery('redirect', [
'controller' => 'Articles',
'action' => 'index',
]);
return $this->redirect($redirect);
}
// display error if user submitted and authentication failed
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error(__('Invalid username or password'));
}
$this->Authorization->skipAuthorization();
}
/**
* Index method
*
* #return \Cake\Http\Response|null|void Renders view
*/
public function index()
{
$users = $this->paginate($this->Users);
$this->set(compact('users'));
}
/**
* View method
*
* #param string|null $id User id.
* #return \Cake\Http\Response|null|void Renders view
* #throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$user = $this->Users->get($id, [
'contain' => ['Articles'],
]);
$this->set(compact('user'));
}
/**
* Add method
*
* #return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$this->Authorization->skipAuthorization();
$user = $this->Users->newEmptyEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->getData());
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
$this->set(compact('user'));
}
/**
* Edit method
*
* #param string|null $id User id.
* #return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
* #throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$user = $this->Users->get($id, [
'contain' => [],
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$user = $this->Users->patchEntity($user, $this->request->getData());
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
$this->set(compact('user'));
}
/**
* Delete method
*
* #param string|null $id User id.
* #return \Cake\Http\Response|null|void Redirects to index.
* #throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$user = $this->Users->get($id);
if ($this->Users->delete($user)) {
$this->Flash->success(__('The user has been deleted.'));
} else {
$this->Flash->error(__('The user could not be deleted. Please, try again.'));
}
return $this->redirect(['action' => 'index']);
}
// in src/Controller/UsersController.php
public function logout()
{
$this->Authorization->skipAuthorization();
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result->isValid()) {
$this->Authentication->logout();
return $this->redirect(['controller' => 'Users', 'action' => 'login']);
}
}
}
And my Application.php like this:
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* #copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* #link https://cakephp.org CakePHP(tm) Project
* #since 3.3.0
* #license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App;
use Cake\Core\Configure;
use Cake\Core\ContainerInterface;
use Cake\Core\Exception\MissingPluginException;
use Cake\Datasource\FactoryLocator;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\BaseApplication;
use Cake\Http\Middleware\BodyParserMiddleware;
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Http\MiddlewareQueue;
use Cake\ORM\Locator\TableLocator;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
// Authentication
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;
// Authorization
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;
/**
* Application setup class.
*
* This defines the bootstrapping logic and middleware layers you
* want to use in your application.
*/
class Application extends BaseApplication implements AuthenticationServiceProviderInterface, AuthorizationServiceProviderInterface {
/**
* Load all the application configuration and bootstrap logic.
*
* #return void
*/
public function bootstrap(): void
{
// Call parent to load bootstrap from files.
parent::bootstrap();
if (PHP_SAPI === 'cli') {
$this->bootstrapCli();
} else {
FactoryLocator::add(
'Table',
(new TableLocator())->allowFallbackClass(false)
);
}
/*
* Only try to load DebugKit in development mode
* Debug Kit should not be installed on a production system
*/
if (Configure::read('debug')) {
$this->addPlugin('DebugKit');
}
$this->addPlugin('Authorization');
// Load more plugins here
}
/**
* Setup the middleware queue your application will use.
*
* #param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
* #return \Cake\Http\MiddlewareQueue The updated middleware queue.
*/
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
// Catch any exceptions in the lower layers,
// and make an error page/response
->add(new ErrorHandlerMiddleware(Configure::read('Error')))
// Handle plugin/theme assets like CakePHP normally does.
->add(new AssetMiddleware([
'cacheTime' => Configure::read('Asset.cacheTime'),
]))
// Add routing middleware.
// If you have a large number of routes connected, turning on routes
// caching in production could improve performance. For that when
// creating the middleware instance specify the cache config name by
// using it's second constructor argument:
// `new RoutingMiddleware($this, '_cake_routes_')`
->add(new RoutingMiddleware($this))
->add(new AuthenticationMiddleware($this))
->add(new AuthorizationMiddleware($this))
// Parse various types of encoded request bodies so that they are
// available as array through $request->getData()
// https://book.cakephp.org/4/en/controllers/middleware.html#body-parser-middleware
->add(new BodyParserMiddleware())
// Cross Site Request Forgery (CSRF) Protection Middleware
// https://book.cakephp.org/4/en/controllers/middleware.html#cross-site-request-forgery-csrf-middleware
->add(new CsrfProtectionMiddleware([
'httponly' => true,
]));
return $middlewareQueue;
}
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => Router::url('/users/login'),
'queryParam' => 'redirect',
]);
// Load identifiers, ensure we check email and password fields
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
// Load the authenticators, you want session first
$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'loginUrl' => Router::url('/users/login'),
]);
return $authenticationService;
}
/**
* Register application container services.
*
* #param \Cake\Core\ContainerInterface $container The Container to update.
* #return void
* #link https://book.cakephp.org/4/en/development/dependency-injection.html#dependency-injection
*/
public function services(ContainerInterface $container): void
{
}
/**
* Bootstrapping for CLI application.
*
* That is when running commands.
*
* #return void
*/
protected function bootstrapCli(): void
{
try {
$this->addPlugin('Bake');
} catch (MissingPluginException $e) {
// Do not halt if the plugin is missing
}
$this->addPlugin('Migrations');
// Load more plugins here
}
public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
$resolver = new OrmResolver();
return new AuthorizationService($resolver);
}
}
What am I failing to see? Thanks!
Add skip authorization to your index method in atricles & users controller
$this->Authorization->skipAuthorization();

Laravel 8.1 how to make seeder of user table

I am trying to seed the user table but I am facing some issues can someone guide me where I am missing.
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
DB::table('users')->insert([
'name' => Str::random(10),
'email' => Str::random(10).'#example.com',
'password' => Hash::make('password'),
]);
}
}
I think you are missing the below line
use Illuminate\Support\Str;
The complete code look like
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeders.
*
* #return void
*/
public function run()
{
DB::table('users')->insert([
'name' => Str::random(10),
'email' => Str::random(10).'#gmail.com',
'password' => Hash::make('password'),
]);
}
}
Try something like this using eloquent instead of the DB class with an existence check too:
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
// Seed test user 1
$seededAdminEmail = 'admin#admin.com';
$user = User::where('email', '=', $seededAdminEmail)->first();
if ($user === null) {
$user = User::create([
'name' => 'Admin',
'email' => $seededAdminEmail,
'password' => Hash::make('password'),
]);
}
// Seed test user 2
$user = User::where('email', '=', 'user#user.com')->first();
if ($user === null) {
$user = User::create([
'name' => 'User',
'email' => 'user#user.com',
'password' => Hash::make('password'),
]);
}
}
}

yii error active record query

i'm trying to use yii and i follow a tutorial to simple create a page that shoe fields of database's table.
i create index view
<?php
/* #var $this yii\web\View */
?>
<h1>articoli/index</h1>
<p>
pippo
<?php
foreach($posts as $post){?>
<h1><?php echo $post->autore; ?> </h1>
<p><?php echo $post->articolo; ?></p>
}
?>
</p>
in controllers i create ArticoliController
<?php
namespace app\controllers;
class ArticoliController extends \yii\web\Controller
{
public function actionIndex()
{
$posts=Articoli::model()->findall();
$data['posts']=$posts;
return $this->render('index',$data);
}
public function actionSaluta(){
$vsa['messaggio']='Alessio';
return $this->render('saluta',$vsa);
}
}
in model i create Articoli .php
<?php
namespace app\models;
use Yii;
/**
* This is the model class for table "articoli".
*
* #property integer $id
* #property string $autore
* #property string $articolo
*/
class Articoli extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'articoli';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['articolo'], 'required'],
[['autore'], 'string', 'max' => 55],
[['articolo'], 'string', 'max' => 255],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'autore' => 'Autore',
'articolo' => 'Articolo',
];
}
}
when i try it return
PHP Fatal Error – yii\base\ErrorException
Class 'app\controllers\Articoli' not found
i don't understand. I think that it must go to app\models\Articoli.php
I try different way
$posts=Articoli::->findall();
but don't work
Yii2 ActiveRecord don't have static function model(). To fetch all records from Articoli you have to use findAll() static method, or find()->all().
Change usage in controller to:
$posts = Articoli::findAll();
In your controller add use:
use \app\models\Articoli;
Or just change this line:
$posts=Articoli::model()->findall();
to this:
$posts = \app\models\Articoli::findAll();
And that's all! ;)

cakephp event listener not found

I'm using Cakephp 3.2 and proffer plugin to upload images.
By default the path of the image is as follows
/media/files/<tablename>/<primary_key>/<filename>
Each time a new row is inserted into same table a new folder is created by its primary key.
I want to upload all images of a table to the same directory. means path like
/media/files/<tablename>/<filename>
I'm using event listener as per given in proffer documentation.
This is my SellersTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\Event\Event;
class SellersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$listener = new App\Event\UploadFileNameListener(); // line 23
$this->eventManager()->on($listener);
$this->table('sellers');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->addBehavior('Proffer.Proffer', [
'profile_picture' => [
'root' => Configure::read('ArgoSystems.media.upload') . DS . 'files',
'dir' => 'dir'
]
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmpty('id', 'create');
$validator
->requirePresence('first_name', 'create')
->notEmpty('first_name');
$validator
->requirePresence('last_name', 'create')
->notEmpty('last_name');
$validator
->email('email')
->requirePresence('email', 'create')
->notEmpty('email')
->add('email', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
$validator->provider('proffer', 'Proffer\Model\Validation\ProfferRules');
$validator
->add('profile_picture', 'proffer', [
'rule' => ['dimensions', [
'min' => ['w' => 100, 'h' => 500],
'max' => ['w' => 100, 'h' => 500],
]],
'message' => 'Image must be of 100 x 500 resolution',
'provider' => 'proffer'
])
->requirePresence('profile_picture', 'create')
->allowEmpty('profile_picture','update');
$validator
->requirePresence('password', 'create')
->notEmpty('password');
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['email']));
return $rules;
}
}
and created UploadFileNameListener.php in src/Event/
<?php
namespace App\Event;
use Cake\Event\Event;
use Cake\Event\EventListenerInterface;
use Cake\Utility\Inflector;
use Proffer\Lib\ProfferPath;
class UploadFileNameListener implements EventListenerInterface
{
public function implementedEvents()
{
return [
'Proffer.afterPath' => 'change',
];
}
/**
* Rename a file and change it's upload folder before it's processed
*
* #param Event $event The event class with a subject of the entity
* #param ProfferPath $path
* #return ProfferPath $path
*/
public function change(Event $event, ProfferPath $path)
{
// Detect and select the right file extension
switch ($event->subject()->get('image')['type']) {
default:
case "image/jpeg":
$ext = '.jpg';
break;
case "image/png":
$ext = '.png';
break;
case "image/gif":
$ext = '.gif';
break;
}
// Create a new filename using the id and the name of the entity
$newFilename = $event->subject()->get('id') . '_' . Inflector::slug($event->subject()->get('name')) . $ext;
// set seed
$path->setSeed('profile_picture');
// Change the filename in both the path to be saved, and in the entity data for saving to the db
$path->setFilename($newFilename);
$event->subject('image')['name'] = $newFilename;
// Must return the modified path instance, so that things are saved in the right place
return $path;
}
}
But this is giving Fatal error as
Error: Uncaught Error: Class 'App\Model\Table\App\Event\UploadFileNameListener' not found in
/var/www/html/projects/admin/src/Model/Table/SellersTable.php:23
From the error message, it's clear that it's trying to load the class with a namespace relative to the namespace of your current class. Try
$listener = new \App\Event\UploadFileNameListener();

Cakephp 3 : Error while creating and saving multiple entities from file input

I have a problem with my cakephp3 app. I try to import some subscribers from a CSV file.
I use a Behavior to import my file (https://github.com/ProLoser/CakePHP-CSV). I haven't manage to load the plugin, so I have put the Behavior file on my project under src/Model/Behavior.
I have added the behavior to my SubscribersTable.
I have created a new view (import/export) and I have added this method to my SubscribersController :
public function importExport()
{
if ($this->request->is('post'))
{
$data = $this->request->data;
if(!empty($data['import_file']['name']))
{
$file = $data['import_file'];
if ((isset($file['tmp_name']) &&($file['error'] == UPLOAD_ERR_OK)))
{
$subscribersData = $this->Subscribers->importCsv($file['tmp_name']);
$entities = $this->Subscribers->newEntities($subscribersData);
$table = $this->Subscribers;
$table->connection()->transactional(function () use ($table, $entities) {
foreach ($entities as $entity) {
$table->save($entity, ['atomic' => false]);
}
});
}
}
}
}
The data I get seems to be good, as the entities created, but, when I call $table-save(), I have this error :
Table "App\Model\Table\SubscribersTable" is not associated with "request"
I have read some other questions on stackoverflow, but I don't understand why I have this error. I am new to cakephp so I don't understand everything...
If can someone help me...
Thanks !!
EDIT : It seems during the debug, the behavior is strange. Maybe this error has nothing to see with my real problem.
Here is the debug timeline :
$table->save() call:
if ($options['atomic']) {
$success = $connection->transactional(function () use ($entity, $options) {
return $this->_processSave($entity, $options);
});
} else {
===> $success = $this->_processSave($entity, $options);
}
...
$this->_processSave call :
$data = $entity->extract($this->schema()->columns(), true);
and during the extract, no column is retrieved because
public function extract(array $properties, $onlyDirty = false)
{
$result = [];
foreach ($properties as $property) {
if (!$onlyDirty || $this->dirty($property)) {
$result[$property] = $this->get($property);
}
}
return $result;
}
$onlyDirty = true and $this->dirty($property)) return false.
So, when this function is called
$success = $this->_insert($entity, $data);
as the data is null, nothing is saved.
I don't really understant the concept of dirty. In the doc, it seems it is usefull when working with BelongToMany, but this element has no link with other tables, so if someone can clarify this concept ?
SubscribersTable :
<?php
namespace App\Model\Table;
use App\Model\Entity\Subscriber;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Subscribers Model
*
*/
class SubscribersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('subscribers');
$this->displayField('email');
$this->primaryKey('email');
$options = array(
// Refer to php.net fgetcsv for more information
'length' => 0,
'delimiter' => ',',
'enclosure' => '"',
'escape' => '\\',
// Generates a Model.field headings row from the csv file
'headers' => true,
// If true, String $content is the data, not a path to the file
'text' => false,
);
$this->addBehavior('Csv', $options);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->add('email', 'valid', ['rule' => 'email'])
->allowEmpty('email', 'create');
$validator
->allowEmpty('contact');
$validator
->allowEmpty('company');
$validator
->allowEmpty('postal_code');
$validator
->add('vip', 'valid', ['rule' => 'boolean'])
->allowEmpty('vip');
$validator
->add('indoor', 'valid', ['rule' => 'boolean'])
->allowEmpty('indoor');
$validator
->add('live', 'valid', ['rule' => 'boolean'])
->allowEmpty('live');
$validator
->add('prod', 'valid', ['rule' => 'boolean'])
->allowEmpty('prod');
$validator
->allowEmpty('localisation');
$validator
->allowEmpty('family');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['email']));
return $rules;
}
}
This error likely comes from inside your SubscribersTable.php.
If you have $this->request ... in that file, it will throw that error because it's attempting to find request as an associated model, since it's not a valid method to call from a model.
Or, if it's in your controller, you likely have $this->Subscribers->request...
It could also be from inside another Model if you refer to it like this: $this->Subscribers->request ...
Ok.
As I thought, the problem wasn't this error (probably due to the use of the debugger), but it was the structure of my input file.
The input file must be formatted as indicated in this document :
http://book.cakephp.org/3.0/en/orm/saving-data.html#converting-multiple-records
The problem has been resolved !
Thanks again for you help !

Resources