Structuring CakePHP models in subfolders

in CakePHP


Say you want to organise your CakePHP models (tables) into subfolders. If you simply move the class into a subfolder, it won't be picked up automatically.

For example, here's how you can tell the TableLocator to look for OrderStatuses in a specific subfolder Orders under Model/Table.

In your controller:

First, prepare the shorthand variables.

$tableRegistry = new \Cake\ORM\TableRegistry();
$tableLocator = $tableRegistry->getTableLocator();

Tell the Table Locator to look in your subfolder. Otherwise, because that subfolder is not a standard location, it won't be checked automatically.

$tableLocator->addLocation('Model/Table/Orders');
$ordersTable = $tableLocator->get('OrderStatuses');

Works for plugins too! Just use the dot notation:

$tableLocator->addLocation('Model/Table/Orders');
$ordersTable = $tableLocator->get('Cart.OrderStatuses');

(You don't have to point to the plugin when adding the location to the table locator in the first line above. Instead, you refer to the plugin using the dot notation inside the $tableLocator->get() call.)

Using Cart.OrderStatuses as an example, here are links to relevant code and what happens behind the scenes - provided, you added your subfolder using addLocation():

  1. TableLocator::get('Cart.OrderStatuses') once done with some preliminary checks and gotten the options ready, this method forwards the call to _getClassName
  2. TableLocator::_getClassName('Cart.OrderStatuses', ['alias'=>'OrderStatuses']) loops through the known locations using className to see if the class exists; this is where it checks the subfolder we added using addLocation() earlier
  3. App::className('Cart.OrderStatuses', 'Model/Table', 'Table') picks the plugin name, if any, out of the dot notation in the first argument, and handles the slashes, then calls the below function to check if the requested class actually exists
  4. App::_classExistsInBase('\Model\Table\OrderStatuses', 'Cart') simply concatenates the two arguments and calls class_exists('Cart\Model\Table\OrderStatuses'), returning its result

Be aware that this may cause trouble if you want a table class like that to pick up a specific entity class. Unless you put your entity class into similarly named subfolder under Model/Entity, you will be getting a default Cake/ORM/Entity.

To understand why this happens and what are the options, let's look at the getEntityClass() method where the tables decide which entity class to pick.

On that line, it takes the table class path, drops the last part and appends /Entity, followed by the alias. This is built on assumption that the table class would be located in Model/Table, it would drop Table, append Entity and get Model/Entity in the end. But if our table is in Model/Table/Orders, it ends up looking inside Model/Table/Entity.

If your entities are actually inside Model/Entity, than a way around this issue is to set the entity class manually:

$table->setEntityClass('Shop.User');

If you only have the table class name in plural, you can copy the approach used in getEntityClass() to bring it to singular:

$entityName = \Cake\Utility\Inflector::classify(\Cake\Utility\Inflector::underscore($tableName));
#cakephp #cakephp-plugin-development