Skip to content
This repository has been archived by the owner on Dec 2, 2023. It is now read-only.

Upgrading Notes for CakePHP 3.x

Mark Sch edited this page Mar 16, 2017 · 30 revisions

Some personal upgrading keynotes

General

  • namespace App = "src", Test = "tests" root folders
  • config, webroot, tmp, logs, etc moved out into root dir
  • App::uses() now use statements (alphabetical order would be nice)
  • Carbon instead of DateTime (https://github.com/cakephp/cakephp/pull/3328)

Locale

  • APP/Locale/deu/LC_MESSAGES/default.po is now src/Locale/de/default.po

Config

  • All one app.php config basically, which is nice.
  • Use 'quoteIdentifiers' => true for your Datasources setup if your tables contain reserved keywords for field names such as "index". I currently have it in as default (already enough other issues to figure out) - not sure how much slower it is compared to not quoting..

Model

  • Tables are plural (ArticlesTable), Entities singular and unfortunately still without prefix (Article)
  • Model::read() or findById() is now basically Table::get($id)
  • Model::hasAny(array $conditions) is now Table::exists(array $conditions)
  • All relations and behaviors are dynamically added via initialize()
  • Custom finders need a Query (additional find() before): $this->Foo->find()->find('slugged', [...])->first()
  • No afterFilter => use formatResults() or mapReduce()
  • No virtual fields anymore. Either virtual properties or custom finders etc ?
  • find(list) doesn't have fields key anymore, instead you should use idField and valueField keys.
  • updateAll() and deleteAll() do not join anymore, they can't. Use the QueryBuilder directly.

Query Conditions

Careful with migration 2.x conditions to 3.x ones. This can get ugly pretty fast, especially around IN / NOT IN.

Condition 2.x Result 2.x Result 3.x Condition 3.x fixed Result 3.x fixed
'field' => ['a', 'b'] field IN ('a', 'b') Exception 'field IN' => ['a', 'b'] same
'field' => ['a'] field = ('a') Exception 'field IN' => ['a'] field IN ('a')
'field' => [] field IS NULL Exception 'field IS' => null or '1!=1' field IS NULL or 1!=1

The last one has an important side-effect: You can not just change every 'field' => $array and add IN. If $array could be empty you will generate an invalid query now, which I find a huge downside to the 3.x ORM handling comparing to 2.x. You always need to have a switch like this then:

// If optional condition
if (!empty($array)) {
    $query->where('field IN' => $array);
}

// If required condition
if (!empty($array)) {
    $query->where('field IN' => $array);
} else {
    $query->where('field IS' => null); // Does allow NULL values (as in 2.x)
    // OR
    $query->where('1!=1'); // Does not allow NULL values (more correct?)
}

So in case you got any 'field' => $array there in your conditions better be sure you know what the desired query is, and modify it accordingly.

To complete the list, here the NOT IN case:

Condition 2.x Result 2.x Result 3.x Condition 3.x fixed Result 3.x fixed
'NOT' => ['field' => ['a', 'b']] NOT (field IN ('a', 'b')) Exception 'field NOT IN' => ['a', 'b'] field NOT IN ('a', 'b')
'NOT' => ['field' => ['a']] NOT (field = ('a')) Exception 'field NOT IN' => ['a'] field NOT IN ('a')
'NOT' => ['field' => []] NOT (field IS NULL) Exception 'field IS NOT' => null or '1=1' field IS NOT NULL or 1=1

When trying to make 2.x more 3.x, this happens:

Condition 2.x Result 2.x Result 3.x
'field IN' => ['a', 'b']] field IN ('a', 'b') same
'field IN' => ['a']] Bug/broken field IN ('a')
'field IN' => []] field IS NULL Exception
'field NOT IN' => ['a', 'b']] field NOT IN ('a', 'b') same
'field NOT IN' => ['a']] Bug/broken field NOT IN ('a')
'field NOT IN' => []] field IS NOT NULL Exception

The exceptions in 3.x I find highly problematic as it now requires a lot of additional application code before a condition can be formed.

Note that the bug/broken 2.x code has been fixed in #8125.

So if you want to use the Shim plugin functionality to assert expected behavior without exceptions, use arrayCondition() or arrayConditionArray() as outlined:

Condition 3.x shim fixed Result 3.x shim fixed Alternatively $query + arrayCondition()
->where($this->arrayConditionsArray('field', ['a', 'b']) field IN ('a', 'b') $this->arrayCondition($query, 'field', ['a', 'b'])
->where($this->arrayConditionsArray('field', ['a']) field IN ('a') $this->arrayCondition($query, 'field', ['a')
->where($this->arrayConditionsArray('field', []) 1!=1 $this->arrayCondition($query, 'field', [])
->where($this->arrayConditionsArray('field NOT', ['a', 'b']) field NOT IN ('a', 'b') $this->arrayCondition($query, 'field NOT', ['a', 'b'])
->where($this->arrayConditionsArray('field NOT', ['a']) field NOT IN ('a') $this->arrayCondition($query, 'field NOT', ['a'])
->where($this->arrayConditionsArray('field NOT', []) 1=1 $this->arrayCondition($query, 'field NOT', [])

Behavior

  • protected $_table (set via constructor) can provide methods with the object, no need to pass it through parameters
  • To get schema data you used to use $Model->schema('fieldname'), now you can use $this->_table->schema()->column('fieldname')
  • Since settings are now _config, default configs are now $this->_defaultConfig and flat (without any alias).
  • Reading them from outside needs ->config() wrapper access (as they are protected).
  • $Model->alias is now $this->_table->alias() then.
  • Make sure you got the right method signatures for callbacks, e.g. beforeSave(Event $event, Entity $entity, ArrayObject $options)
  • Make sure you got a TableRegistry::clear(); in your tearDown() method to avoid polluting tests with config settings from previous tests.

Controller

Component

  • All settings and attributes are now _config array - with public config() access.

View

  • ctp templates are now in Template, only "real" classes like RssView or SomeHelper stay in View.
  • You can use Cells now (requestAction is the devil and should not be used anymore if you ever went down that path).

Using classes in templates

It's probably best to avoid using Cake\Utility\Configure::read(). You can probably use class_alias('Cake\Utility\Configure', 'Conf'); in bootstrap and then use

Conf::read(‘debug’)

in the templates.

The alternative for (static) access would be to have helpers around all those things, so you can easily do

$this->Configure->read('debug')

Also eases testing/mocking I imagine.

Helper

  • All settings and attributes are now _config array - with public config() access.

FormHelper

  • create() now needs an entity passed for automagic schema-field-mapping.
  • templates are used to define output/markup

Console

  • Shells are easier to construct and work with or mock - __construct(ConsoleIo $io = null)
  • $this->_io->overwrite() can be nice for progress bars (careful that no newlines are outputted, then it works for all OS)

Routing

Default routes should be avoided in favor of custom written ones. InflectedRoute class will need to be used for BC if lowercase controller names are necessary/desired. Otherwise, for new projects, use Dashed.

So all links generated in the controller or view layer also need to be properly cased now:

$this->Url->build(array('plugin' => 'Install', 'controller' => 'Install', 'action' => 'index'));

Test

  • tests are now in subfolder TestCase inside "tests"/"TestCase"
  • bootstrap.php needed in /tests