beforeSave method not working in cakephp3 - cakephp

I have data that I want to modify first before saving it to my database, and so I've researched the beforeSave method.
I have a user's picture input and I want to save its path on my DB after successfully validating it and here's my current code:
src/model/table/UsersTable.php
use Cake\ORM\Entity;
use Cake\Event\Event;
use ArrayObject;
use Cake\Validation\Validator;
public function validationDefault(Validator $validator)
{
$validator
->allowEmptyFile('image_location')
->add('image_location',
[
'mimeType' => [
'rule' => array('mimeType', array( 'image/png', 'image/jpg', 'image/jpeg')),
'message' => 'Please upload images only (png, jpg).'
],
'fileSize' => [
'rule' => array('fileSize', '<=', '10MB'),
'message' => 'Image must be less than 10MB.'
],
]);
return $validator;
}
public function beforeSave($event, $entity, $options)
{
if ($entity->image_location['name']) {
$tmp = $entity->image_location['tmp_name'];
$hash = rand();
$date = data("Ymd");
$image = $dage.$hash;
$target = WWW_ROOT.'img'.DS.'uploads'.DS;
$target = $target.basename($image);
$image_location = "uploads/".$image;
$entity->image_location = $image_location;
move_uploaded_file($tmp, $target);
}
}
The only working part on this is the validation part, but after it successfully validates the image file, the beforeSave method is not working.
What error do I have in my current code or how can I use the beforeSave method in cakephp3.
Thank you very much!
EDIT
I even tried this line:
public function beforeSave($event, $entity, $options)
{
debug($entity);
if ($entity->image_location['name']) {
$tmp = $entity->image_location['tmp_name'];
$hash = rand();
$date = data("Ymd");
$image = $dage.$hash;
$target = WWW_ROOT.'img'.DS.'uploads'.DS;
$target = $target.basename($image);
$image_location = "uploads/".$image;
$entity->image_location = $image_location;
move_uploaded_file($tmp, $target);
}
}
to check the entity the beforeSave method is receiving but, it does not output anything.

Related

PHPstan: Cannot access property $work on array|EntityInterface|null

I am developing a web app, trying to keep PHPstan's suggestions in check.
I am having some difficulties with this method:
/**
* AJAX: deletes a work file
*
* #return \Cake\Http\Response|false
*/
public function delete()
{
$this->autoRender = false;
$this->viewBuilder()->setLayout('ajax');
$this->request->allowMethod(['post', 'delete']);
$data = $this->request->getData();
$data = is_array($data) ? $data : [$data];
$workFile = $this->WorkFiles->find('all')
->where(['WorkFiles.id' => $data['id']])
->contain(['Works'])
->first();
$res = [
'status' => 'error',
'message' => __('The file could not be deleted. Please, try again.'),
'class' => 'alert-error',
];
if ($workFile->work->anagraphic_id == $this->authAnagraphics['id']) { // error #1
if ($this->WorkFiles->delete($workFile)) { // error #2
$res = [
'status' => 'success',
'message' => __('File successfully deleted.'),
'class' => 'alert-success',
];
}
}
return $this->response->withStringBody((string)json_encode($res));
}
The code itself works, but I'm having two phpstan errors:
[phpstan] Cannot access property $work on array|Cake\Datasource\EntityInterface|null.
[phpstan] Parameter #1 $entity of method Cake\ORM\Table::delete() expects Cake\Datasource\EntityInterface, array|Cake\Datasource\EntityInterface|null given.
Am I doing something wrong?
Always use inline annotation then here:
/** #var \App\Model\Entity\WorkFile|null $workFile */
$workFile = $this->WorkFiles->find('all')
->where(['WorkFiles.id' => $data['id']])
->contain(['Works'])
->first();
But the comments are right, you are blindly using a possible null value afterwards, as such your code is not written correctly.
Use this instead:
/** #var \App\Model\Entity\WorkFile $workFile */
$workFile = $this->WorkFiles->find('all')
->where(['WorkFiles.id' => $data['id']])
->contain(['Works'])
->firstOrFail();

Cakephp3 : using another model in a controller

I started an app with CakePHP3 and i need to record some users's actions. So, I have migrated my log structure, I have baked my controller & model and now, I try to get a log when a user log in.
I updated my UsersController like this:
namespace App\Controller;
use App\Controller\AppController;
use App\Model\Table\LogsTable;
use App\Model\Entity\User;
use App\Model\Entity\Log;
class UsersController extends AppController {
public function login(){
$this->viewBuilder()->layout('external');
$user = $this->Users->newEntity();
if($this->request->is('post')){
$user = $this->Auth->identify();
if($user){
//DOING : enregistrement valide$log = new Log();
$log->user_id = 1;
$log->action = 'lorem ipsum';
$log->target_user = 0;
$log->target_object = 0;
$log->comment = 'test';
$logs = new LogsTable();
$logs->save($log);
$this->Auth->setUser($user);
if($this->Auth->user('security') == 'admin'){
return $this->redirect(['action' => 'admin_index']);
}else{
return $this->redirect($this->Auth->redirectUrl());
}
}
//TODO : enregistrement faux
$this->Flash->error(__('Email or password are wrong.'));
}
$this->set(compact('user'));
$this->set('_serialize', ['user']);
}
}
But it doesn't work, I have the error message for the save() :
Error: Call to a member function transactional() on a non-object
Any ideas?
This way
use Cake\ORM\TableRegistry;
$logs = TableRegistry::get('LogsTable');
$logs->save($log);
more info
EDIT since 3.6 you should use
use Cake\ORM\TableLocator
$articles = TableRegistry::getTableLocator()->get('Articles', [
'className' => 'App\Custom\ArticlesTable',
'table' => 'my_articles',
'connection' => $connectionObject,
'schema' => $schemaObject,
'entityClass' => 'Custom\EntityClass',
'eventManager' => $eventManager,
'behaviors' => $behaviorRegistry
]);
more info here

How to upload file using Cakephp 3.0?

I am trying to create a file upload on cakephp, I haven't been able to find any decent tutorials for cakephp 3.0 that go in detail, and I don't understand how to install plugins.
I have this in my add section
echo $this->Form->create('filename', array('action' => 'upload', 'type' => 'file'));
echo $this->Form->file('filename');
I haven't added anything in the controller yet
/**
* Index method
*
* #return void
*/
public function index()
{
$this->paginate = [
'contain' => ['Courses']
];
$this->set('contents', $this->paginate($this->Contents));
$this->set('_serialize', ['contents']);
}
/**
* View method
*
* #param string|null $id Content id.
* #return void
* #throws \Cake\Network\Exception\NotFoundException When record not found.
*/
public function view($id = null)
{
$content = $this->Contents->get($id, [
'contain' => ['Courses']
]);
$this->set('content', $content);
$this->set('_serialize', ['content']);
}
/**
* Add method
*
* #return void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$content = $this->Contents->newEntity();
if ($this->request->is('post')) {
$content = $this->Contents->patchEntity($content, $this->request->data);
if ($this->Contents->save($content)) {
$this->Flash->success('The content has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The content could not be saved. Please, try again.');
}
}
$courses = $this->Contents->Courses->find('list', ['limit' => 200]);
$this->set(compact('content', 'courses'));
$this->set('_serialize', ['content']);
}
/**
* Edit method
*
* #param string|null $id Content id.
* #return void Redirects on successful edit, renders view otherwise.
* #throws \Cake\Network\Exception\NotFoundException When record not found.
*/
public function edit($id = null)
{
$content = $this->Contents->get($id, [
'contain' => []
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$content = $this->Contents->patchEntity($content, $this->request->data);
if ($this->Contents->save($content)) {
$this->Flash->success('The content has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The content could not be saved. Please, try again.');
}
}
$courses = $this->Contents->Courses->find('list', ['limit' => 200]);
$this->set(compact('content', 'courses'));
$this->set('_serialize', ['content']);
}
/**
* Delete method
*
* #param string|null $id Content id.
* #return void Redirects to index.
* #throws \Cake\Network\Exception\NotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$content = $this->Contents->get($id);
if ($this->Contents->delete($content)) {
$this->Flash->success('The content has been deleted.');
} else {
$this->Flash->error('The content could not be deleted. Please, try again.');
}
return $this->redirect(['action' => 'index']);
}
but after this no idea what to do.
First of all, you need to decide on WHEN you're going to handle uploads. I've managed to create a dirty (but working) approach using beforeMarshal method and afterSave method (I'll explain why these two at the end).
If you create your file input like:
<?= $this->Form->file('submittedfile', ['class' => 'form-control input-upload', 'style' => 'height:100px']) ?>
or for hasMany association:
<?= $this->Form->file('images.'.$i.'.submittedfile', ['class' => 'form-control input-upload', 'style' => 'height:100px']) ?>
and you define the right associations:
$this->hasMany('Images', [
'foreignKey' => 'model_id'
]);
you could process those files before the Entity gets patched and saved:
public function beforeMarshal(Event $event, \ArrayObject $data, \ArrayObject $options) {
$images = array();
$dir = md5(time().$data['name']);
for ($i = 0; $i < count($data['images']); $i++) {
$image = $data['images'][$i]['submittedfile'];
if (!empty($image['name'])) {
if(!isset($data['id'])) {
$data['temp_dir'] = $dir;
}
else {
$dir = $data['id'];
}
if ($this->Images->uploadFile(array('img', 'model', $dir), $image) === true) {
$images[] = array('name' => pathinfo($image['name'], PATHINFO_FILENAME), 'ext' => pathinfo($image['name'], PATHINFO_EXTENSION));
}
}
}
$data['images'] = $images;
}
This is of course an example. I've decided to check, if there's an ID property set on the Entity (like for edit), because if it's not (like for create), you have to somehow identify the right path.
Here you've got a file uploading function:
public function uploadDir($path = array()) {
return $this->wwwRoot . implode(DS, $path);
}
public function uploadFile($path = array(), $filetoupload = null) {
if (!$filetoupload) {
return false;
}
$dir = new Folder($this->uploadDir($path), true, 755);
$tmp_file = new File($filetoupload['tmp_name']);
if (!$tmp_file->exists()) {
return false;
}
$file = new File($dir->path . DS . $filetoupload['name']);
if (!$tmp_file->copy($dir->pwd() . DS . $filetoupload['name'])) {
return false;
}
$file->close();
$tmp_file->delete();
return true;
}
If you added your images while there was no subdirectory with main entity ID, you have to rename the directory as soon as you get the ID:
public function afterSave(Event $event, Entity $entity, \ArrayObject $options) {
if(!empty($entity->temp_dir)) {
$this->Images->renameFolder(array('img', 'model', $entity->temp_dir),$entity->id);
}
}
calling:
public function renameFolder($path = array(), $newName) {
$oldPath = $this->wwwRoot . implode(DS, $path);
$nameToChange = array_pop($path);
array_push($path, $newName);
$newPath = $this->wwwRoot . implode(DS, $path);
return rename($oldPath, $newPath);
}
Using beforeMarshal you're able to inject your file data into Entity structure before the whole Entity is ready for saving (with associations).
Using afterSave you're able to identify the main object ID and call the set of objects you've uploaded before.
Remember to set recursive rights for saving files onto the directory, as well as rights for creating and renaming directories.

Saving associated models in Cakephp 3

I have a form that collects data about an Article, and I want to save that data, as well as for a model called Abstract, where an Article hasMany Abstracts. My models look like this:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AbstractsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Articles');
}
public function validationDefault(Validator $validator)
{
$validator
->notEmpty('body');
return $validator;
}
}
And
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
$this->hasMany('Abstracts');
}
public function validationDefault(Validator $validator)
{
$validator ->notEmpty('category')
return $validator;
}
}
My input form has a field named 'abstracts.body', and in my ArticlesController I have this function:
public function add()
{
$data = $this->request->data;
$article = $this->Articles->newEntity($data, [
'associated' => ['Abstracts']
]);
if ($this->request->is('post')) {
$article->user_id = $this->Auth->user('id');
$data['abstracts']['user_id'] = $article->user_id;
$data['abstracts']['approved'] = 0;
$article = $this->Articles->patchEntity($article, $data, [
'associated' => ['Abstracts']
]);
if ($this->Articles->save($article, [ 'validate' => false,
'associated' => ['Abstracts']
]) )
{
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $article);
}
My Abstracts table is pretty straightforward:
CREATE TABLE 'abstracts' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'article_id' INTEGER , 'user_id' INTEGER , 'body' TEXT, 'approved' BOOLEAN )
From debugging I can see that I have the correct 'abstracts' array within my $data (in add()), but it doesn't appear to ever try to save it to the database. Can someone please point out my error? Thanks!
Got it.
I started going wrong here:
My input form has a field named 'abstracts.body'
Because it's a hasMany relationship, I need to have that input be 'abstracts.0.body'
Then the rest of LeWestopher's answer will work-- adding an index to the fields I want to fill in from the Controller, so $data[abstracts][0]['user_id'] => ... and so on. Thanks!
You're post processing your $data['abstracts'] array incorrectly resulting in the association not saving. $data['abstracts'] is expected to be an array of Abstracts. Your issue lies here:
$data['abstracts']['user_id'] = $article->user_id;
$data['abstracts']['approved'] = 0;
You should be able to fix this pretty easily by changing this to:
foreach($data['abstracts'] as $index => $abstract) {
$abstract['user_id'] = $article->user_id;
$abstract['approved'] = 0;
$data['abstracts'][$index] = $abstract;
}
This should correctly iterate over your array of abstracts, set the user_id and approved keys appropriately and then it should save correctly.
CakePHP 3.x Documentation on Saving Associations
EDIT: Very interesting issue indeed. Try it without using patchEntity, and use newEntity by itself instead:
public function add()
{
if ($this->request->is('post')) {
$data = $this->request->data;
// Post process abstracts objects
foreach($data['abstracts'] as $index => $abstract) {
$abstract['user_id'] = $article->user_id;
$abstract['approved'] = 0;
$data['abstracts'][$index] = $abstract;
}
// Build newEntity
$article = $this->Articles->newEntity($data, [
'associated' => ['Abstracts']
]);
// Save our entity with associations
if ($this->Articles->save($article, [
'validate' => false,
'associated' => ['Abstracts']
])) {
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
// On save fail
$this->Flash->error(__('Unable to add your article.'));
$this->set('article', $article);
}
}
EDIT 2: Your issue looks like it's definitely in your form helper. Your current form helper input creates an $data array that looks like this:
$data = [
'abstracts' => [
'body' => 'example text'
],
'category' => 'Science'
];
Which SHOULD look like:
$data = [
'abstracts' => [
['body' => 'example text'],
['body' => 'Im your second abstract'],
['body' => 'Abstract three!']
],
'category' => 'Science'
];
The issue lies in:
abstracts.body
Which should read as (in array dot notation):
// abstracts.0.body
echo $this->Form->input('abstracts.0.body', [
'label' => 'summary of article',
'maxlength' =>'440',
'rows' => '7'
]);
I believe that should be the last issue you run into.

how to make login with only one field + cakephp 2.x

I am trying to make a login by getting/authorizing only one input *user_number* (Not username - password).
I made my current login page with the following way:
Cakephp2.x simple login
Any help plz!
Keep it simple
If you only have one way of identifying users, the simplest (and therefore recommended) way to identify users would be to define your own login function. e.g.:
public function login() {
if ($this->request->is('post')) {
$number = $this->request->data['User']['user_number'];
$user = $this->User->findByUserNumber($number);
if ($user && $this->Auth->login($user)) {
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Session->setFlash(__('User %d doesn\'t exist', $number), 'default', array(), 'auth');
}
}
}
Note that this varies very little from the standard way of logging a user in with Cake 2.x
Create a Custom Authentication object
Create a Custom Authentication object that authenticates uses by user-number only;
Creating Custom Authentication objects
app/Controller/Component/Auth/UserNumberAuthenticate.php
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class UserNumberAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
$userModel = $this->settings['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->settings['fields'];
if (
empty($request->data[$model])
|| empty($request->data[$model][$fields['username']])
) {
return false;
}
return $this->_findUser($request->data[$model][$fields['username']]);
}
/**
* Find a user record via his user-number/identifier
*
* #param string $usernumber The user-number/identifier.
* #return Mixed Either false on failure, or an array of user data.
*/
protected function _findUser($usernumber) {
$userModel = $this->settings['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->settings['fields'];
$conditions = array(
$model . '.' . $fields['username'] => $usernumber,
);
if (!empty($this->settings['scope'])) {
$conditions = array_merge($conditions, $this->settings['scope']);
}
$result = ClassRegistry::init($userModel)->find('first', array(
'conditions' => $conditions,
'recursive' => $this->settings['recursive'],
'contain' => $this->settings['contain'],
));
if (empty($result) || empty($result[$model])) {
return false;
}
$user = $result[$model];
unset($result[$model]);
return array_merge($user, $result);
}
}
Then specify that you want to use your custom authentication object
Inside your AppController:
public $components = array(
'Auth' => array(
'authenticate' => array(
'UserNumber' => array(
'userModel' => 'User',
'fields' => array('username' => 'user_number')
)
)
)
);

Resources