Contain only works when specifying either one of required models - php

If I add two models in contain, at the same time, only one property will be populated. If I add either one separately, the corresponding property will be populated fine. I suspect a clash with the users table somehow.
Basics:
task lead: tasks hasOne organizational_units hasOne users
tasks [id, organizational_unit_id, ...] > organizational_units [id, user_id, ...] > users [id, ...]
task collaborators: tasks hasAndBelongsToMany users (tasks_users)
tasks [id, ...] > tasks_users [task_id, user_id] > users [id, ...]
Code:
//populates $task->organizational_unit property
$this->Tasks->find()->contain(['OrganizationalUnits.Users'])->where(['Tasks.id' => $id]);
//populates $task->users property
$this->Tasks->find()->contain(['Users'])->where(['Tasks.id' => $id]);
//only populates organizational_unit property, missing users property
$this->Tasks->find()->contain(['OrganizationalUnits.Users','Users'])->where(['Tasks.id' => $id]);
Task Table:
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Tasks Model
*
* #property \App\Model\Table\TasksTable&\Cake\ORM\Association\BelongsTo $ParentTasks
* #property \App\Model\Table\OrganizationalUnitsTable&\Cake\ORM\Association\BelongsTo $OrganizationalUnits
* #property \App\Model\Table\FiscalYearsTable&\Cake\ORM\Association\BelongsTo $FiscalYears
* #property \App\Model\Table\TaskStatusesTable&\Cake\ORM\Association\BelongsTo $TaskStatuses
* #property \App\Model\Table\QuartersTable&\Cake\ORM\Association\BelongsTo $Quarters
* #property \App\Model\Table\TaskTypesTable&\Cake\ORM\Association\BelongsTo $TaskTypes
* #property \App\Model\Table\TaskPrioritiesTable&\Cake\ORM\Association\BelongsTo $TaskPriorities
* #property \App\Model\Table\TaskDiscussionNotesTable&\Cake\ORM\Association\HasMany $TaskDiscussionNotes
* #property \App\Model\Table\TaskOutcomesTable&\Cake\ORM\Association\HasMany $TaskOutcomes
* #property \App\Model\Table\TasksTable&\Cake\ORM\Association\HasMany $ChildTasks
* #property \App\Model\Table\UsersTable&\Cake\ORM\Association\BelongsToMany $Users
*
* #method \App\Model\Entity\Task newEmptyEntity()
* #method \App\Model\Entity\Task newEntity(array $data, array $options = [])
* #method \App\Model\Entity\Task[] newEntities(array $data, array $options = [])
* #method \App\Model\Entity\Task get($primaryKey, $options = [])
* #method \App\Model\Entity\Task findOrCreate($search, ?callable $callback = null, $options = [])
* #method \App\Model\Entity\Task patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* #method \App\Model\Entity\Task[] patchEntities(iterable $entities, array $data, array $options = [])
* #method \App\Model\Entity\Task|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\Task saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\Task[]|\Cake\Datasource\ResultSetInterface|false saveMany(iterable $entities, $options = [])
* #method \App\Model\Entity\Task[]|\Cake\Datasource\ResultSetInterface saveManyOrFail(iterable $entities, $options = [])
* #method \App\Model\Entity\Task[]|\Cake\Datasource\ResultSetInterface|false deleteMany(iterable $entities, $options = [])
* #method \App\Model\Entity\Task[]|\Cake\Datasource\ResultSetInterface deleteManyOrFail(iterable $entities, $options = [])
*
* #mixin \Cake\ORM\Behavior\TimestampBehavior
* #mixin \Cake\ORM\Behavior\TreeBehavior
*/
class TasksTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('tasks');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->addBehavior('Tree', [
'level' => 'level', // Defaults to null, i.e. no level saving
]);
$this->belongsTo('ParentTasks', [
'className' => 'Tasks',
'foreignKey' => 'parent_id',
]);
$this->belongsTo('OrganizationalUnits', [
'foreignKey' => 'organizational_unit_id',
'joinType' => 'INNER',
]);
$this->belongsTo('FiscalYears', [
'foreignKey' => 'fiscal_year_id',
'joinType' => 'INNER',
]);
$this->belongsTo('TaskStatuses', [
'foreignKey' => 'task_status_id',
]);
$this->belongsTo('Quarters', [
'foreignKey' => 'quarter_id',
]);
$this->belongsTo('TaskTypes', [
'foreignKey' => 'task_type_id',
'joinType' => 'INNER',
]);
$this->belongsTo('TaskPriorities', [
'foreignKey' => 'task_priority_id',
'joinType' => 'INNER',
]);
$this->hasMany('TaskDiscussionNotes', [
'foreignKey' => 'task_id',
]);
$this->hasMany('TaskOutcomes', [
'foreignKey' => 'task_id',
]);
$this->hasMany('ChildTasks', [
'className' => 'Tasks',
'foreignKey' => 'parent_id',
]);
$this->belongsToMany('Users', [
'foreignKey' => 'task_id',
'targetForeignKey' => 'user_id',
'joinTable' => 'tasks_users',
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): \Cake\Validation\Validator
{
//removed
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): \Cake\ORM\RulesChecker
{
//removed
return $rules;
}
}

Related

How to setup a HasAndBelongsToMany association with the same table?

What I want
i want to setup a table tasks table with a manyToMany relation between the tasks. One task should have many successors and one can have many predecessors.
My tasks table also has a foreign key to itself, which identifies superior tasks in a oneToMany relation.
The tables look like the following:
tasks(id, superior_task_id, [other attributes])
tasks_predecessors(task_id, pre_id)
Where tasks.id is the primary key, tasks.superior_task_id is a foreign key contraint to tasks and tasks_predecessors.task_id with tasks_predecessors.pre_id is the primary key of tasks_predecesssors, both attributes of tasks_predecessors are foreign keys of tasks.
My status quo
I have tried to use the standard way of creating models from tables: bake in the command line. And used this as my starting point. With this, nothing happend. It was like the tasks_predecessors table didn't exist. The files TasksPredecessors.php and TasksPredecessorsTable.php were created and were fitted with the "belongsTo" associations to the tasks table. In the tasks table, there were one Predecessors association to correct table. With the standard templates and controllers, the data were not accessible.
Now I did some changes to try to set it up as a normal manyToMany relation with both "sides" in one table.
My Code in TasksTable.php:
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Tasks Model
*
* #property \App\Model\Table\ProjectsTable&\Cake\ORM\Association\BelongsTo $Projects
* #property \App\Model\Table\CompaniesTable&\Cake\ORM\Association\BelongsTo $Companies
* #property \App\Model\Table\ToolsTable&\Cake\ORM\Association\BelongsToMany $Tools
*
* #method \App\Model\Entity\Task newEmptyEntity()
* #method \App\Model\Entity\Task newEntity(array $data, array $options = [])
* #method \App\Model\Entity\Task[] newEntities(array $data, array $options = [])
* #method \App\Model\Entity\Task get($primaryKey, $options = [])
* #method \App\Model\Entity\Task findOrCreate($search, ?callable $callback = null, $options = [])
* #method \App\Model\Entity\Task patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* #method \App\Model\Entity\Task[] patchEntities(iterable $entities, array $data, array $options = [])
* #method \App\Model\Entity\Task|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\Task saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\Task[]|\Cake\Datasource\ResultSetInterface|false saveMany(iterable $entities, $options = [])
* #method \App\Model\Entity\Task[]|\Cake\Datasource\ResultSetInterface saveManyOrFail(iterable $entities, $options = [])
* #method \App\Model\Entity\Task[]|\Cake\Datasource\ResultSetInterface|false deleteMany(iterable $entities, $options = [])
* #method \App\Model\Entity\Task[]|\Cake\Datasource\ResultSetInterface deleteManyOrFail(iterable $entities, $options = [])
*
* #mixin \Cake\ORM\Behavior\TimestampBehavior
*/
class TasksTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('tasks');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('UsersCreator', [
'foreignKey' => 'creator_id',
'className' => 'Users'
]);
$this->belongsTo('TasksSup', [
'foreignKey' => 'superior_task_id',
'className' => 'Tasks'
]);
$this->belongsTo('Projects', [
'foreignKey' => 'project_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Companies', [
'foreignKey' => 'company_id',
'joinType' => 'INNER',
]);
$this->hasMany('Predecessors', [
'foreignKey' => 'task_id',
'className' => 'Tasks',
'targetForeignKey' => 'pre_id',
'joinTable' => 'tasks_predecessors'
]);
$this->belongsToMany('Successors', [
'foreignKey' => 'pre_id',
'className' => 'Tasks',
'targetForeignKey' => 'task_id',
'joinTable' => 'tasks_predecessors'
]);
$this->belongsToMany('Tools', [
'foreignKey' => 'task_id',
'targetForeignKey' => 'tool_id',
'joinTable' => 'tasks_tools',
]);
$this->belongsToMany('UsersAssigned', [
'foreignKey' => 'task_id',
'targetForeignKey' => 'user_id',
'joinTable' => 'tasks_users',
'className' => 'Users'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
$validator
->nonNegativeInteger('id')
->allowEmptyString('id', null, 'create');
$validator
->scalar('name')
->maxLength('name', 128)
->requirePresence('name', 'create')
->notEmptyString('name');
$validator
->dateTime('start_at')
->allowEmptyDateTime('start_at');
$validator
->dateTime('end_at')
->allowEmptyDateTime('end_at');
$validator
->allowEmptyString('restricted');
$validator
->integer('status')
->allowEmptyString('status');
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): RulesChecker
{
$rules->add($rules->existsIn('creator_id', 'UsersCreator'), ['errorField' => 'creator_id']);
$rules->add($rules->existsIn('superior_task_id', 'TasksSup'), ['errorField' => 'superior_task_id']);
$rules->add($rules->existsIn('project_id', 'Projects'), ['errorField' => 'project_id']);
$rules->add($rules->existsIn('company_id', 'Companies'), ['errorField' => 'company_id']);
return $rules;
}
}
My code in TasksPredecessorsTable.php:
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* TasksPredecessors Model
*
* #property \App\Model\Table\TasksTable&\Cake\ORM\Association\BelongsTo $Tasks
*
* #method \App\Model\Entity\TasksPredecessor newEmptyEntity()
* #method \App\Model\Entity\TasksPredecessor newEntity(array $data, array $options = [])
* #method \App\Model\Entity\TasksPredecessor[] newEntities(array $data, array $options = [])
* #method \App\Model\Entity\TasksPredecessor get($primaryKey, $options = [])
* #method \App\Model\Entity\TasksPredecessor findOrCreate($search, ?callable $callback = null, $options = [])
* #method \App\Model\Entity\TasksPredecessor patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* #method \App\Model\Entity\TasksPredecessor[] patchEntities(iterable $entities, array $data, array $options = [])
* #method \App\Model\Entity\TasksPredecessor|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\TasksPredecessor saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* #method \App\Model\Entity\TasksPredecessor[]|\Cake\Datasource\ResultSetInterface|false saveMany(iterable $entities, $options = [])
* #method \App\Model\Entity\TasksPredecessor[]|\Cake\Datasource\ResultSetInterface saveManyOrFail(iterable $entities, $options = [])
* #method \App\Model\Entity\TasksPredecessor[]|\Cake\Datasource\ResultSetInterface|false deleteMany(iterable $entities, $options = [])
* #method \App\Model\Entity\TasksPredecessor[]|\Cake\Datasource\ResultSetInterface deleteManyOrFail(iterable $entities, $options = [])
*/
class TasksPredecessorsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('tasks_predecessors');
$this->setDisplayField(['task_id', 'pre_id']);
$this->setPrimaryKey(['task_id', 'pre_id']);
$this->belongsToMany('TasksSuc', [
'foreignKey' => 'task_id',
'joinType' => 'INNER',
'className' => 'Tasks'
]);
$this->belongsToMany('TasksPre', [
'foreignKey' => 'pre_id',
'joinType' => 'INNER',
'className' => 'Tasks'
]);
}
/**
* 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): RulesChecker
{
$rules->add($rules->existsIn('task_id', 'TasksSuc'), ['errorField' => 'task_id']);
$rules->add($rules->existsIn('pre_id', 'TasksPre'), ['errorField' => 'pre_id']);
return $rules;
}
}
The $accessible array of tasks looks like the following:
protected $_accessible = [
'name' => true,
'start_at' => true,
'end_at' => true,
'creator_id' => true,
'created' => true,
'modified' => true,
'superior_task_id' => true,
'restricted' => true,
'project_id' => true,
'company_id' => true,
'status' => true,
'users_creator' => true,
'tasks_sup' => true,
'project' => true,
'company' => true,
'predecessors' => true,
'successors' => true,
'tools' => true,
'users_assigned' => true,
];
And in TasksPredecessors.php like this:
protected $_accessible = [
'tasks_suc' => true,
'tasks_pre' => true,
];
I access the data in the tasks_controller like this:
public function view($id = null)
{
$task = $this->Tasks->get($id, [
'contain' => ['UsersCreator', 'TasksSup', 'Projects', 'Companies', 'Successors', 'Tools', 'UsersAssigned', 'Predecessors'],
]);
$this->set(compact('task'));
}
public function add()
{
$task = $this->Tasks->newEmptyEntity();
if ($this->request->is('post')) {
$task = $this->Tasks->patchEntity($task, $this->request->getData(), [
'associated' => ['Tools._joinData'], //adding quantity of tools
]);
if ($this->Tasks->save($task)) {
$this->Flash->success(__('The task has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The task could not be saved. Please, try again.'));
}
$usersCreator = $this->Tasks->UsersCreator->find('list', ['limit' => 200])->all();
$tasksSup = $this->Tasks->TasksSup->find('list', ['limit' => 200])->all();
$projects = $this->Tasks->Projects->find('list', ['limit' => 200])->all();
$companies = $this->Tasks->Companies->find('list', ['limit' => 200])->all();
$successors = $this->Tasks->Successors->find('list', ['limit' => 200])->all();
$tools = $this->Tasks->Tools->find('list', ['limit' => 200])->all();
$usersAssigned = $this->Tasks->UsersAssigned->find('list', ['limit' => 200])->all();
$this->set(compact('task', 'usersCreator', 'tasksSup', 'projects', 'companies', 'successors', 'tools', 'usersAssigned'));
}
public function edit($id = null)
{
$task = $this->Tasks->get($id, [
'contain' => ['Successors', 'Tools', 'UsersAssigned'],
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$task = $this->Tasks->patchEntity($task, $this->request->getData());
if ($this->Tasks->save($task)) {
$this->Flash->success(__('The task has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The task could not be saved. Please, try again.'));
}
$usersCreator = $this->Tasks->UsersCreator->find('list', ['limit' => 200])->all();
$tasksSup = $this->Tasks->TasksSup->find('list', ['limit' => 200])->all();
$projects = $this->Tasks->Projects->find('list', ['limit' => 200])->all();
$companies = $this->Tasks->Companies->find('list', ['limit' => 200])->all();
$successors = $this->Tasks->Successors->find('list', ['limit' => 200])->all();
$tools = $this->Tasks->Tools->find('list', ['limit' => 200])->all();
$usersAssigned = $this->Tasks->UsersAssigned->find('list', ['limit' => 200])->all();
$this->set(compact('task', 'usersCreator', 'tasksSup', 'projects', 'companies', 'successors', 'tools', 'usersAssigned'));
}
These are the functions created by the bake command.
My Errors
If I add a task via /tasks/add, everything seems to work properly, until I look it up in the tasks_predecessors table. It still doesn't contain any entry.
If I now try to view one task via /tasks/view/1, the error SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Predecessors.task_id' in 'where clause' comes up, with the corresponding SQL query:
SELECT Predecessors.id AS Predecessors__id, Predecessors.name AS Predecessors__name, Predecessors.start_at AS Predecessors__start_at, Predecessors.end_at AS Predecessors__end_at, Predecessors.creator_id AS Predecessors__creator_id, Predecessors.created AS Predecessors__created, Predecessors.modified AS Predecessors__modified, Predecessors.superior_task_id AS Predecessors__superior_task_id, Predecessors.restricted AS Predecessors__restricted, Predecessors.project_id AS Predecessors__project_id, Predecessors.company_id AS Predecessors__company_id, Predecessors.status AS Predecessors__status FROM tasks Predecessors WHERE Predecessors.task_id in (:c0)
If I try to edit the task, an other error occurs:
Warning (2): array_combine(): Both parameters should have an equal number of elements [CORE\src\ORM\Rule\ExistsIn.php, line 143]
Warning (512): Unable to emit headers. Headers sent in file=C:\xampp\htdocs\Handwerker_Projektmanagement_IP2021\vendor\cakephp\cakephp\src\Error\Debugger.php line=988 [CORE\src\Http\ResponseEmitter.php, line 71]
Warning (2): Cannot modify header information - headers already sent by (output started at C:\xampp\htdocs\Handwerker_Projektmanagement_IP2021\vendor\cakephp\cakephp\src\Error\Debugger.php:988) [CORE\src\Http\ResponseEmitter.php, line 168]
Warning (2): Cannot modify header information - headers already sent by (output started at C:\xampp\htdocs\Handwerker_Projektmanagement_IP2021\vendor\cakephp\cakephp\src\Error\Debugger.php:988) [CORE\src\Http\ResponseEmitter.php, line 197]
Warning (2): Cannot modify header information - headers already sent by (output started at C:\xampp\htdocs\Handwerker_Projektmanagement_IP2021\vendor\cakephp\cakephp\src\Error\Debugger.php:988) [CORE\src\Http\ResponseEmitter.php, line 197]
with
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'LIMIT 1' at line 1
and the SQL query: SELECT 1 AS existing FROM tasks TasksSuc WHERE LIMIT 1
Conclusion
I can't make an sense of these errors. If I try different things in the controller or ...Table.php files, I will be surprised with different kind of errors, which all don't make sense to me. I hope someone can help me quick, because I could't find anything about this type of relation, which in my opinion is not that uncommon.

Related Models through Junction Table yii2

i have 3 tables for equipments, a table for users, and a table user_equipment_mapping.
Equipments:
1.Laptop: PK - Id, laptop_model, laptop_series, laptop_date_acq
2.Display: PK - ID, display_model, display_series, display_date_acq
3.Phone : PK - ID, phone_model, phone_series, phone_date_acq
User: ID - PK , username, password
User_Equipment_mapping : ID - PK, user_id - FK(id from tbl user), laptop_id - FK(id from tbl laptop), display_id - FK (id from tbl display), phone_id - FK (id from tbl phone), start_date, end_date.
For me this is a many-many relationship.
I want to get in userequipment/create an array of users through junction table so i can assign the equipments to an user using dropdown in gridview. I need your help getting users in create, i have created a method in clas UserEquipmentMapping and tried to print_r($users) in view but it gets me the follwing error : Using $this when not in object context.
Thank you for any suggestions in advance!
Controller :
<?php
namespace app\controllers;
use Yii;
use app\models\UserEquipmentMapping;
use app\models\UserequipmentmappingSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
/**
* UserequipmentmappingController implements the CRUD actions for UserEquipmentMapping model.
*/
class UserequipmentmappingController extends Controller
{
/**
* {#inheritdoc}
*/
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
],
];
}
/**
* Lists all UserEquipmentMapping models.
* #return mixed
*/
public function actionIndex()
{
$searchModel = new UserequipmentmappingSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
/**
* Displays a single UserEquipmentMapping model.
* #param integer $id
* #return mixed
* #throws NotFoundHttpException if the model cannot be found
*/
public function actionView($id)
{
return $this->render('view', [
'model' => $this->findModel($id),
]);
}
/**
* Creates a new UserEquipmentMapping model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionCreate()
{
$model = new UserEquipmentMapping();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('create', [
'model' => $model,
]);
}
/**
* Updates an existing UserEquipmentMapping model.
* If update is successful, the browser will be redirected to the 'view' page.
* #param integer $id
* #return mixed
* #throws NotFoundHttpException if the model cannot be found
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('update', [
'model' => $model,
]);
}
/**
* Deletes an existing UserEquipmentMapping model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* #param integer $id
* #return mixed
* #throws NotFoundHttpException if the model cannot be found
*/
public function actionDelete($id)
{
$this->findModel($id)->delete();
return $this->redirect(['index']);
}
/**
* Finds the UserEquipmentMapping model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
* #param integer $id
* #return UserEquipmentMapping the loaded model
* #throws NotFoundHttpException if the model cannot be found
*/
protected function findModel($id)
{
if (($model = UserEquipmentMapping::findOne($id)) !== null) {
return $model;
}
throw new NotFoundHttpException('The requested page does not exist.');
}
}
Model :
<?php
namespace app\models;
use Yii;
/**
* This is the model class for table "user_equipment_mapping".
*
* #property int $id
* #property int $user_id
* #property int|null $laptop_id
* #property int|null $phone_id
* #property int|null $display_id
* #property string|null $start_date
* #property string|null $stop_date
*
* #property Display $display
* #property Laptop $laptop
* #property Phone $phone
* #property User $user
*/
class UserEquipmentMapping extends \yii\db\ActiveRecord
{
/**
* {#inheritdoc}
*/
public static function tableName()
{
return 'user_equipment_mapping';
}
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['user_id'], 'required'],
[['user_id', 'laptop_id', 'phone_id', 'display_id'], 'integer'],
[['start_date', 'stop_date'], 'safe'],
[['display_id'], 'exist', 'skipOnError' => true, 'targetClass' => Display::className(), 'targetAttribute' => ['display_id' => 'id']],
[['laptop_id'], 'exist', 'skipOnError' => true, 'targetClass' => Laptop::className(), 'targetAttribute' => ['laptop_id' => 'id']],
[['phone_id'], 'exist', 'skipOnError' => true, 'targetClass' => Phone::className(), 'targetAttribute' => ['phone_id' => 'id']],
[['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['user_id' => 'id']],
];
}
/**
* {#inheritdoc}
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'user_id' => 'User ID',
'laptop_id' => 'Laptop ID',
'phone_id' => 'Phone ID',
'display_id' => 'Display ID',
'start_date' => 'Start Date',
'stop_date' => 'Stop Date',
];
}
/**
* Gets query for [[Display]].
*
* #return \yii\db\ActiveQuery|DisplayQuery
*/
public function getDisplay()
{
return $this->hasOne(Display::className(), ['id' => 'display_id']);
}
/**
* Gets query for [[Laptop]].
*
* #return \yii\db\ActiveQuery|LaptopQuery
*/
public function getLaptop()
{
return $this->hasOne(Laptop::className(), ['id' => 'laptop_id']);
}
/**
* Gets query for [[Phone]].
*
* #return \yii\db\ActiveQuery|PhoneQuery
*/
public function getPhone()
{
return $this->hasOne(Phone::className(), ['id' => 'phone_id']);
}
/**
* Gets query for [[User]].
*
* #return \yii\db\ActiveQuery|UserQuery
*/
public function getUser()
{
return $this->hasOne(User::className(), ['id' => 'user_id']);
}
public function getUsername()
{
return $this->hasMany(User::className(),['user_id' => 'id'])
->viaTable('user');
}
/**
* {#inheritdoc}
* #return UserEquipmentMappingQuery the active query used by this AR class.
*/
public static function find()
{
return new UserEquipmentMappingQuery(get_called_class());
}
}
Create :
<?php
use yii\helpers\Html;
/* #var $this yii\web\View */
/* #var $model app\models\UserEquipmentMapping */
$this->title = 'Create User Equipment Mapping';
$this->params['breadcrumbs'][] = ['label' => 'User Equipment Mappings', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<?php
use app\models\UserEquipmentMapping;
$users =UserEquipmentMapping::getUsername();
print_r($users);die;
?>
<div class="user-equipment-mapping-create">
<h1><?= Html::encode($this->title) ?></h1>
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>

How to pass information via button in table builder to a form builder?

To simplify, I have companies and workers attached to them in my pyrocms app. In the companies (created by the table builder in the admin panel automatically), there are buttons in the raw of each company. edit button is one of them provided out of the box. So I want to add another button, say 'Add Worker', and open the page of worker creation which I have done successfully. There is a multiple relation between companies and workers since a worker can work in multiple companies. You can think it as a categories of any post. The thing I want is, in the worker creation form page, I want to have the company whose 'Add worker' button is clicked appear in the 'working companies' field of worker automatically. What is the proper way of implementing such idea? I can pass HTML attributes to button using attributes but I don't know if it helps.
This is the $buttons array in the CompanyTableBuilder.
/**
* The table buttons.
* #var array|string
*/
protected $buttons = [
'edit',
'add_worker' => [
'attributes' => [
'href' => '/admin/crm/workers/create',
],
'text' => 'Add worker',
'type' => 'success',
]
];
Let's imagine you have two groups of clients: workers and payers
class ClientTableBuilder extends \Anomaly\UsersModule\User\Table\UserTableBuilder
{
/**
* The table model
*
* #var string
*/
protected $model = UserModel::class;
/**
* The table views.
*
* #var array
*/
protected $views = [
'workers' => [
'query' => WorkersQuery::class,
'buttons' => [
'jobs' => [
'type' => 'info',
],
],
],
'payers' => [
'query' => PayersQuery::class,
'buttons' => [
'payments' => [
'type' => 'info',
],
],
],
];
}
When you would press the jobs button, you will go to admin/{addon}/clients/jobs/{user_id} route. So you would need to have the next one controller:
class ClientsController extends AdminController
{
/**
* Shows the clients list.
*
* #param ClientTableBuilder $table The table
* #return Response
*/
public function clients(ClientTableBuilder $table)
{
return $table->render();
}
/**
* Shows the client's jobs list.
*
* #param UserRepositoryInterface $users The users
* #param ClientJobsTableBuilder $table The table
* #param $id The identifier
* #return Response
*/
public function assignedJobs(
UserRepositoryInterface $users,
ClientJobsTableBuilder $table,
$id
) {
/* #var UserInterface $user */
if (!$user = $users->find($id)) {
return $this->response->json([
'success' => false,
'message' => "Can't find user with id {$id}!",
]);
}
if ($this->request->ajax()) {
$table->setAjax(true);
}
return $table->setUser($user)->render();
}
/**
* Shows modal with unassigned jobs list
*
* #param UserRepositoryInterface $users The users
* #param ClientJobsLookupTableBuilder $table The table
* #param $id The identifier
* #return Response
*/
public function unassignedJobs(
UserRepositoryInterface $users,
ClientJobsLookupTableBuilder $table,
$id
) {
/* #var UserInterface $user */
if (!$user = $users->find($id)) {
return $this->response->json([
'success' => false,
'message' => "Can't find user with id {$id}!",
]);
}
return $table->setUser($user)->render();
}
/**
* Attach a job to a client
*
* #param int|str $user The user's id
* #param int|str $job The job's id
* #return JsonResponse
*/
public function attach($user, $job)
{
if ($error = $this->dispatch(new AttachJobToUser($user, $job))) {
return $this->response->json([
'success' => false,
'message' => $error,
]);
}
return $this->response->json([
'success' => true,
'user' => (int) $user,
'job' => (int) $job,
]);
}
/**
* Detach a job from a client
*
* #param int|str $user The user's id
* #param int|str $job The job's id
* #return JsonResponse
*/
public function detach($user, $job)
{
if ($error = $this->dispatch(new DetachJobFromUser($user, $job))) {
return $this->response->json([
'success' => false,
'message' => $error,
]);
}
return $this->response->json([
'success' => true,
'user' => (int) $user,
'job' => (int) $job,
]);
}
}
Then a value TB looks like:
class ClientJobsTableBuilder extends ValueTableBuilder
{
/**
* Table's user
*
* #var UserInterface|null
*/
protected $user = null;
/**
* Table's columns
*
* #var array
*/
protected $columns = [
'name' => [
'heading' => 'Name',
'value' => '<strong>{entry.name}</strong>',
],
'type' => [
'heading' => 'Type',
],
'categories' => [
'heading' => 'Categories',
'value' => 'entry.type.categories.pluck("name")|join("<br>")',
],
];
/**
* Table's buttons
*
* #var string
*/
protected $buttons = ClientJobsTableButtons::class;
/**
* Table's actions
*
* #var array
*/
protected $actions = [];
/**
* Table's options
*
* #var array
*/
protected $options = [
'sortable' => true,
];
/**
* Table's assets
*
* #var array
*/
protected $assets = [
'scripts.js' => [
'{YOUR_MODULE_FULL_NS}::js/detach.js',
],
];
/**
* Gets the user.
*
* #return UserInterface|null The user.
*/
public function getUser()
{
return $this->user;
}
/**
* Sets the user.
*
* #param UserInterface $user The user
* #return self
*/
public function setUser(UserInterface $user)
{
$this->user = $user;
return $this;
}
}
then th valuetablebuttons:
class ClientJobsTableButtons
{
/**
* Handle the table buttons
*
* #param ClientJobsTableBuilder $builder The builder
*/
public function handle(ClientJobsTableBuilder $builder)
{
/* #var UserInterface $user */
if (!$user = $builder->getUser()) {
return;
}
$builder->setButtons([
'detach' => [
'type' => 'danger',
'data-detach' => '{entry.id}',
'data-user' => $user->getId(),
'data-dismiss' => 'multiple',
],
]);
}
}
And the same shit for the lookup TB:
class ClientJobsLookupTableBuilder extends TableBuilder
{
/**
* AJAX mode flag
*
* #var bool
*/
protected $ajax = true;
/**
* Table's user
*
* #var UserInterface|null
*/
protected $user = null;
/**
* Table's columns
*
* #var array
*/
protected $columns = [
'name' => [
'heading' => 'Name',
'value' => '<strong>{entry.name}</strong>',
],
'type' => [
'heading' => 'Type',
],
'categories' => [
'heading' => 'Categories',
'value' => 'entry.type.categories.pluck("name")|join("<br>")',
],
];
/**
* Table's buttons
*
* #var string
*/
protected $buttons = ClientJobsLookupTableButtons::class;
/**
* Table's actions
*
* #var array
*/
protected $actions = [];
/**
* Table's options
*
* #var array
*/
protected $options = [
'sortable' => false,
];
/**
* Table's assets
*
* #var array
*/
protected $assets = [
'scripts.js' => [
'{YOUR_MODULE_FULL_NS}::js/attach.js',
],
];
/**
* Gets the user.
*
* #return UserInterface|null The user.
*/
public function getUser()
{
return $this->user;
}
/**
* Sets the user.
*
* #param UserInterface $user The user
* #return self
*/
public function setUser(UserInterface $user)
{
$this->user = $user;
return $this;
}
}
And lookup TB buttons:
class ClientJobsLookupTableButtons
{
/**
* Handle the table buttons
*
* #param ClientJobsLookupTableBuilder $builder The builder
*/
public function handle(ClientJobsLookupTableBuilder $builder)
{
/* #var UserInterface $user */
if (!$user = $builder->getUser()) {
return;
}
$builder->setButtons([
'attach' => [
'data-attach' => '{entry.id}',
'data-user' => $user->getId(),
'data-dismiss' => 'multiple',
],
]);
}
}
After that you would need only to write some JS for right behavior.
UPD: This is the example of *Query class:
class WorkersQuery
{
/**
* Handle the query.
*
* #param Builder $query The query builder
* #param RoleRepositoryInterface $roles The roles repository
*/
public function handle(Builder $query, RoleRepositoryInterface $roles)
{
/* #var RoleInterface $role */
$role = $roles->findBySlug('worker');
$query
->leftJoin(
'users_users_roles',
'users_users_roles.entry_id',
'=',
'users_users.id'
)
->where('users_users_roles.related_id', $role->getId());
}
}
And the example of AttachJobToUser command:
class AttachJobToUser
{
/**
* User's identifier
*
* #var mixed
*/
protected $user;
/**
* Job's identifier
*
* #var mixed
*/
protected $job;
/**
* Create a new instance of AttachJobToUser class
*
* #param $user
* #param $job
*/
public function __construct($user, $job)
{
$this->user = $user;
$this->job = $job;
}
/**
* Handle the command
*
* #param UserRepositoryInterface $users The users
* #param JobRepositoryInterface $jobs The jobs
* #return boolean|string
*/
public function handle(
UserRepositoryInterface $users,
JobRepositoryInterface $jobs
) {
/* #var UserInterface $user */
if (!$user = $users->find($this->user)) {
return "Can't find user with id '{$this->user}'";
}
/* #var JobInterface $job */
if (!$job = $jobs->find($this->job)) {
return "Can't find job with id '{$this->job}'";
}
if (!$job->addWorker($user)) {
return "Can't attach a job with id '{$this->job}' to a worker with id '{$this->user}'";
}
return false;
}
}

How to get the value of custom field checkout address in Carrier Model Magento 2

I followed the tutorial here to make a custom field in address form checkout:
<?php
$customAttributeCode = 'custom_field';
$customField = [
'component' => 'Magento_Ui/js/form/element/abstract',
'config' => [
// customScope is used to group elements within a single form (e.g. they can be validated separately)
'customScope' => 'shippingAddress.custom_attributes',
'customEntry' => null,
'template' => 'ui/form/field',
'elementTmpl' => 'ui/form/element/input',
'tooltip' => [
'description' => 'this is what the field is for',
],
],
'dataScope' => 'shippingAddress.custom_attributes' . '.' . $customAttributeCode,
'label' => 'Custom Attribute',
'provider' => 'checkoutProvider',
'sortOrder' => 0,
'validation' => [
'required-entry' => true
],
'options' => [],
'filterBy' => null,
'customEntry' => null,
'visible' => true,
];
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children'][$customAttributeCode] = $customField;
the above code is showing in in checkout, but when the validation triggered to calculate the shipping method price in Model/Carrier.php that i made following tutorial in here, i'm confused how to get the value from the custom field, here's the Model/Carrier.php :
<?php
namespace Inchoo\Shipping\Model\Carrier;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Rate\Result;
class Example extends \Magento\Shipping\Model\Carrier\AbstractCarrier implements
\Magento\Shipping\Model\Carrier\CarrierInterface
{
/**
* #var string
*/
protected $_code = 'example';
/**
* #param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* #param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory
* #param \Psr\Log\LoggerInterface $logger
* #param \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory
* #param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory
* #param array $data
*/
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory,
\Psr\Log\LoggerInterface $logger,
\Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory,
\Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory,
array $data = []
) {
$this->_rateResultFactory = $rateResultFactory;
$this->_rateMethodFactory = $rateMethodFactory;
parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
}
/**
* #return array
*/
public function getAllowedMethods()
{
return ['example' => $this->getConfigData('name')];
}
/**
* #param RateRequest $request
* #return bool|Result
*/
public function collectRates(RateRequest $request)
{
if (!$this->getConfigFlag('active')) {
return false;
}
/** #var \Magento\Shipping\Model\Rate\Result $result */
$result = $this->_rateResultFactory->create();
/** #var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */
$method = $this->_rateMethodFactory->create();
$method->setCarrier('example');
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod('example');
$method->setMethodTitle($this->getConfigData('name'));
/*you can fetch shipping price from different sources over some APIs, we used price from config.xml - xml node price*/
$amount = $this->getConfigData('price');
$method->setPrice($amount);
$method->setCost($amount);
$result->append($method);
return $result;
}
}
$data = json_decode(file_get_contents('php://input'), true);
$subdistrict = $data['addressInformation']['shipping_address']['customAttributes']['subdistrict'];

Yii2 rest api join query with ActiveDataProvider

I have a custom action in ActiveController and need to fetch some data by joining two tables.
I have written following query .
$query = Item::find()->joinWith(['subcategory'])->select(['item.*', 'sub_category.name'])->where(['item.active' => 1])->addOrderBy(['item.id' => SORT_DESC]);
$pageSize = (isset($_GET["limit"]) ? $_GET["limit"] : 1) * 10;
$page = isset($_GET["page"]) ? $_GET["page"] : 1;
$dataProvider = new ActiveDataProvider(['query' => $query, 'pagination' => ['pageSize' => $pageSize, "page" => $page]]);
$formatter = new ResponseFormatter();
return $formatter->formatResponse("", $dataProvider->getTotalCount(), $dataProvider->getModels());
but it is throwing an exception
"message": "Setting unknown property: common\\models\\Item::name",
Here is the item Model with all the fields and relation.
<?php
namespace common\models;
use Yii;
use yii\behaviors\TimestampBehavior;
use yii\db\BaseActiveRecord;
use yii\db\Expression;
/**
* This is the model class for table "item".
*
* #property integer $id
* #property integer $subcategory_id
* #property string $title
* #property resource $description
* #property integer $created_by
* #property integer $updated_by
* #property string $created_at
* #property string $updated_at
* #property string $image
* #property integer $active
*
* #property SubCategory $subcategory
*/
class Item extends \yii\db\ActiveRecord
{
public $imageFile;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'item';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['created_by', 'updated_by'], 'required'],
[['subcategory_id', 'created_by', 'updated_by', 'active'], 'integer'],
[['description'], 'string'],
[['created_at', 'updated_at'], 'safe'],
[['title', 'image'], 'string', 'max' => 999],
[['title'], 'unique'],
[['imageFile'], 'file', 'skipOnEmpty' => true, 'extensions' => 'png, jpg'],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'subcategory_id' => 'Subcategory ID',
'title' => 'Title',
'description' => 'Description',
'created_by' => 'Created By',
'updated_by' => 'Updated By',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
'image' => 'Image',
'active' => 'Active',
'imageFile' => 'Image',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getSubcategory()
{
return $this->hasOne(SubCategory::className(), ['id' => 'subcategory_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getCreatedBy()
{
return $this->hasOne(User::className(), ['id' => 'created_by']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getUpdatedBy()
{
return $this->hasOne(User::className(), ['id' => 'updated_by']);
}
public function behaviors()
{
return [
'timestamp' => [
'class' => TimestampBehavior::className(),
'attributes' => [
BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
BaseActiveRecord::EVENT_BEFORE_UPDATE => 'updated_at',
],
'value' => new Expression('NOW()'),
],
];
}
}
The joinWith makes a query using the joins requested, but result data are mapped in source model (in this case Item).
Since you have select(['item.*', 'sub_category.name']) , the framework will try to fill 'name' field of Item model, that does not exist and this generates the error.
According with documentation (http://www.yiiframework.com/doc-2.0/guide-rest-resources.html#overriding-extra-fields) you should have db relation subcategory populated from db, by default, but I don't see subcategory relation in your model.
So you have only to create subcategory relation in your model, such as:
public function getSubcategory() { return $this->hasOne(Subcategory::className(), ['id' => 'subcategory_id']); }
So you should solve your problem.
Other solution to have custom fields from more models could be:
1) Create a sql View (and from that create the Model) with fields that you want and pass it to ActiveDataProvide
2) Override extraFields method of the model (http://www.yiiframework.com/doc-2.0/yii-base-arrayabletrait.html#extraFields%28%29-detail)
Again, I suggest you to read this good article:
http://www.yiiframework.com/wiki/834/relational-query-eager-loading-in-yii-2-0/

Categories