Completed
Push — master ( eeec40...5164d5 )
by 紘己
04:26
created

ChangelogBehavior::collectChangelogBeforeValues()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 20
nc 7
nop 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A ChangelogBehavior::_associationsIndexedByProperty() 0 9 1
1
<?php
2
3
namespace Changelog\Model\Behavior;
4
5
use ArrayObject;
6
use Cake\Database\Type;
7
use Cake\Datasource\EntityInterface;
8
use Cake\Event\Event;
9
use Cake\ORM\Association;
10
use Cake\ORM\Association\BelongsTo;
11
use Cake\ORM\Behavior;
12
use Cake\ORM\Locator\LocatorAwareTrait;
13
use Cake\ORM\Table;
14
use Cake\Utility\Hash;
15
use Exception;
16
use UnexpectedValueException;
17
18
/**
19
 * TODO: impl
20
 *
21
 * Licensed under The MIT License
22
 * For full copyright and license information, please see the LICENSE file
23
 */
24
class ChangelogBehavior extends Behavior
25
{
26
27
    use LocatorAwareTrait;
28
29
    /**
30
     * Default config
31
     *
32
     * These are merged with user-provided configuration when the behavior is used.
33
     *
34
     * @var array
35
     */
36
    protected $_defaultConfig = [
37
        'autoSave' => true,
38
        'changelogTable' => 'Changelog.Changelogs',
39
        'columnTable' => 'Changelog.ChangelogColumns',
40
        'collectChangesOnBeforeSave' => true,
41
        'combinations' => [],
42
        'convertAssociations' => true,
43
        'convertForeignKeys' => true,
44
        'convertDatetimeColumns' => true,
45
        'equalComparison' => true,
46
        'exchangeForeignKey' => true,
47
        'filterForeignKeys' => true,
48
        'ignoreColumns' => [
49
            'id',
50
            'created',
51
            'modified'
52
        ],
53
        'logIsNew' => false,
54
        'logEmptyChanges' => false,
55
        'saveChangelogOnAfterSave' => true,
56
        'tableLocator' => null
57
    ];
58
59
    /**
60
     * Holds collected before values
61
     *
62
     * @var array
63
     */
64
    protected $_collectedBeforeValues = null;
65
66
    /**
67
     * Holds changes to save
68
     *
69
     * @var array
70
     */
71
    protected $_changes = [];
72
73
    /**
74
     * olds combination columns
75
     *
76
     * @var array
77
     */
78
    protected $_combinationColumns = [];
79
80
    /**
81
     * Initialize tableLocator also.
82
     *
83
     * {@inheritdoc}
84
     */
85
    public function initialize(array $config = [])
86
    {
87
        parent::initialize($config);
88
        if ($tableLocator = $this->config('tableLocator')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
89
            $this->tableLocator($tableLocator);
90
        }
91
    }
92
93
    /**
94
     * beforeSave callback.
95
     * Collects original values of associations.
96
     *
97
     * {@inheritdoc}
98
     */
99
    public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
100
    {
101
        if ($this->config('collectChangesOnBeforeSave')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
102
            $this->collectChanges($entity, $options);
103
        }
104
    }
105
106
    public function collectChanges(EntityInterface $entity, ArrayObject $options = null)
107
    {
108
        $Changelogs = $this->getChangelogTable();
0 ignored issues
show
Unused Code introduced by
$Changelogs is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
109
        $Columns = $this->getColumnTable();
110
        $table = $this->_table;
111
112
        /**
113
         * Be sure whether log new entity or not.
114
         */
115
        if (!$this->config('logIsNew') && $entity->isNew()) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
116
            return false;
117
        }
118
119
        /**
120
         * Extract column names from original values
121
         */
122
        $columns = array_keys($entity->getOriginalValues());
123
124
        /**
125
         * Extract dirty columns.
126
         * Exchange columns to actually dirty ones.
127
         */
128
        $afterValues = $entity->extract($columns, $isDirty = true);
129
        $columns = array_keys($afterValues);
130
131
        /**
132
         * Adds extra columns when combinations was given.
133
         */
134
        $this->_combinationColumns = [];
135
        if ($combinations = $this->config('combinations')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
136
            foreach ($combinations as $name => $settings) {
137
                $settings = $this->_normalizeCombinationSettings($settings);
138
                $this->_combinationColumns = array_merge($this->_combinationColumns, $settings['columns']);
139
            }
140
            $this->_combinationColumns = array_values(array_unique($this->_combinationColumns));
141
142
            $columns = array_values(array_unique(array_merge($columns, $this->_combinationColumns)));
143
            $afterValues = $entity->extract($columns);
144
        }
145
146
        /**
147
         * Extract original value from decided columns.
148
         */
149
        $beforeValues = $entity->extractOriginal($columns);
0 ignored issues
show
Bug introduced by
The method extractOriginal() does not exist on Cake\Datasource\EntityInterface. Did you maybe mean extract()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
150
151
        /**
152
         * Filters ignored columns
153
         */
154
        $columns = array_diff($columns, $this->config('ignoreColumns'));
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
155
156
        /**
157
         * Exception, before counts should equal to after counts
158
         */
159
        if (count($beforeValues) !== count($afterValues)) {
160
            return false;
161
        }
162
163
        /**
164
         * Filters changes
165
         */
166
        $changes = [];
167
        $associations = $this->_associationsIndexedByProperty();
168
        $foreignKeys = $this->_associationsForForeignKey();
169
        foreach ($columns as $column) {
170
            /**
171
             * Prepare values for events
172
             */
173
            $before = $beforeValues[$column];
174
            $after = $afterValues[$column];
175
176
            $isAssociation = array_key_exists($column, $associations);
177
            $isForeignKey = array_key_exists($column, $foreignKeys);
178
179
            /**
180
             * Prepare association/column info
181
             */
182
            if ($isAssociation) {
183
                $columnDef = null;
184
                $association = $associations[$column];
185
            } else {
186
                $columnDef = $table->schema()->column($column);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\ORM\Table::schema() has been deprecated with message: 3.4.0 Use setSchema()/getSchema() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
187
                $association = null;
188
                if ($isForeignKey) {
189
                    $association = $foreignKeys[$column];
190
                }
191
            }
192
193
            /**
194
             * Event data. These variables can be changed via registered events.
195
             */
196
            $eventData = new ArrayObject(compact([
197
                'entity',
198
                'isForeignKey',
199
                'isAssociation',
200
                'before',
201
                'after',
202
                'beforeValues',
203
                'afterValues',
204
                'column',
205
                'columnDef',
206
                'association',
207
                'table',
208
                'Columns'
209
            ]));
210
211
            /**
212
             * Dispatches convert event
213
             */
214
            $event = $table->dispatchEvent('Changelog.convertValues', [$eventData]);
0 ignored issues
show
Unused Code introduced by
$event is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
215
            extract((array)$eventData);
0 ignored issues
show
Bug introduced by
(array) $eventData cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
216
217
            if (!$this->_combinationColumns || !in_array($column, $this->_combinationColumns)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_combinationColumns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
218
                /**
219
                 * Dispatches filter event
220
                 */
221
                $event = $table->dispatchEvent('Changelog.filterChanges', [$eventData]);
222
                if (!$event->result) {
223
                    continue;
224
                }
225
            }
226
227
            /**
228
             * Determine changes from result
229
             */
230
            extract((array)$eventData);
0 ignored issues
show
Bug introduced by
(array) $eventData cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
231
            $changes[] = [
232
                'column' => $column,
233
                'before' => $before,
234
                'after' => $after,
235
            ];
236
        }
237
238
        /**
239
         * Make combinations
240
         */
241
        if ($combinations = $this->config('combinations')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
242
            $changes = $this->makeChangelogCombinations($entity, $changes, $combinations);
243
        }
244
245
        return $this->_changes = $changes;
246
    }
247
248
    public function makeChangelogCombinations(EntityInterface $entity, array $changes, array $combinations)
249
    {
250
        $indexedByColumn = collection($changes)->indexBy('column')->toArray();
251
        $removeKeys = [];
252
        $foreignKeys = $this->_associationsForForeignKey();
253
        foreach ($combinations as $name => $settings) {
254
            $settings = $this->_normalizeCombinationSettings($settings);
255
256
            $values = [];
257
            foreach ($settings['columns'] as $column) {
258
                $removeKeys[] = $column;
259
260
                if ($association = Hash::get($foreignKeys, $column)) {
261
                    $property = $association->property();
262
                    if (isset($indexedByColumn[$property])) {
263
                        $values['before'][$column] = $indexedByColumn[$property]['before'];
264
                        $values['after'][$column] = $indexedByColumn[$property]['after'];
265
                        $removeKeys[] = $property;
266
                    } elseif (isset($indexedByColumn[$column])) {
267
                        $values['before'][$column] = $indexedByColumn[$column]['before'];
268
                        $values['after'][$column] = $indexedByColumn[$column]['after'];
269
                    } else {
270
                        $values['before'][$column] = $entity->get($column);
271
                        $values['after'][$column] = $entity->get($column);
272
                    }
273
                } elseif (isset($indexedByColumn[$column])) {
274
                    $values['before'][$column] = $indexedByColumn[$column]['before'];
275
                    $values['after'][$column] = $indexedByColumn[$column]['after'];
276
                } else {
277
                    $values['before'][$column] = $entity->get($column);
278
                    $values['after'][$column] = $entity->get($column);
279
                }
280
            }
281
282
            if (isset($settings['convert'])) {
283
                $convert = $settings['convert'];
284
                $converted = $convert($name, $values['before'], $values['after']);
285
            } else {
286
                $converted = [
287
                    'column' => $name,
288
                    'before' => implode(' ', array_filter($values['before'])),
289
                    'after' => implode(' ', array_filter($values['after'])),
290
                ];
291
            }
292
293
            /**
294
             * Filters converted value not equal to. also remove keys from indexedColumn
295
             * to prevent changes remaining.
296
             */
297
            if ($converted['before'] == $converted['after']) {
298
                unset($combinations[$name]);
299
                continue;
300
            }
301
            $indexedByColumn[$name] = $converted;
302
        }
303
304
        $removeKeys = array_diff($removeKeys, array_keys($combinations));
305
        $indexedByColumn = array_diff_key($indexedByColumn, array_flip($removeKeys));
306
        return array_values($indexedByColumn);
307
    }
308
309
    protected function _normalizeCombinationSettings($settings)
310
    {
311
        if (!is_array($settings)) {
312
            throw new UnexpectedValueException(__d('changelog', 'Changelog: `combinations` option should be array'));
313
        }
314
315
        /**
316
         * If numric keys e.g. ['first_name', 'last_name'] given, Handles it
317
         * as a list of columns.
318
         */
319
        if (Hash::numeric(array_keys($settings))) {
320
            $settings = ['columns' => $settings];
321
        }
322
323
        if (!isset($settings['columns']) || !is_array($settings['columns'])) {
324
            throw new UnexpectedValueException(__d('changelog', 'Changelog: `combinations` option should have `columns` key and value as array of columns'));
325
        }
326
327
        return $settings;
328
    }
329
330
    /**
331
     * afterSave callback.
332
     * This logs entities when `onAfterSave` option was turned on.
333
     *
334
     * {@inheritdoc}
335
     */
336
    public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options)
337
    {
338
        if ($this->config('saveChangelogOnAfterSave')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
339
            $this->saveChangelog($entity, $this->_changes);
340
        }
341
    }
342
343
    /**
344
     * Saves changelogs for entity.
345
     *
346
     * @param \Cake\Datasource\EntityInterface $entity The entity object to log changes.
347
     * @return \Cake\Datasource\EntityInterface|bool Entity object when logged otherwise `false`.
348
     */
349
    public function saveChangelog(EntityInterface $entity, $changes = [])
350
    {
351
        /**
352
         * Be sure whether change was done or not
353
         */
354
        if (!$this->config('logEmptyChanges') && empty($changes)) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
355
            return false;
356
        }
357
358
        /**
359
         * Saves actually
360
         */
361
        $data = new ArrayObject([
362
            'model' => $this->_table->alias(),
0 ignored issues
show
Deprecated Code introduced by
The method Cake\ORM\Table::alias() has been deprecated with message: 3.4.0 Use setAlias()/getAlias() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
363
            'foreign_key' => $entity->get($this->_table->primaryKey()),
0 ignored issues
show
Bug introduced by
It seems like $this->_table->primaryKey() targeting Cake\ORM\Table::primaryKey() can also be of type array; however, Cake\Datasource\EntityInterface::get() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Deprecated Code introduced by
The method Cake\ORM\Table::primaryKey() has been deprecated with message: 3.4.0 Use setPrimaryKey()/getPrimaryKey() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
364
            'is_new' => $entity->isNew(),
365
            'changelog_columns' => $changes,
366
        ]);
367
        $options = new ArrayObject([
368
            'associated' => 'ChangelogColumns',
369
            'atomic' => false
370
        ]);
371
372
        $Changelogs = $this->getChangelogTable();
373
        return $this->_table->dispatchEvent('Changelog.saveChangelogRecords', compact('Changelogs', 'data', 'options'))->result;
374
    }
375
376
    /**
377
     * Helper method to get table associations array
378
     * indexed by these properties.
379
     *
380
     * @return \Cake\ORM\Association[]
381
     */
382
    protected function _associationsIndexedByProperty()
383
    {
384
        return collection($this->_table->associations())
385
            ->combine(function ($association) {
386
                return $association->property();
387
            }, function ($association) {
388
                return $association;
389
            })->toArray();
390
    }
391
392
    /**
393
     * Helper method to get associations array that table has
394
     * foreign key (means BelongsTo) indexed by foreign key.
395
     *
396
     * @return \Cake\ORM\Association[]
397
     */
398
    protected function _associationsForForeignKey()
399
    {
400
        return collection($this->_table->associations())
401
            ->filter(function ($association) {
402
                return $association instanceof BelongsTo;
403
            })->combine(function ($association) {
404
                return $association->foreignKey();
405
            }, function ($association) {
406
                return $association;
407
            })->toArray();
408
    }
409
410
    /**
411
     * Returns changelogs table
412
     *
413
     * @return \Cake\ORM\Table
414
     */
415
    public function getChangelogTable()
416
    {
417
        return $this->tableLocator()->get($this->config('changelogTable'));
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
418
    }
419
420
    /**
421
     * Returns changelogs table
422
     *
423
     * @return \Cake\ORM\Table
424
     */
425
    public function getColumnTable()
426
    {
427
        return $this->tableLocator()->get($this->config('columnTable'));
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
428
    }
429
430
    /**
431
     * Define additional events for filter
432
     *
433
     * {@inheritdoc}
434
     */
435
    public function implementedEvents()
436
    {
437
        return parent::implementedEvents() + [
438
            'Changelog.convertValues' => 'convertChangelogValues',
439
            'Changelog.filterChanges' => 'filterChanges',
440
            'Changelog.saveChangelogRecords' => 'saveChangelogRecords',
441
        ];
442
    }
443
444
    /**
445
     * Default convert process
446
     *
447
     * @param \Cake\Event\Event $event The event for callback
448
     * @param ArrayObject $data The event data. contains:
449
     *                          - entity
450
     *                          - isForeignKey
451
     *                          - isAssociation
452
     *                          - before
453
     *                          - after
454
     *                          - beforeValues
455
     *                          - afterValues
456
     *                          - column
457
     *                          - columnDef
458
     *                          - table
459
     *                          - Columns
460
     * @return array couple of $before, $after
461
     */
462
    public function convertChangelogValues(Event $event, ArrayObject $data)
463
    {
464
        extract((array)$data);
0 ignored issues
show
Bug introduced by
(array) $data cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
465
        /**
466
         * @var \Cake\ORM\Entity $entity
467
         * @var bool $isForeignKey
468
         * @var bool $isAssociation
469
         * @var mixed $before
470
         * @var mixed $after
471
         * @var array $beforeValues
472
         * @var array $afterValues
473
         * @var string $column
474
         * @var array $columnDef
475
         * @var \Cake\ORM\Table $table
476
         * @var \Cake\ORM\Table $Columns
477
         */
478
479
        /**
480
         * Date inputs sometime represents string value in
481
         * entity. This converts value for comparison.
482
         */
483
        if ($this->config('convertDatetimeColumns') && !$isAssociation && isset($columnDef['type'])) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
484
            switch ($columnDef['type']) {
485
                case 'date':
486
                case 'datetime':
487
                case 'time':
488
                    $baseType = $table->schema()->baseColumnType($column);
489
                    if ($baseType && Type::map($baseType)) {
490
                        $driver = $table->connection()->driver();
491
                        $before = Type::build($baseType)->toPHP($before, $driver);
492
                        $after = Type::build($baseType)->toPHP($after, $driver);
493
                    }
494
                    break;
495
            }
496
        }
497
498
        /**
499
         * Converts foreign keys. This converts belongsTo ID columns to associated
500
         * entity. Then it takes display field for the table.
501
         */
502
        if ($isForeignKey && $this->config('convertForeignKeys')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
503
            if ($this->config('exchangeForeignKey')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
504
                unset($data['beforeValues'][$column]);
505
                unset($data['afterValues'][$column]);
506
                $column = $association->property();
507
                $data['column'] = $column;
508
                $this->_combinationColumns[] = $column;
509
            }
510
511
            $before = $association->findById($before)->first(); 
512
            $after = $association->findById($after)->first();
513
            $before = $this->convertAssociationChangeValue($before, $association, 'before');
514
            $after = $this->convertAssociationChangeValue($after, $association, 'after');
515
        }
516
517
        /**
518
         * Converts associations
519
         */
520
        $converter = $this->config('convertAssociations');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
521
        if ($isAssociation && $converter) {
522
            /** 
523
             * If array was given, handles it as whitelist of associations
524
             */
525
            if (!is_array($converter) || is_callable($converter) || in_array($column, $converter)) {
526
                $before = $this->convertAssociationChangeValue($before, $association, 'before');
527
                $after = $this->convertAssociationChangeValue($after, $association, 'after');
528
            }
529
        }
530
531
        /**
532
         * Modifies event data
533
         */
534
        $data['before'] = $before;
535
        $data['after'] = $after;
536
        $data['beforeValues'][$column] = $before;
537
        $data['afterValues'][$column] = $after;
538
    }
539
540
    /**
541
     * Default converter for association values
542
     */
543
    public function convertAssociationChangeValue($value, $association, $kind)
544
    {
545
        if (!$value) {
546
            return is_array($value) ? null : $value;
547
        }
548
549
        $isMany = in_array($association->type(), [Association::MANY_TO_MANY, Association::ONE_TO_MANY]);
550
        $property = $association->property();
551
552
        /**
553
         * Call actual converter. callable can be set with `convertAssociations`
554
         * option.
555
         */
556
        $converter = $this->config('convertAssociations');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
557
        $callable = is_callable($converter) ? $converter : [$this, 'defaultConvertAssociation'];
558
        $arguments = [$property, $value, $kind, $association, $isMany];
559
560
        return call_user_func_array($callable, $arguments);
561
    }
562
563
    /**
564
     * Default converter for association values.
565
     *
566
     * @param string $property association property name
567
     * @param mixed $value expects EntityInterface/EntityInterface[]
568
     * @param string $kind either 'before'/'after'
569
     * @param \Cake\ORM\Association $association association object for the value
570
     * @param boolean $isMany true => [hasMany, belongsToMany] false => [hasOne, belongsTo]
571
     * @param array $beforeValue association original values. indexed by association properties.
0 ignored issues
show
Bug introduced by
There is no parameter named $beforeValue. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
572
     * @return mixed converted value
573
     */
574
    public function defaultConvertAssociation($property, $value, $kind, $association, $isMany)
575
    {
576
        if (!$value) {
577
            return null;
578
        }
579
        $displayField = $association->displayField();
580
581
        // hasMany, belongsToMany
582
        if ($isMany) {
583
            $values = (array)$value;
584
            return implode(', ', collection($values)
585
                ->sortBy($displayField, SORT_ASC, SORT_NATURAL)
586
                ->extract($displayField)
587
                ->filter()
588
                ->toArray());
589
        // hasOne, belongsTo
590
        } else {
591
            if (!$value instanceof EntityInterface) {
592
                return $value;
593
            }
594
595
            if ($kind === 'before') {
596
                return $value->getOriginal($displayField);
597
            } else {
598
                return $value->get($displayField);
599
            }
600
        }
601
    }
602
603
    /**
604
     * Default filter
605
     *
606
     * @param \Cake\Event\Event $event The event for callback
607
     * @param ArrayObject $data The event data. contains:
608
     *                          - entity
609
     *                          - isForeignKey
610
     *                          - isAssociation
611
     *                          - before
612
     *                          - after
613
     *                          - beforeValues
614
     *                          - afterValues
615
     *                          - column
616
     *                          - columnDef
617
     *                          - table
618
     *                          - Columns
619
     * @return bool column is changed or not
620
     */
621
    public function filterChanges(Event $event, ArrayObject $data)
622
    {
623
        extract((array)$data);
0 ignored issues
show
Bug introduced by
(array) $data cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
624
        /**
625
         * @var \Cake\ORM\Entity $entity
626
         * @var bool $isForeignKey
627
         * @var bool $isAssociation
628
         * @var mixed $before
629
         * @var mixed $after
630
         * @var array $beforeValues
631
         * @var array $afterValues
632
         * @var string $column
633
         * @var array $columnDef
634
         * @var \Cake\ORM\Table $table
635
         * @var \Cake\ORM\Table $Columns
636
         */
637
638
        /**
639
         * Filter e.g. null != ''
640
         */
641
        if ($this->config('equalComparison')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
642
            return $before != $after;
643
        }
644
645
        /**
646
         * Filter foreign keys
647
         */
648
        if ($this->config('filterForeignKeys')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
649
            return !$isForeignKey;
650
        }
651
652
        return true;
653
    }
654
655
    /**
656
     * Default save process
657
     *
658
     * @param \Cake\Event\Event $event The event for callback
659
     * @param \Cake\ORM\Table $Changelogs The table for parent
660
     * @param ArrayObject $data save data
661
     * @param ArrayObject $options save options
662
     * @return bool column is changed or not
663
     */
664
    public function saveChangelogRecords(Event $event, Table $Changelogs, ArrayObject $data, ArrayObject $options)
665
    {
666
        /**
667
         * Save changes to database
668
         */
669
        if ($this->config('autoSave')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
670
            $changelog = $Changelogs->newEntity((array)$data);
671
            return $Changelogs->save($changelog, (array)$options);
672
        }
673
    }
674
675
}
676