CakePHP 3.X - patchEntity failed to set foreign key - php

I'm trying to save an Order entity, but patchEntity always set two fields that are foreign keys to null.
Orders are associated to Addresses with 2 associations (Delivery and Invoice).
Associated addresses already exists, so I just want to save address id as a foreign key into Orders table.
OrdersTable
namespace OrderManager\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use OrderManager\Model\Entity\Order;
/**
* Orders Model
*/
class OrdersTable extends Table {
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config) {
$this->table('orders');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Contacts', [
'foreignKey' => 'contact_id',
'joinType' => 'INNER',
'className' => 'ContactManager.Contacts'
]);
// ...
$this->belongsTo('DeliveryAddresses', [
'foreignKey' => 'delivery_address',
'className' => 'ContactManager.Addresses'
]);
$this->belongsTo('InvoiceAddresses', [
'foreignKey' => 'invoice_address',
'className' => 'ContactManager.Addresses'
]);
}
public function validationDefault(Validator $validator) {
// ...
$validator
->add('delivery_address', 'valid', ['rule' => 'numeric'])
->allowEmpty('delivery_address');
$validator
->add('invoice_address', 'valid', ['rule' => 'numeric'])
->allowEmpty('invoice_address');
// ...
}
Controller
$data = [
// ...
'contact_id' => 34,
'delivery_address' => 8,
'invoice_address' => 8,
'currency' => 'Euro',
'total_paid' => '100.00',
'shipping_number' => ''
// ...
];
$entity = $this->Orders->newEntity();
$entity = $this->Orders->patchEntity($entity, $data);
debug($entity);
debug($entity) always tells me :
'delivery_address' => null,
'invoice_address' => null,
When I remove the belongsTo associations (DeliveryAddresses and InvoiceAddresses), my fields get the numeric value (8). But I need these associations.
How can I keep these associations and save numeric values for the foreign keys ?

The foreign key names are conflicting with the association property names (where the data for the associations is being stored), which are by default being derived from the association name, and in case of a belongsTo one it's the singular underscored variant of the association name, ie delivery_address and invoice_address.
See Cookbook > Database Access & ORM > Associations > BelongsTo Associations
To fix this, either stick to the conventions and append _id to your foreign keys, ie delivery_address_id and invoice_address_id, or change the property names using the propertyName option
$this->belongsTo('DeliveryAddresses', [
'propertyName' => 'delivery_address_data',
//...
]);
$this->belongsTo('InvoiceAddresses', [
'propertyName' => 'invoice_address_data',
//...
]);
Unless you're working with a legacy database, I'd strongly recommend to choose the former solution and make your foreign keys stick to the conventions!

Related

Laravel Backpack - Inline create, relationship not being added on DB

I'm trying the 4.1 new feature "Inline create", but I can't seem to associate the ids of the items created. Let me explain what I'm doing / what I want:
I have "Folders" that have "Chapters" inside (so 1-n relation).
My code:
CRUD::addField([ //Folder crud
'name' => 'chapters',
'type' => 'relationship',
'label' => 'Unidad',
'model' => "App\Models\Chapter",
'inline_create' => [
'entity' => 'chapter',
'modal_class' => 'modal-dialog modal-xl',
'modal_route' => route('chapter-inline-create'),
'create_route' => route('chapter-inline-create-save'),
]
]);
protected function setupCreateOperation() //Chapter crud
{
CRUD::setValidation(ChapterRequest::class);
CRUD::addField([
'name' => 'name',
'type' => 'text',
'label' => 'Nombre'
]);
}
public function chapters() //Folder model
{
return $this->hasMany(Chapter::class);
}
public function folder() //Chapter model
{
return $this->belongsTo(Folder::class);
}
It creates the main item and the related items no problem, but it doesn't actually relate them in the database at any point.
Any clue of what I might be doing wrong? Followed the docs but can't seem to make it work.
Thank you.
Do you have the right column names in the db ? The columns that are making the relationship possible, i.e in the folder table you should have a column named something like chapter_name or chapter_id, to identify the chapter where the folder belongs to.
Moreover, if those columns do not follow laravel conventions you need to add them as the second and third parameter when you are implementing the relationship in the models
More details here https://laravel.com/docs/8.x/eloquent-relationships#one-to-many
One note on this... I was running into this issue and realized that I forgot to make the parent_id fillable on my child model.
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'parent_id',
]

CakePHP associations relations table

I need to build the associations like Group hasMany users and User belongToMany groups.
But I can't get the right result, it always use the wrong table instead groups_relations
My models:
class GroupsTable extends Table
{
public function initialize(array $config)
{
$this->setTable('groups');
$this->setDisplayField('title');
$this->setPrimaryKey('id');
$this->hasMany('Users', [
'joinTable' => 'groups_relations',
'foreignKey' => 'user_id',
]);
}
}
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->table('user_users');
$this->belongsToMany('Groups', [
'joinTable' => 'groups_relations',
'foreignKey' => 'group_id',
]);
}
}
class GroupsRelationsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('groups_relations');
$this->setDisplayField('group_id');
$this->setPrimaryKey('id');
$this->belongsTo('Groups', [
'foreignKey' => 'group_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
}
}
And my table groups_relations:
id | group_id | user_id
I run query as:
$groupsWithUsers = $this->Groups->find('all', array(
'contain' => array('Users')
));
I can't understand how to tell to cake use my intermediary table and append reuslts to array.
joinTable is not a valid configuration key for a hasMany association. I think that you want to have Groups belongsToMany Users. Another clue about this is that hasMany is the "opposite" of belongsTo, while belongsToMany is it's own opposite. (That is, if A hasMany B, then B belongsTo A, but if A belongsToMany B, then B belongsToMany A.) Note that you will also want to change your GroupsRelations association with Users to belongsTo.
Is this code that was baked for you? Because it should know better. When I run into sticky association problems, I sometimes have Cake bake the model code for me, and then look at how the result differs from what I've written.
Rather than trying to use the relation the way you are doing why not just select from the relations table in the first place. This seems like the more Cake way of doing things. You can exclude the conditions clause if you want all data back.
$groupsWithUsers = $this->GroupsRelations->find('all', array(
'contain' => ['Users', 'Groups'],
'conditions' => ['Group.id' => $id]
)
);
After further looking into this I found something I have not used but seems to fit exactly what you need its a belongsToMany using an intermediary table. In your table file for the users you would add the following. A similar entry would be added to the group page.
$this->belongsToMany('Groups', [
'through' => 'GroupRelations',
]);

In CakePHP, how to have a Table belongsToMany OtherTable hasMany AnotherTable

I am creating a tool that allows to generate dynamic forms. There are several tables in question:
Form [the Form master table]
FormField [JoinTable to Field]
Field [the fields available for inclusion in Form]
FieldValidation [The table containing relation data between the FormField and the Validation option]
Validation [The available Validation options]
For the FieldValidation - this could in effect be a hasMany from Field, but I am unsure of whether I need to set up this relation from the Field table, or from the join table FieldValidation. The Validation table literally just includes the definitions for the validation options. This does not actually need to be a belongsToMany relation from the FormField/Field table. A hasMany is fine if that simplifies things.
Is this even possible?
Form -> [FormField] -> Field -> [FieldValidation] -> Validation
I have never done this before - so if there is a better way to approach this, I am all ears. My main concern is being able to select Form, contain Field's, and then contain the Validation for each field selected. Obviously, multiple validation rules can be selected per field.
A little late, but I did resolve this issue.
ERD Diagram of Physical Relationship in DB
Model: UsersTable
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('users');
$this->displayField('username');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsToMany('Projects', [
'foreignKey' => 'user_id',
'targetForeignKey' => 'project_id',
'through' => 'ProjectsUsers'
]);
$this->hasMany('ProjectsUsers', [
'foreignKey' => 'user_id'
]);
}
}
Model: ProjectsTable
class ProjectsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('projects');
$this->displayField('name');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsToMany('Users', [
'foreignKey' => 'project_id',
'targetForeignKey' => 'user_id',
'through' => 'ProjectsUsers'
]);
$this->hasMany('ProjectsUsers', [
'foreignKey' => 'project_id'
]);
}
}
Model: ProjectsUsersTable - this is the model for the JOIN table (through)
class ProjectsUsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('projects_users');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'user_id'
]);
$this->belongsTo('Projects', [
'foreignKey' => 'project_id'
]);
$this->hasMany('ProjectsUsersPermissions', [
'foreignKey' => 'projects_users_id'
]);
}
}
Model: ProjectsUsersPermissions - this is the relation to the join table
class ProjectsUsersPermissionsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('projects_users_permissions');
$this->displayField('role');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('ProjectsUsers', [
'foreignKey' => 'projects_users_id'
]);
}
}
Then the controller find action
$this->Projects->find()
->where(
[
'Projects.id' => $projectId
]
)
->contain(
[
'Users', // through belongsToMany
'ProjectsUsers' => [ // through hasMany [joinTableModel]
'ProjectsUsersPermissions' // through hasMany
]
]
)
->first();
This may be overkill for this scenario, and it is not my exact implementation - so don't think I am just doing unnecessary joins/contains. In my real life scenario, this works perfectly.
Hope this helps someone!

Laravel model inheritance

I am using the Toddish/Verify library for Laravel as it includes 99% of what I need for my project. All I need is to add some fields.
I have added them in a migration, and I want to add them also to mass creation:
use Toddish\Verify\Models\User as VerifyUser;
class User extends VerifyUser
{
public function __construct () {
array_merge ($this->fillable, array(
'salutation', 'title', 'firstname', 'lastname', 'phonenumber', 'mobilenumber'
));
}
}
However, when I run my creation test:
public function testUserCreation () {
$user = User::create(
[
'username' => 'testusername',
'email' => 'email#test.com',
'password' => 'testpassword',
'salutation' => 'MrTest',
'title' => 'MScTest',
'firstname' => 'Testfirstname',
'lastname' => 'Testlastname',
'phonenumber' => 'testPhoneNumber',
'mobilenumber' => 'testMobileNumber',
]
);
$this->assertEquals($user->salutation, 'MrTest');
$this->assertEquals($user->title, 'MScTest');
$this->assertEquals($user->firstname, 'Testfirstname');
$this->assertEquals($user->lastname, 'Testlastname');
$this->assertEquals($user->phonenumber, 'testPhoneNumber');
$this->assertEquals($user->mobilenumber, 'testMobileNumber');
}
I get this:
Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation: 19 users.username may not be NULL (SQL: insert into "users" ("updated_at", "created_at") values (2014-03-03 09:57:41, 2014-03-03 09:57:41))
in all tests that involve user creation, as if it had forgotten about the parents attributes when saving the model.
What am I doing wrong?
The problem is that you're overriding what I assume is the Eloquent constructor, so the values are never getting passed.
Change __construct to look like the following.
public function __construct(array $attributes = array())
{
parent::__construct($attributes);
array_merge ($this->fillable, array(
'salutation', 'title', 'firstname', 'lastname', 'phonenumber', 'mobilenumber'
));
}
The Model::create method will actually create a new instance of the model and pass the array into the __construct. You're overriding this and preventing it from passing the information through.
Note If you decide to override core methods like you've done here, always check inheritance and make sure you aren't breaking anything.

Kohana 3.3 ORM relations

i'm starting with Kohana 3.3 ORM trying to apply it to an existing internal project.
The project is in use, so i can't change the schema's names. The current schema definition is the following:
Table: utente
idUtente VARCHAR PK
nome VARCHAR
// other fields
Table: sessione
idSessione SERIAL PK
idUtente VARCHAR (FK to utente.idUtente)
// other fields
Table: ruolo
idRuolo SERIAL PK
nome VARCHAR
//other fields
Table: ruoloutente
idRuolo PK (FK to ruolo.idRuolo)
idUtente PK (FK to utente.idUtente)
scadenza DATETIME
// other fields
Now i defined custom table name and custom primary key name into the models and if i use ORM::factory('Utente', 'Marco'); (or any other model) everything is going fine.
class Model_Utente extends ORM {
protected $_table_name ='utente';
protected $_primary_key ='idUtente';
protected $_has_many =
array(
'ruoli' => array(
'model' => 'Ruolo',
'far_key' => 'idRuolo',
'foreign_key' => 'idUtente',
'through' => 'ruoloutente',
),
'sessioni' => array(
'model' => 'Sessione',
'far_key' => 'idSessione',
'foreign_key' => 'idUtente',
),
);
// class logic here
}
class Model_Ruolo extends ORM {
protected $_table_name ='ruolo';
protected $_primary_key ='idRuolo';
protected $_has_many =
array(
'utenti' => array(
'model' => 'Utente',
'far_key' => 'idUtente',
'foreign_key' => 'idRuolo',
'through' => 'ruoloutente',
),
);
// class logic here
}
class Model_Sessione extends ORM {
protected $_table_name ='sessione';
protected $_primary_key ='idSessione';
protected $_belongs_to =
array(
'utente' => array(
'model' => 'Utente',
'far_key' => 'idUtente',
'foreign_key' => 'idUtente',
),
);
// class logic here
}
Now from an instance of Utente i execute $this->ruoli->find_all() and $this->sessioni->find_all() but i obtain an empty model on both..
The generated query is correct on both finding, query executed directly in SQL returns 4 results on ruoli and two results on sessioni..
Found a solution
My problem was that i supposed that find() and find_all() methods would also perisist in the caller object the query results instead of only return the result. Many thanks to every one

Categories