cakephp use record values in afterDelete - cakephp

I am using the soft delete plugin for my EmployeesTable.php in CakePHP 3.0.
When I soft delete an employee I wish to update their associated user table with $user->set('active', false); within the afterDelete()
My problem is getting the user_id used in the employees table into the afterDelete()
I have even tried to just the id of the employee deleted and do a find on that but the examples I have seen using the $this->id return and error Table "App\Model\Table\EmployeesTable" is not associated with "id" from the beforeDelete.
EmployeesTable.php
public function afterDelete( ) {
$user = $this->Users->get($this->user_id);
$user->set('active', false);
$this->Users->save($user);
}
I have also tried catching $this->id in beforeDelete() and passing it to the afterDelete() but again get the error Table "App\Model\Table\EmployeesTable" is not associated with "id"

private $deletedUserId;
public function beforeDelete($event, $entity, $options) {
$this->deletedUserId = $entity->user_id;
}
public function afterDelete( ) {
$user = $this->Users->get($this->deletedUserId);
$user->set('active', false);
$this->Users->save($user);
}

I haven't used Cake 3 yet, but I've always solved this in Cake 2.x using that last try of yours. Problem is, $this->id is not a safe variable to use, as it already exists in the model and is modified during database interactions.
I'd say you just need to have a private $deletedId; in your model, set it in beforeDelete() and get it in afterDelete(). You can even unset it right after using it, just in case.

I'm not sure but I think you can use the Event object with entities like:
public function afterDelete(Event $event) {
$entity = $event->data["entity"];
Log::debug("all data: " . $entity);
}
This will log a array inside the debug.log file, with all the data of the deleted user.

Related

How to use beforeSave in CakePHP 3? $event, $entity and $options must be always filled?

I'm inside "PostsTable.php" I'm trying to get form data to treat image files.
In CakePHP 2, I used to do:
public function beforeSave($options = array())
{
if(!empty($this->data['Post']['picture']['name'])...
Someone could explain this in Cake 3:
beforeSave
Cake\ORM\Table::beforeSave(Event $event, Entity $entity, ArrayObject $options)
?
ADDED
I try this snippet of code to see if I'm able to save this field on database just as a test but it seems beforeSave is being ignored:
public function beforeSave($options)
{
if(!empty($entity->pic1['name']))
{
$entity->pic1 = 'jus a test';
}
Thanks
Start with the function definition.
Cake\ORM\Table::beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
Since CakePHP is calling the function automatically, this is how it is being called, so build your function identically to the function definition:
// In PostsTable.php
public function beforeSave($event, $entity, $options) {
}
If you aren't sure what data is being sent, use CakePHP's debug() function:
debug($event); debug($entity); debug($options);
Once you find your data in $entity use it to do what you want to do to your data:
if (!empty($entity->picture['name'])) { ...
Here is an example that I used:
use Cake\Event\Event;
public function beforeSave(Event $event)
{
$entity = $event->getData('entity');
if(!empty($entity->picture['name']){
// your action here
}
}
Let me read the manual for you:
The Model.beforeSave event is fired before each entity is saved. Stopping this event will abort the save operation. When the event is stopped the result of the event will be returned.
Also:
Model.beforeSave: Will be triggered just before the list of fields to be persisted is calculated. It receives both the entity and the options as arguments. The options array is passed as an ArrayObject, so any changes in it will be reflected in every listener and remembered at the end of the event so it can be used for the rest of the save operation. Returning false in any of the listeners will abort the saving process. If the event is stopped using the event API, the event object's result property will be returned. This can be useful when having your own saving strategy implemented inside a listener.
To do whatever you did before in Cake2 you can simply modify $entity because entities have replaced the Model::$data property.
if(!empty($entity->picture['name'])
If you don't know how events work read about them. Also the migration guide is a must read. It lists everything that has changed.

Yii2 add condition in the last one of chain relations

Right now I have:
$products = Product::findAll([1,2,3,4]);
foreach ($products as $product){
$text = $product->part->type->texts;
}
This returns the related records from Texts table.
But I need to have only 1 record from it, and to do that I need to have one more condition in the last join type->texts, which is not defined in the model. It's dynamic session variable.
Is there any way to do this?
If you want modify the last relation query to have additional condition and return one record instead of many, simply change last relation call like so:
$text = $product->part->type->getTexts()->andWhere(...)->one();
Direct relation method call returns yii\db\ActiveQuery instance so you can modify conditions how you want.
If you want to use modified relation in more than just one place, create separate method for that:
/**
* #return yii\db\ActiveQuery
*/
public function getDynamicText()
{
// Insert link from texts relation similar as in hasMany() and additional condition in andWhere()
return $this->hasOne(...)->andWhere(...);
}
And then use it:
$text = $product->part->type->dynamicText;
In this case, scopes would be a handy solution, especially if you're going to use complicated conditions.
1. Start by creating a model that extends ActiveQuery with a method that will be used to add conditions to your query, for example active = 1:
namespace app\models;
use yii\db\ActiveQuery;
class TextQuery extends ActiveQuery
{
public function active($state = 1)
{
$this->andWhere(['active' => $state]); // or any other condition
return $this;
}
}
2. Override the find() method in your Text model:
public static function find()
{
return new \app\models\TextQuery(get_called_class());
}
3. Add a method in your Type model that retrieves your relational data via the newly made scope:
public function getActiveText()
{
return $this->hasMany(Text::className(), ['type_id' => 'id'])->active();
}
Finally, use it as follows:
$text = $product->part->type->activeText;
The docs are pretty clear on this, check 'em out.

CakePHP: create temporary table and use it

I'm trying to create a temporary table within CakePHP 2.x, but I always receive the message "Error: Call to a member function query() on a non-object".
I did some research and found a solution, but this one is not working for me: Create temporary table in CakePHP and load it as a Model
EDIT:
The following code now produces a different error: "Error: Table devices_cls for model DevicesCl was not found in datasource default."
Here is my code:
class DevicesController extends AppController {
public $uses = array('Device','Client');
public function index(){
$conditions = array();
$tmpModel = 'DevicesCl';
$tmpTable = 'devices_cls';
$this->loadModel('DevicesCl');
$this->DevicesCl->query("CREATE TEMPORARY TABLE IF NOT EXISTS devices_cls AS (SELECT uuid,ownerUuid,status,os,imei,updatedOn,msisdn,model FROM device)" );
As I'm quite new to CakePHP, do I need to add an additional model class? I don't think so, as this should be handled by the temporary table - right?
Thanks for your support!
class DevicesController extends AppController {
public $uses = array('Device','Client');
public function index(){
$conditions = array();
$tmpModel = 'DevicesCl';
$tmpTable = 'devices_cls';
$this->loadModel('DevicesCl');
$this->DevicesCl->useTable=false;
$this->DevicesCl->query("CREATE TEMPORARY TABLE IF NOT EXISTS devices_cls AS (SELECT uuid,ownerUuid,status,os,imei,updatedOn,msisdn,model FROM device)" );
$this->DevicesCl->useTable = $tmpTable;
The problem is that the Model "DevicesCl" does not exist yet.
in your example on the link : the Model exist
so if you change
$this->DevicesCl->query(...)
to
$this->Model->query(...)
But i don t see why you need a functionnality like that... i think there are surely best solutions no ?
In Cakephp 3, you can do the following:
Model/Table/MyTempTable.php
<?php
// ......
class MyTempTable extends Table{
public function initDownload(){
$connection = ConnectionManager::get('default');
$connection->execute('CREATE TEMPORARY TABLE temptemp as (select * from refernece_db)');
// Do your thing
}
}
// When use the temp table, first should do:
$this->MyTempTable->initDownload();
// Then, can use Cakephp3 Table syntax normally as if it is a normal table

Filter data based on User field cakePHP

So I have a company based system and I am keeping all data within the one database and I separate the data by using a site_id field which is present in all tables and in the users table.
Now at the moment I am doing a condition on every single find('all'). Is there a more global way to do this. Maybe in the AppController? Even for saving data I am having to set the site_id every single save.
Something like
public function beforeFilter() {
parent::beforeFilter();
$this->set('site_id', $this->Auth->user('site_id'));
}
Any direction would only help. Thanks
TLDR:
Create a Behavior with a beforeFind() method that appends a site_id condition to all queries.
Example/Details:
Create a behavior something along the lines of the below. Note, I'm getting the siteId from a Configure variable, but feel free to get that however you want.
<?php
class SiteSpecificBehavior extends ModelBehavior {
public function setup(Model $Model, $settings = array()) {
$Model->siteId = Configure::read('Site.current.id');
}
public function beforeFind(Model $Model, $query = array()) {
$siteId = $Model->siteId;
$query['conditions'][$Model->alias . '.site_id'] = $siteId ;
return $query;
}
}
Then on any/all models where you want to make sure it's site-specific, add:
public $actsAs = array('SiteSpecific');
This can be tweaked and improved up certainly, but it should give you a good idea.

Problem with altering model attributes in controller

Today I've got a problem when I tried using following code to alter the model attribute in the controller
function userlist($trigger = 1)
{
if($trigger == 1)
{
$this->User->useTable = 'betausers'; //'betausers' is completely the same structure as table 'users'
}
$users = $this->User->find('all');
debug($users);
}
And the model file is
class User extends AppModel
{
var $name = "User";
//var $useTable = 'betausers';
function beforeFind() //only for debug
{
debug($this->useTable);
}
}
The debug message in the model showed the userTable attribute had been changed to betausers.And It was supposed to show all records in table betausers.However,I still got the data in the users,which quite confused me.And I hope someone can show me some directions to solve this problem.
Regards
Model::useTable is only consulted during model instantiation (see the API documentation for Model::__construct). If you want to change the model's table on the fly, you should use Model::setSource:
if ( $trigger == 1 ) {
$this->User->setSource('betausers');
}
The table to use is "fixed" when the model is loaded/instantiated. At that time a DB connection object is created, the table schema is being checked and a lot of other things happen. You can change that variable later all you want, Cake is not looking at it anymore after that point.
One model should be associated with one table, and that association shouldn't change during runtime. You'll need to make another model BetaUser and dynamically change the model you're using. Or rethink your database schema, a simple flag to distinguish beta users from regular users within the users table may be better than a whole new table.

Resources