CakePHP 3.0.0 cascade dependent deletes - cascade

If I have the following in some model tables; As, Bs, Cs and Ds.
They have these associations defined:
AsTable:
$this->hasOne('Bs', [
'dependent' => true,
'cascadeCallbacks' => true,
]);
BsTable:
$this->belongsTo('As', [
'foreignKey' => 'A_id',
]);
$this->hasMany('Cs', [
'foreignKey' => 'B_id',
'dependent' => true,
'cascadeCallbacks' => true,
]);
CsTable:
$this->belongsTo('Bs', [
'foreignKey' => 'B_id',
]);
$this->hasMany('Ds', [
'foreignKey' => 'C_id',
'dependent' => true,
'cascadeCallbacks' => true,
]);
DsTable:
$this->belongsTo('Cs', [
'foreignKey' => 'C_id',
]);
Deleting an A now deletes the A, the B and any linked Cs but the Ds remain.
Deleting a C causes linked Ds to be deleted.
Why doesn't deleting an A also delete Ds linked to the Cs which are deleted?
How can I make it so it does?
EDIT:
Cake exact version 3.0.0
Looking at the SQL in the debugkit sql log it seems that the sequence is wrong to me.
select from Bs where A_id = a_id (finds record with id "b_id")
delete from Cs where B_id = b_id
select from Cs where B_id = b_id
delete from Bs where id = b_id
delete from A where id = a_id
(replaced table names and ids for readability)
Never touches D at all and obviously since it deletes from Cs before selecting it's never going to get the id(s) to be able to find anything in D.
Mightily confused!

Try upgrading you CakePHP installation to the latest 3.0 stable release

Related

cakephp4 get 1st record from containing table with order by a field

I have cakephp4 project
having 1 to many relationship between Portfolios and PSnaps
I want to show all 'Portfolios' with one associated record from PSnaps where its PSnaps.status=1 and order=>['PSnap.order_at'=>'ASC']
I tried many things but getting the correct result
below is giving 90% correct result only ordering on PSnaps.order_at is not working.
along with hasmany() i have created hasOne() association as shown in below model
Model
class PortfoliosTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('portfolios');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->hasOne('FirstPSnaps', [
'className' => 'PSnaps',
'foreignKey' => 'portfolio_id',
'strategy' => 'select',//also tried join
//'joinType'=>'LEFT',//also tried inner,left,right
//'sort' => ['FirstPSnaps.order_at' => 'ASC'], //*******this is not working
'conditions' => function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
$query->order(['FirstPSnaps.order_at' => 'ASC']);//*******also not working
return [];
}
])
;
$this->hasMany('PSnaps', [
'foreignKey' => 'portfolio_id',
]);
}
Controller
$pfolios = $this->Portfolios->find('all')
->select(['Portfolios.id','Portfolios.client','Portfolios.country','Portfolios.url'])
->where(['Portfolios.status'=>1])
->order(['Portfolios.order_at'=>'asc','Portfolios.id'=>'asc'])
->limit(8)
->contain([
'FirstPSnaps'=>function($q){
return $q
->select(['FirstPSnaps.portfolio_id','FirstPSnaps.snap'])
//->where(['FirstPSnaps.status'=>1])
//->order(['FirstPSnaps.order_at'=>'asc'])
;
}
])
->toArray();
it is returning correct porfolios with 1 p_snap record but ordering/sorting is not correct as I need first p_snap something like where p_snap.status=1 and p_span.portfolio_id= portfolios.id limit 1.

D8 migrate to field_ip address attached to accounts

I am working on a migrate script in D8 that pulls from a MySQL DB and will populate a table in D8 created by this module...
https://github.com/bjaxelsen/field_ipaddress
This module is a D8 port of this one...
https://www.drupal.org/project/field_ipaddress
anyways I got the module installed and added the field to the accounts with the unlimited number of values.
A general user migrate was already made and works fine. It will migrate the username, email, and status into Drupal and all is good here.
The second migration is to move a list of user IP addresses into D8. My dataset will look like this...
user_id slstatus status_id ipFrom ipTo modifyDateUnix
50374 1 0 1.1.1.1 1.1.1.1 1505415351
25108 0 1 4.4.4.0 4.4.4.255 1479243329
The real code runs the INET_ATON function in mysql to return integers, but the above to show the intent.
In the prepare row function I also take the IP Addresses and convert them into hex values, which is needed for the db table storage.
slstatus relates to the user's active status and status_id relates to the ip record's status. These are used to determine a new variable called deleted. I also make an variable called idx which is a...well an index. All of these new fields are added back to the row.
My YAML looks like this and is where my headache begins...
id: UserIpsMigrate
migration_group: 'UMG'
label: 'User IP Migration'
source:
plugin: UserIpsMigrate
process:
'field_ip_address/bundle': 'user'
'field_ip_address/deleted': deleted
'field_ip_address/entity_id':
plugin: migration_lookup
migration: UserMigrate
source: user_id
'field_ip_address/revision_id':
plugin: migration_lookup
migration: UserMigrate
source: user_id
'field_ip_address/langcode': 'en'
'field_ip_address/delta': idx
'field_ip_address/field_ip_address_ip_ipv6': 0
'field_ip_address/field_ip_address_ip_from': ipFrom
'field_ip_address/field_ip_address_ip_to': ipTo
destination:
plugin: entity:user
default_bundle: migration
migration_dependencies:
required:
- user_migrate
The errors I am getting show that this migration is trying to create a user...which will fail with this data and so I am certain I need to use a different destination plugin, but I have no idea...
1) which destination plugin that I should be using
2) if I went "off the rails" with my yaml
I found a solution.
First I changed my yaml file to look like this.
id: UserIpsMigrate
migration_group: HEUMG
label: 'User IP Migration'
source:
plugin: tUserIpsMigrate
process:
bundle:
plugin: default_value
default_value: user
deleted: deleted
entity_id:
plugin: migration_lookup
migration: UserMigrate
source: user_id
revision_id:
plugin: migration_lookup
migration: UserMigrate
source: user_id
langcode:
plugin: default_value
default_value: en
delta: delta
field_ip_address_ipv6:
plugin: default_value
default_value: 0
field_ip_address_ip_from: ipFrom
field_ip_address_ip_to: ipTo
destination:
plugin: field_ip_address
migration_dependencies:
required:
- user_migrate
I also found that the functions GetIds() are very important in determining the mapping from source to destination targets and is used by the migration system when making the migration mapping table.
Extending the sqlBase class, I had a query to pull the data need for the migration and it would use 2 keys from different tables users and ipaddresses. So my source getIds() function looked like this...
/**
* {#inheritdoc}
*/
public function getIds() {
return [
'user_id' => [
'type' => 'integer',
'alias' => 'slc',
],
'siteLicense_id' => [
'type' => 'integer',
'unsigned' => FALSE,
],
];
}
...and my destination getIds ended up as this...
/**
* {#inheritdoc}
*/
public function getIds() {
return [
'entity_id' => [
'type' => 'integer',
'unsigned' => FALSE,
],
'deleted' => [
'type' => 'integer',
'size' => 'tiny',
],
'delta' => [
'type' => 'integer',
'unsigned' => FALSE,
],
'langcode' => [
'type' => 'string',
'max_length' => 32,
'is_ascii' => TRUE,
],
];
}
With the above getIds() defined you will have a new table create once you run a drush command, like "drush migrate:status".
The table "migrate_map_" + your migration id or in my case migrate_map_useripsmigrate is created with the fields of source_id_hash, sourceid1, sourceid2, destid1, destid2, destid3, destid4, source_row_status, rollback_status, last_imported and hash. The source and destination fields are mapped to the getIds defined above and this is how the migration system keeps track of all migrations ran, from this specific migration. Other migrations will have their own tables.
As for the destination plugin itself, I had to make my own destination plugin based on extending the DestinationBase class.
Extending DestinationBase requires you to define the internals of 3 functions, import, getIds and, fields. GetIds I covered above and fields are just like defining fields in an SQL source plugin. Import is how your destination plugin will save the source data to the destination. In my case, I used an insert query like this...
/**
* {#inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = []) {
$bundle = $row->getDestinationProperty('bundle');
$deleted = $row->getDestinationProperty('deleted');
$entity_id = $row->getDestinationProperty('entity_id');
$revision_id = $row->getDestinationProperty('revision_id');
$langcode = $row->getDestinationProperty('langcode');
$delta = $row->getDestinationProperty('delta');
$field_ip_address_ipv6 = $row->getDestinationProperty('field_ip_address_ipv6');
$field_ip_address_ip_from = $row->getDestinationProperty('field_ip_address_ip_from');
$field_ip_address_ip_to = $row->getDestinationProperty('field_ip_address_ip_to');
$result = $this->con->insert('user__field_ip_address')
->fields([
'bundle' => $bundle,
'deleted' => $deleted,
'entity_id' => $entity_id,
'revision_id' => $revision_id,
'langcode' => $langcode,
'delta' => $delta,
'field_ip_address_ipv6' => $field_ip_address_ipv6,
'field_ip_address_ip_from' => $field_ip_address_ip_from,
'field_ip_address_ip_to' => $field_ip_address_ip_to,
])
->execute();
return [
'entity_id' => $entity_id,
'deleted' => $deleted,
'delta' => $delta,
'langcode' => $langcode,
];
}
The return should be the same fields as defined in your destination getIds() function and in the same order.
This may not be the most ideal method of writing a destination plugin, but it does work.

CakePHP retrieving when related model NULL

I have a null-able relation model, and I am using this standard code to retrieve my records:
$this->paginate = [
'contain' => ['Clients']
];
$coupons = $this->paginate($this->Coupons);
I am getting just the records that have client associated.
what is the best practice to make the contain work like OR, and not AND
EDIT: the relationship is seted as the following:
$this->belongsTo('Clients', [
'foreignKey' => 'client_id',
'joinType' => 'INNER'
]);
I have solved it by setting the joinType to LEFT:
$this->belongsTo('Clients', [
'foreignKey' => 'client_id',
'joinType' => 'LEFT'
]);

Filter by a field that's a foreign key

Using Yii2.
I have a table called 'calificacion' that has a foreign key 'alumno-id' that points to field id in table alumno.
The column defined in the GridView widget is:
[
'header' => 'Alumno',
'attribute' => 'alumno.id',
'value' => 'alumno.name'
],
And it's showing perfectly, but the filter, in the header of the column, is not appearing. I want to have a textbox for writing the name of the alumno and get filtered. How can I achieve that?
EDIT: Here're the files https://dl.dropboxusercontent.com/u/7059378/Desktop.zip
First declare an attribute in your SearchModel.
public $alumno_name;
In your search model's rule add:
[['alumno_name'], 'safe'],
Join with alumno relation in search method (supponsinbly there is a alumno relation in your Calificacion model):
$query = Calificacion::find()
->joinWith(['alumno alumno']);
To sort with $alumno_name add:
$dataProvider->sort = [
'attributes' => [
//Other attributes here
'alumno_name' => [
'asc' => ['alumno.name' => SORT_ASC],
'desc' => ['alumno.name' => SORT_DESC],
],
]
];
To filter this you have to add:
$query->andFilterWhere(['like', 'alumno.name', $this->alumno_name]);
Finally in your grid view add:
[
'attribute' => 'alumno_name',
'value' => 'alumno.name'
],
You can find more info here and here.
Also instead of header use 'label' => 'Alumno'.
You must first define a relation in Model of that table 'calificacion', like
public function getAlumno()
{
return $this->hasOne(Alumno::className(), ['id' => 'alumno_id']);
}
Than in the search model of Calificacion set that you are joining tables after validation, like
$query->joinWith('alumno');
Than set search like
$query->andFilterWhere([
'alumno.id' => $this.alumno_id
]);

Multiple hasMany() associations with different foreign keys

I am implementing a simple system that allows recording transactions between users. For that I have two tables transactions and users.
Every transaction is between two users and records the flow from one user to the other in the fields from_user_id and to_user_id which are foreign keys to users.id.
Now I would like to get all transactions for an user (in reverse chronologically order). I'd love to have something like this:
class UsersTable extends Table {
public function initialize(array $config) {
...
$this->hasMany('Transactions', [
'foreignKey' => [
'OR' => [
'Users.id = Transactions.from_user_id',
'Users.id = Transactions.to_user_id'
]
],
'finder' => [
...
]
]);
...
}
}
Of course this does not work out (SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Transactions.Array' in 'where clause') and setting foreignKey to false and using a custom queryBuilder is not allowed for hasMany associations.
What does work is using two different associations for both foreign keys (one for negative and one for positive transactions) and then merging the two, but I would rather not go this way. This is the current approach:
$this->hasMany('NegativeTransactions', [
'className' => 'Transactions',
'foreignKey' => 'from_user_id'
]);
$this->hasMany('PositiveTransactions', [
'className' => 'Transactions',
'foreignKey' => 'to_user_id'
]);
Has anybody any ideas?