Application Skeleton
pieceofcake2/app is the application skeleton for CakePHP 2 applications running on pieceofcake2/cakephp. It uses a directory layout compatible with CakePHP 5 (config/, src/, templates/, tests/, webroot/) and loads CakePHP and all dependencies with Composer. Its application classes are in the App namespace, as in CakePHP 5, and its templates are .php files in the directories of CakePHP 5 (layout/, element/, email/, Error/).
For global classes and .ctp templates, use pieceofcake2/app-legacy instead. To move an existing application to this layout and to namespaced classes, see Migrating to the Modern Layout: the layout and namespace commands of pieceofcake2/upgrade do it.
WARNING
CakePHP 2.x is for the maintenance of existing applications. For new projects, use the current major version of CakePHP instead.
Requirements
- PHP 8.0, 8.1, 8.2, 8.3, 8.4 or 8.5
- Composer
- Database: MySQL 5.6+, PostgreSQL 9.4+, SQLite 3 or SQL Server 2022+
- PHP extensions:
mbstring,intl,openssland the PDO driver of your database
See Requirements for details.
Installation
Create a new application with Composer's create-project command:
composer create-project pieceofcake2/app my_appThis creates the my_app directory and installs CakePHP (pieceofcake2/cakephp) into vendor/. The Bake and DebugKit plugins and the development tools (PHPUnit, PHPStan with pieceofcake2/phpstan-cakephp2, and the CakePHP coding standard) are installed as development dependencies, with the Markdown docs of this book for AI coding tools (pieceofcake2/docs, in vendor/pieceofcake2/docs/docs/).
Then finish the configuration:
- Copy
config/database.php.defaulttoconfig/database.php, and set the connection withDATABASE_URLinconfig/.env.local(see Environment Variables (.env) and Database and Mailer DSNs) or enter it in the file. Set the test database inconfig/.env.test.local(see Running Tests). - Change
Security.saltandSecurity.cipherSeedinconfig/core.php, or setSECURITY_SALTandSECURITY_CIPHER_SEEDinconfig/.env.local. The skeleton ships with well-known example values that must not be used in a real application. See Generating the Security Salts. - If you send email, copy
config/email.php.defaulttoconfig/email.php, and set the transport withMAILER_DSNor configure it in the file. - Make sure that the web server can write to
tmp/andlogs/.
Version Control
The .gitignore file of the skeleton is meant for your application, so you can run git init right after create-project. It ignores:
vendor/and the plugins that Composer installs intoplugins/(plugins/Bake/andplugins/DebugKit/). If you install more plugins with Composer, add them to.gitignore; the plugins of your own application inplugins/are committed.- the contents of
tmp/andlogs/. Theemptyfiles that keep the directories are committed. config/database.phpandconfig/email.php, and the.localfiles of.env(config/.env.localandconfig/.env.*.local), which hold the settings and credentials of each machine.config/.envandconfig/.env.testare committed, as in Symfony; keep real secrets in the.localfiles or in real environment variables.phpunit.xml(a local copy ofphpunit.xml.dist) and.phpunit.result.cache.
Commit composer.lock, so that every environment installs the same versions of the dependencies.
Development Plugins
config/bootstrap.php loads the Bake and DebugKit plugins when they are installed:
use Cake\Core\CakePlugin;
use Cake\Error\MissingPluginException;
foreach (['Bake', 'DebugKit'] as $plugin) {
try {
CakePlugin::load($plugin);
} catch (MissingPluginException) {
// The plugin is not installed.
}
}They are development dependencies, so they are skipped when the application is installed with composer install --no-dev. Run Bake with:
bin/cake bakeTo show the DebugKit toolbar, add the DebugKit.Toolbar component to your AppController (see the DebugKit documentation). Add it only when the plugin is loaded, so that the application still works without the development dependencies:
// src/Controller/AppController.php
namespace App\Controller;
use Cake\Controller\Controller;
use Cake\Core\CakePlugin;
class AppController extends Controller
{
/**
* Constructor. Adds the DebugKit toolbar when the plugin is loaded.
*
* @param \Cake\Network\CakeRequest|null $request Request object.
* @param \Cake\Network\CakeResponse|null $response Response object.
*/
public function __construct($request = null, $response = null)
{
if (CakePlugin::loaded('DebugKit')) {
$this->components[] = 'DebugKit.Toolbar';
}
parent::__construct($request, $response);
}
}The toolbar is only shown when debug is greater than 0.
Load the other plugins in config/bootstrap.php with CakePlugin::load() (or CakePlugin::loadAll()). CakePlugin::load() throws a MissingPluginException when the plugin is not installed, so wrap the plugins that are development dependencies as shown above.
Directory Layout
my_app/
├── bin/
│ └── cake the console
├── config/ configuration (core.php, bootstrap.php, routes.php, database.php, ...)
├── logs/ log files
├── plugins/ plugins installed by Composer
├── resources/
│ └── locales/ translation files
├── src/ PHP code (the App namespace)
│ ├── Console/ shells, tasks and bake templates
│ ├── Controller/
│ ├── Model/
│ └── View/ helpers
├── templates/ view templates (.php)
│ ├── Error/ error pages
│ ├── Pages/ pages of PagesController
│ ├── element/ elements (Flash/ for the flash messages)
│ ├── email/ email templates (html/, text/)
│ └── layout/ layouts (email/ for the email layouts)
├── tests/ tests (the App\Test namespace)
│ ├── Fixture/
│ └── TestCase/
├── tmp/ cache, sessions and test files
├── vendor/ Composer packages, including CakePHP
└── webroot/ document root (index.php, test.php, css/, js/, img/)webroot/index.php, webroot/test.php and bin/cake load config/define.php, which sets the root directory (ROOT), the application directory (APP_DIR, src), the document root (WWW_ROOT) and the Composer vendor directory (VENDORS). CakePHP finds the other directories by itself: config/, tests/, tmp/, logs/, templates/ and resources/locales/ are used when they exist. See CakePHP Folder Structure.
The legacy layout with an app/ directory (app/Config/, app/Controller/, app/View/, ...) is supported as well, but the skeleton only ships the modern layout.
Namespace
The classes of src/ are in the App namespace, which follows their directories: App\Controller\PostsController in src/Controller/PostsController.php, App\Model\Post in src/Model/Post.php. Composer loads them with PSR-4, App\ from src/ and App\Test\ from tests/, so a new class file needs no composer dump-autoload:
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Test\\": "tests/"
}
},config/core.php tells CakePHP the namespace, so that it finds the controllers, models, components, helpers, behaviors and shells of the application in it (see App.namespace):
Configure::write('App.namespace', 'App');The classes of CakePHP are in the Cake namespace and are imported with use statements, also in config/ and in the templates, which stay without a namespace:
// src/Controller/AppController.php
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
}The settings that name a class use ::class, e.g. 'renderer' => ExceptionRenderer::class in config/core.php. The names of CakePHP 2 ('File', 'Auth', 'DebugKit.Toolbar') keep working. See Namespaced Classes.
Configuration
| File | Contents |
|---|---|
config/core.php | Debug level, error and exception handling, the namespace of the application, caching, session, security salts. |
config/bootstrap.php | Cache configuration, plugin loading, logging and other application setup. |
config/routes.php | URL routing. |
config/database.php | Database connections (default, and test with APP_ENV=test). Copy it from database.php.default. |
config/email.php | Email transports and profiles. Copy it from email.php.default. |
config/acl.php, config/acl.ini.php | Configuration of IniAcl and PhpAcl. |
config/define.php | Path constants of the modern layout. |
config/Schema/ | Schema files of the DbAcl, i18n and session tables. |
See Configuration for the settings.
Environment Variables (.env)
Settings such as the debug level, security salt and database credentials are read from environment variables. As in Symfony, config/core.php loads them from .env files in config/ with symfony/dotenv, so they apply to web requests, bin/cake and tests:
// config/core.php
use Symfony\Component\Dotenv\Dotenv;
(new Dotenv())->usePutenv()->loadEnv(CONFIG . '.env');The files are loaded in this order and merged; a later file wins over an earlier one:
| File | Loaded | In Git |
|---|---|---|
config/.env | always (required) | committed: the defaults of the application |
config/.env.local | except when APP_ENV is test | ignored: your machine |
config/.env.{APP_ENV} (e.g. config/.env.test) | always | committed: the defaults of an environment |
config/.env.{APP_ENV}.local | always | ignored: your machine, for that environment |
- A variable that is set in the real environment (web server, shell, container, CI) is never overwritten, so real environment variables win over all files. The values are written to
$_SERVER,$_ENVandputenv(), soenv(),getenv(),$_SERVERand$_ENVreturn them. APP_ENVcomes from the real environment, otherwise from the files:config/.envsetsAPP_ENV=dev.vendor/bin/phpunit,bin/cake testandwebroot/test.phpsetAPP_ENV=testwhen it is not set (see Running Tests). In production, setAPP_ENV=prodas a real environment variable.config/.envmust exist; the skeleton ships it.config/.env.localand the other.localfiles hold only what differs on your machine, such asDATABASE_URLand the salts. Never put production secrets in a committed file.- Values do not need quotes unless they contain spaces. Inside double quotes,
$NAMEand${NAME}are replaced with the value of a variable; single-quote a value that contains$.
| Variable | Setting | Default |
|---|---|---|
APP_ENV | the environment: dev, test or prod; selects config/.env.{APP_ENV} | dev (from config/.env) |
APP_DEBUG | debug (0, 1, 2) | 0 |
SECURITY_SALT | Security.salt | value in config/core.php |
SECURITY_CIPHER_SEED | Security.cipherSeed (1 to 4294967295, legacy 'cipher' cookies only) | value in config/core.php |
COOKIE_KEY | Cookie.key, the CookieComponent key for the 'aes' type | null (Security.salt is used) |
DATABASE_URL | default connection in config/database.php (also test with APP_ENV=test), as a DSN | values in config/database.php |
MAILER_DSN | transport of the default profile in config/email.php, as a Symfony Mailer DSN | values in config/email.php |
- Unset or empty variables keep the defaults.
- In production, set
APP_ENV=prodand the other settings as real environment variables. DATABASE_URLis read byconfig/database.php.default, andMAILER_DSNbyconfig/email.php.default. If you createdconfig/database.phporconfig/email.phpfrom an earlier skeleton, copy the__construct()method into it (and remove theapplyEnv()method of the formerDB_*variables and the formerAPP_ENVbranch). Copy theAPP_ENVdefault oftests/bootstrap.php,bin/cakeandwebroot/test.phpfrom the skeleton too.
See Environment Variables for the syntax of the files and for applications that were not created from the skeleton.
Migrating from an Earlier Skeleton
Applications created from pieceofcake2/app 2.4.x load the .env files with josegonzalez/dotenv. To switch to the loading above:
composer remove josegonzalez/dotenvandcomposer require symfony/dotenv.- Replace the
.envloading at the top ofconfig/core.phpwith the two lines above, and theConfigure::write('debug', ...)line withConfigure::write('debug', env('APP_DEBUG') ?? 0);. - Move a root
.envtoconfig/: the root.envfiles are no longer read. Commitconfig/.envwith the defaults andAPP_ENV=dev(see the skeleton's file), and move the values of your machine and the secrets toconfig/.env.local. - Keep only what the tests change in
config/.env.test, and only what differs on your machine inconfig/.env.test.local: the files are merged now. - In
.gitignore, ignore/config/.env.localand/config/.env.*.localinstead ofconfig/.envand the root.envfiles. - In production, set
APP_ENV=prod(and the other settings) as real environment variables.
Database and Mailer DSNs
A DSN sets a whole connection in one string, as in CakePHP 3.x and later and in Symfony. The 'dsn' key of a connection in config/database.php and of a profile in config/email.php takes the DSN, and the skeleton sets it from the environment variables of Symfony (DATABASE_URL, MAILER_DSN):
// config/database.php
$this->default['dsn'] = env('DATABASE_URL');
if (env('APP_ENV') === 'test') {
$this->test = $this->default;
}
// config/email.php
$this->default['dsn'] = env('MAILER_DSN');There is one variable for the database, as in Symfony: DATABASE_URL is the database of the current environment, and both connections use it. In the tests (APP_ENV=test) it is the test database of config/.env.test.
# MySQL
DATABASE_URL="mysql://user:password@localhost:3306/my_app?encoding=utf8mb4"
# PostgreSQL
DATABASE_URL="postgres://user:password@localhost:5432/my_app?schema=public"
# SQLite: three slashes for a path relative to the working directory, four for an absolute path
DATABASE_URL="sqlite:////var/db/my_app.sqlite"
# SQLite in memory (e.g. in config/.env.test.local)
DATABASE_URL="sqlite:///:memory:"
# SQL Server
DATABASE_URL="sqlserver://user:password@localhost:1433/my_app?encoding=utf8"
# SMTP with STARTTLS on port 587
MAILER_DSN="smtp://user:password@smtp.example.com:587"
# SMTP over SSL/TLS (port 465)
MAILER_DSN="smtps://user:password@smtp.example.com"
# Local SMTP server for development (e.g. Mailpit)
MAILER_DSN="smtp://localhost:1025"
# PHP mail()
MAILER_DSN="sendmail://default"
# Do not send (Debug transport)
MAILER_DSN="null://null"- The scheme selects the datasource (
mysql,postgres,sqlite,sqlserver) or the transport (smtp/smtpsforSmtp,sendmail/nativeforMail,nullforDebug). - Query parameters set the other keys:
encoding,schema,prefix,persistent=trueorflags[1002]=...for a database,timeout,clientortls=1for SMTP. - Encode reserved characters of a password:
%40for@,%3Ffor?,%23for#,%25for%. For example,p@ss?isp%40ss%3F. - The keys in the DSN replace the same keys in
config/database.phporconfig/email.php, and the other keys are kept. The keys of the skeleton files are defaults: a DSN without a password (mysql://root@localhost/my_app) keeps'password' => 'password'of the file, so writemysql://root:@localhost/my_appfor an empty password. - An unset or empty variable is ignored, and the keys of the file are used.
- Plugins can add schemes (e.g.
ses+api://of an Amazon SES plugin) withCakeEmail::setDsnClassMap()andConnectionManager::setDsnClassMap()in their bootstrap. - See Database DSNs and Mailer DSN for all the rules.
Docker and CI
Pass the DSNs as environment variables. With Docker Compose:
services:
app:
environment:
DATABASE_URL: "mysql://my_app:secret@db:3306/my_app?encoding=utf8mb4"
MAILER_DSN: "smtp://mailpit:1025"
db:
image: mysql:8.4
environment:
MYSQL_DATABASE: my_app
MYSQL_USER: my_app
MYSQL_PASSWORD: secret
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
mailpit:
image: axllent/mailpitIn GitHub Actions, set DATABASE_URL to the test database for the test step. Real environment variables win over config/.env.test:
- name: Run tests
env:
DATABASE_URL: mysql://root:root@127.0.0.1:3306/test_app?encoding=utf8mb4
run: |
cp config/database.php.default config/database.php
vendor/bin/phpunitSee Continuous Integration for a complete workflow.
Generating the Security Salts
Generate Security.salt and Security.cipherSeed with a cryptographically secure random generator. These commands work in any POSIX shell on Linux and macOS (the cipher seed is only for the legacy 'cipher' cookie type; see cipherSeed and the 'cipher' Cookie Type):
# SECURITY_SALT: 64 hexadecimal characters
php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'
openssl rand -hex 32 # without PHP
# SECURITY_CIPHER_SEED: a number from 1 to 4294967295
php -r 'echo random_int(1, 4294967295), PHP_EOL;'
od -An -N4 -tu4 /dev/urandom | tr -dc 0-9; echo # without PHPTo append both to config/.env.local, which is ignored by Git (the last line wins when a variable is set twice):
php -r 'printf("SECURITY_SALT=\"%s\"\nSECURITY_CIPHER_SEED=\"%d\"\n", bin2hex(random_bytes(32)), random_int(1, 4294967295));' >> config/.env.localor without PHP:
printf 'SECURITY_SALT="%s"\nSECURITY_CIPHER_SEED="%s"\n' "$(openssl rand -hex 32)" "$(od -An -N4 -tu4 /dev/urandom | tr -dc 0-9)" >> config/.env.local- Keep the salt at least 32 characters long: it is the default key of
CookieComponent, and the'aes'cookie type rejects shorter keys. - Do not use a longer cipher seed: only 1 to 4294967295 is meaningful (see below).
- Use different values in each environment, and do not commit them. In production, set real environment variables.
- Changing
Security.saltlater logs users out, invalidates open forms, makes encrypted cookies unreadable, breaks data encrypted withSecurity::encrypt()without an explicit HMAC salt, and breaks passwords hashed withSimplePasswordHasher(bcrypt hashes are not affected). ChangingSecurity.cipherSeedonly breaks values encrypted withSecurity::cipher(), such as cookies of the default'cipher'type.
See Generating the Security Salts for details and for how to rotate the values safely.
cipherSeed and the 'cipher' Cookie Type
Security.cipherSeed is used only by Security::cipher(), that is, by the 'cipher' encryption type of CookieComponent, which is the default type and is insecure, also with a seed in the meaningful range. The fallback in config/core.php is an in-range example value; replace it all the same.
Use the 'aes' type with its own key of 32 random bytes or more instead. The skeleton sets Security.useOpenSsl to true, which 'aes' cookies need on PHP 8 (see Enable OpenSSL), and sets Cookie.type and Cookie.key in config/core.php, next to the salts, so no controller changes are needed:
Configure::write('Cookie.type', 'aes');
Configure::write('Cookie.key', env('COOKIE_KEY') ?: null);CookieComponent uses an explicit 'type'/'key' setting (or type() and $this->Cookie->key) first, then Cookie.type/Cookie.key, then 'cipher' and Security.salt. When COOKIE_KEY is unset, Cookie.key is null and the key is Security.salt; if the salt is shorter than 32 bytes, writing or reading an encrypted cookie throws a CakeException.
Generate the key with php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;' or openssl rand -hex 32, and set it as COOKIE_KEY in config/.env.local (or as a real environment variable in production). Switching an existing application from 'cipher' to 'aes' loses the existing cookie values once (for example, remember-me logins). See cipherSeed and the 'cipher' Cookie Type for how the seed is used, the impact, which option fits your application, and the migration steps.
Running the Application
For development, start PHP's built-in web server with:
bin/cake serverand open http://localhost:8765/. Use --port (-p) to choose another port.
In production, point the document root of your web server to webroot/, never to the project directory, which contains config/, logs/, tmp/, vendor/ and .env files. Set APP_ENV=prod and the other settings as real environment variables (see Environment Variables (.env)). The webroot/.htaccess file rewrites the requests to index.php for Apache with mod_rewrite. For Apache and nginx examples, see Web Server Configuration, and before you go live, Deployment.
Pages and Styles
The layouts, the home page, the error pages and the flash messages are ported from the CakePHP 5 application skeleton and use its stylesheets in webroot/css/:
| File | Description |
|---|---|
normalize.min.css | normalize.css 8.0.1 |
milligram.min.css | Milligram 1.4.1 |
fonts.css | The Raleway font, served from webroot/font/ (no request to a font service) |
cake.css | The application styles: layout, flash messages, forms, tables, paginator, error pages and debug output |
home.css | The styles of the home page only |
cake.generic.css | The stylesheet of the earlier skeleton. The templates no longer load it; it is kept for views that still use its classes (for example views baked with the legacy bake theme) and is loaded by the fallback layouts of the core |
Templates:
templates/layout/default.php: the top navigation bar and the content container. Whiledebugis 2, the SQL log is shown below the content.templates/layout/error.php: a minimal layout for the error pages in production.templates/layout/flash.php: the page rendered byController::flash().templates/element/Flash/:default,success,error,warningandinfoflash messages ($this->Flash->success('Saved.')and so on). Messages are escaped; pass['params' => ['escape' => false]]to output HTML. Click a message to hide it.
Views baked with pieceofcake2/bake use the same styles.
Home Page
templates/Pages/home.php is shown at / while debug is enabled. It checks the environment of the application: the PHP version and extensions (mbstring, intl, openssl, PDO and the driver of the default datasource), the tmp/ and logs/ directories, the cache, the database connection (with the error message when it fails), the security salt and cipher seed, and the DebugKit and Bake plugins. Replace it with your own page before turning debug off: in production it throws a NotFoundException. See Pages Controller.

On a phone:

Error Pages
When debug is 0, templates/Error/error400.php and error500.php render a short message in the error layout:


In debug mode, they use the default layout and show the stack trace, like the other error pages of CakePHP.
Flash Messages

Existing Applications
Applications created from an earlier version of the skeleton are not affected: they own their templates and webroot/css/. To use the new look, copy templates/layout/, templates/element/Flash/, templates/Error/, webroot/css/, webroot/font/ and webroot/img/cake.logo.svg into your application (or the .ctp templates of pieceofcake2/app-legacy, with Layouts/, Elements/ and Errors/). Views that you baked for cake.generic.css need to be baked again (or restyled), see Baked Views and the Legacy Theme.
Development
Running Tests
Put your test cases in tests/TestCase/ and your fixtures in tests/Fixture/, in the App\Test\TestCase and App\Test\Fixture namespaces (e.g. App\Test\TestCase\Controller\PostsControllerTest in tests/TestCase/Controller/PostsControllerTest.php).
vendor/bin/phpunit and bin/cake test run with APP_ENV=test by default: tests/bootstrap.php and bin/cake set it before CakePHP loads the .env files when it is not set (webroot/test.php, the browser test runner, does the same). So the tests load config/.env.test after config/.env, without config/.env.local. config/.env.test is committed and changes only what the tests need: DATABASE_URL is the test database (the default connection, which config/database.php copies to the test connection with APP_ENV=test) and MAILER_DSN is null://null. Set your test database in config/.env.test.local, which is ignored by Git, or as a real environment variable, which wins over the files, as CI does:
# config/.env.test.local
DATABASE_URL="mysql://my_app:secret@localhost:3306/test_my_app?encoding=utf8mb4"Your development DATABASE_URL stays in config/.env.local, which the tests do not load. A real environment variable wins over the files, so do not export your development DATABASE_URL or another APP_ENV in the shell where you run the tests: the fixtures drop and create tables.
Then run the tests with PHPUnit:
vendor/bin/phpunitor a single test case with the test shell:
bin/cake test app Controller/PostsControllerphpunit.xml.dist has two test suites:
| Suite | Directory | Run with |
|---|---|---|
App | tests/TestCase/, without EndToEnd/ | vendor/bin/phpunit (the default suite) |
EndToEnd | tests/TestCase/EndToEnd/ | vendor/bin/phpunit --testsuite EndToEnd |
vendor/bin/phpunit --testsuite App,EndToEnd runs both. defaultTestSuite="App" in phpunit.xml.dist keeps vendor/bin/phpunit fast and free of a browser. Delete that one attribute to make vendor/bin/phpunit run everything, as a Symfony application does.
See Testing for writing the tests.
End-to-End Tests
tests/TestCase/EndToEnd/ is for browser End-to-End tests with pieceofcake2/panther, which runs them the way Symfony Panther does: the application starts with PHP's built-in web server and headless Chrome drives it. To use them:
Install the package and a chromedriver that matches your Chrome:
bashcomposer require --dev "pieceofcake2/panther:^1.0.1" dbrekelmans/bdi vendor/bin/bdi detect driversUncomment the
ServerExtensioninphpunit.xml.dist. It keeps the web server and the browser running for the whole run; nothing starts until an End-to-End test runs.Write tests that extend
PieceOfCake2\Panther\TestSuite\PantherTestCaseintests/TestCase/EndToEnd/, and run them withvendor/bin/phpunit --testsuite EndToEnd.
The web server that Panther starts inherits the environment of PHPUnit, including APP_ENV=test. It loads config/.env.test (and config/.env.test.local) too, where DATABASE_URL (the default connection of the pages) is the test database, so the pages read the fixtures of the tests. No other setting is needed. Panther's PANTHER_APP_ENV is only needed to give the web server a different APP_ENV than the tests.
The tests are configured with Panther's PANTHER_* variables. phpunit.xml.dist sets PANTHER_ERROR_SCREENSHOT_DIR=./tmp/error-screenshots for the screenshots of the failed tests.
Set per-machine values, such as PANTHER_NO_HEADLESS=1 (to watch the browser), in your shell or in a phpunit.xml copy of phpunit.xml.dist. In CI, run the End-to-End tests in a separate job. See End-to-End Testing and the panther documentation for the variables, the test database and a GitHub Actions example.
Code Standards
Check your code against the CakePHP coding standard:
vendor/bin/phpcsphpcs.xml uses the CakePHP2Namespace standard of pieceofcake2/phpcs-cakephp2: the classes are declared in a namespace, in a file named after the class, and the classes that they use are imported with use statements. config/ keeps its global classes (DATABASE_CONFIG, EmailConfig and the schema classes). See CakePHP Coding Conventions.
Static Analysis
Run PHPStan (level 5) on src/ and config/:
vendor/bin/phpstan analyseThe configuration is in phpstan.neon and uses the pieceofcake2/phpstan-cakephp2 extension. See Static Analysis.
Origin
This package is based on the app/ directory of cakephp/cakephp (cakephp/cakephp on Packagist, last upstream version 2.10.24), originally by the CakePHP community (Cake Software Foundation, Inc.). It was extracted into a separate package and is maintained for CakePHP 2 on modern PHP by pieceofcake2. Changes from the original are listed in the commit history.
