Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
CakePHP
https://github.com/cakephp/cakephp
Admin
CakePHP is a rapid development framework for PHP that uses MVC patterns and conventions over
...
Tokens:
29,897
Snippets:
385
Trust Score:
7.5
Update:
1 week ago
Context
Skills
Chat
Benchmark
74.5
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# CakePHP CakePHP is a rapid development framework for PHP that uses well-known design patterns including MVC (Model-View-Controller), Associative Data Mapping, and Front Controller. Its primary goal is to provide a structured, convention-over-configuration foundation that enables PHP developers at all skill levels to build robust web applications quickly and without sacrificing flexibility. Version 5.x (current stable: 5.3.4) requires PHP 8.2+ and fully implements PSR-7, PSR-15, PSR-17, and PSR-18 standards throughout its HTTP layer. The framework ships as a single `cakephp/cakephp` Composer package that internally bundles over a dozen independently-usable sub-packages covering ORM/database, caching, collections, routing, HTTP client/server, console commands, internationalization, logging, validation, events, and utility classes. Each subsystem integrates seamlessly with the others through shared conventions and a global event system, while remaining usable standalone via Composer. The result is a full-stack toolkit capable of handling everything from RESTful API backends to complex server-rendered applications. --- ## Installation Install CakePHP via Composer. Use the official app skeleton to scaffold a new project, or require the package directly into an existing one. ```bash # New application (recommended) composer create-project --prefer-dist cakephp/app my_app # Add to existing project composer require cakephp/cakephp ``` --- ## ORM — Connecting to the Database `Cake\Datasource\ConnectionManager` registers named database connections referenced by all Table objects. Drivers exist for MySQL, PostgreSQL, SQLite, and SQL Server. ```php use Cake\Datasource\ConnectionManager; ConnectionManager::setConfig('default', [ 'className' => \Cake\Database\Connection::class, 'driver' => \Cake\Database\Driver\Mysql::class, 'host' => 'localhost', 'database' => 'my_db', 'username' => 'root', 'password' => 'secret', 'encoding' => 'utf8mb4', 'timezone' => 'UTC', 'cacheMetadata' => true, 'quoteIdentifiers' => false, ]); // Retrieve the connection and run a raw query $conn = ConnectionManager::get('default'); $stmt = $conn->execute('SELECT COUNT(*) AS total FROM articles WHERE published = :pub', ['pub' => 1], ['pub' => 'boolean']); $row = $stmt->fetch(\PDO::FETCH_ASSOC); echo $row['total']; // e.g. 42 ``` --- ## ORM — Table Objects and the Table Locator `Cake\ORM\Table` is the primary interface for a database table. `TableLocator` lazily instantiates and caches Table objects by alias, resolving them to `App\Model\Table\*Table` classes automatically. ```php use Cake\ORM\Locator\TableLocator; $locator = new TableLocator(); $articles = $locator->get('Articles'); // resolves App\Model\Table\ArticlesTable // Custom Table class with associations // src/Model/Table/ArticlesTable.php namespace App\Model\Table; use Cake\ORM\RulesChecker; use Cake\ORM\Table; use Cake\Validation\Validator; class ArticlesTable extends Table { public function initialize(array $config): void { parent::initialize($config); $this->setTable('articles'); $this->setPrimaryKey('id'); $this->belongsTo('Users'); $this->hasMany('Comments'); $this->belongsToMany('Tags'); } public function validationDefault(Validator $validator): Validator { $validator ->notEmptyString('title', 'A title is required') ->maxLength('title', 255) ->notEmptyString('body'); return $validator; } public function buildRules(RulesChecker $rules): RulesChecker { $rules->add($rules->existsIn('user_id', 'Users')); return $rules; } } ``` --- ## ORM — Querying Data (SelectQuery / Query Builder) `Table::find()` returns a `SelectQuery` that is lazily executed. The fluent API supports conditions, ordering, limiting, eager-loading associations, custom finders, and map/reduce pipelines. ```php use Cake\ORM\Locator\LocatorAwareTrait; $articles = $locator->get('Articles'); // Basic find with conditions, ordering, and limit $recentPublished = $articles->find() ->select(['id', 'title', 'published_at', 'Users.username']) ->contain(['Users', 'Tags']) ->where([ 'Articles.published' => true, 'Articles.published_at >=' => new \DateTime('-30 days'), ]) ->orderBy(['Articles.published_at' => 'DESC']) ->limit(10) ->all(); foreach ($recentPublished as $article) { printf("[%s] %s by %s\n", $article->published_at, $article->title, $article->user->username); } // Dynamic finder (auto-generated from field name) $article = $articles->findBySlug('my-great-post')->firstOrFail(); // Custom finder defined on ArticlesTable // public function findPublished(SelectQuery $query, array $options): SelectQuery // { return $query->where(['Articles.published' => true]); } $published = $articles->find('published')->limit(5)->all(); // Aggregate $total = $articles->find()->where(['user_id' => 7])->count(); ``` --- ## ORM — Saving and Deleting Entities `Table::newEntity()` / `Table::patchEntity()` marshals request data (including nested associations) into entity objects. `Table::save()` persists the entity and fires lifecycle callbacks. `Table::delete()` removes a record after firing `beforeDelete` / `afterDelete`. ```php $articles = $locator->get('Articles'); // Create a new article with nested associations $data = [ 'title' => 'My Great Post', 'body' => 'Full article content here.', 'user_id' => 1, 'tags' => ['_ids' => [3, 5]], 'comments' => [ ['body' => 'First comment!'], ], ]; $article = $articles->newEntity($data, ['associated' => ['Tags', 'Comments']]); if ($articles->save($article, ['associated' => ['Tags', 'Comments']])) { echo 'Saved with ID: ' . $article->id; } else { // Validation / rules errors print_r($article->getErrors()); } // Update an existing entity $article = $articles->get(42, contain: ['Tags']); $articles->patchEntity($article, ['title' => 'Updated Title']); $articles->saveOrFail($article); // Delete $article = $articles->get(42); if (!$articles->delete($article)) { echo 'Delete failed'; } // Bulk update (no events/callbacks fired) $articles->updateAll(['published' => true], ['user_id' => 1]); // Bulk delete $articles->deleteAll(['published' => false, 'created <' => new \DateTime('-1 year')]); ``` --- ## Database — Low-Level Query Builder `Connection::selectQuery()` / `updateQuery()` / `insertQuery()` / `deleteQuery()` expose a SQL-level query builder with a rich expression system, aggregate functions, and automatic type casting that bypasses the ORM layer. ```php use Cake\Database\Connection; use Cake\Database\Driver\Mysql; $conn = new Connection(['driver' => Mysql::class, 'database' => 'test', 'username' => 'root', 'password' => '']); // Fluent SELECT with complex WHERE $query = $conn->selectQuery() ->select(['id', 'title', 'view_count']) ->from('articles') ->where(function (\Cake\Database\Expression\QueryExpression $exp) { return $exp ->eq('published', true) ->gt('view_count', 100) ->notEq('spam', true); }) ->orderBy(['view_count' => 'DESC']) ->limit(20); foreach ($query as $row) { echo $row['title'] . ' — ' . $row['view_count'] . " views\n"; } // Aggregates with func() $stats = $conn->selectQuery() ->select([ 'total' => $query->func()->count('*'), 'avg_views' => $query->func()->avg('view_count'), ]) ->from('articles') ->where(['published' => true]); $result = $stats->execute()->fetch(\PDO::FETCH_ASSOC); echo "Total: {$result['total']}, Avg views: {$result['avg_views']}"; // INSERT / UPDATE / DELETE helpers $conn->insert('articles', ['title' => 'Test', 'body' => 'Body', 'created' => new \DateTime()], ['created' => 'datetime']); $conn->update('articles', ['title' => 'New Title'], ['id' => 1]); $conn->delete('articles', ['created <' => new \DateTime('-1 year')], ['created' => 'datetime']); ``` --- ## Cache — Read/Write/Remember `Cake\Cache\Cache` is a static façade over pluggable backends (File, APCu, Redis, Memcached, Array). Multiple named pools can be configured, each with independent TTL, prefix, and fallback settings. ```php use Cake\Cache\Cache; use Cake\Cache\Engine\RedisEngine; use Cake\Cache\Engine\FileEngine; // Configure multiple named pools Cache::setConfig('default', [ 'className' => FileEngine::class, 'duration' => '+1 hour', 'path' => CACHE, 'prefix' => 'app_', ]); Cache::setConfig('long_term', [ 'className' => RedisEngine::class, 'duration' => '+1 week', 'host' => '127.0.0.1', 'port' => 6379, 'prefix' => 'lt_', 'fallback' => 'default', // fall back to 'default' if Redis is unavailable ]); // Write / Read / Delete Cache::write('site_config', ['theme' => 'dark', 'lang' => 'en'], 'long_term'); $config = Cache::read('site_config', 'long_term'); // ['theme' => 'dark', 'lang' => 'en'] Cache::delete('site_config', 'long_term'); // Read-through caching (returns cached value or executes callback and stores result) $articles = Cache::remember('homepage_articles', function () use ($locator) { return $locator->get('Articles') ->find('published') ->orderBy(['created' => 'DESC']) ->limit(5) ->toArray(); }, 'default'); // Batch operations Cache::writeMany(['key1' => 'val1', 'key2' => 'val2'], 'default'); $results = Cache::readMany(['key1', 'key2'], 'default'); // Atomic counter operations (not supported by FileEngine) Cache::write('page_views', 0, 'default'); Cache::increment('page_views', 1, 'default'); // returns 1 Cache::decrement('page_views', 1, 'default'); // returns 0 // Group-based cache invalidation Cache::setConfig('posts_cache', ['className' => FileEngine::class, 'duration' => '+1 day', 'groups' => ['posts']]); Cache::clearGroup('posts', 'posts_cache'); ``` --- ## Collection — Functional Data Transformation `Cake\Collection\Collection` wraps any iterable and provides a rich, lazy-evaluated functional API for transforming, filtering, grouping, and aggregating datasets. ```php use Cake\Collection\Collection; $users = new Collection([ ['name' => 'Alice', 'age' => 32, 'dept' => 'Engineering'], ['name' => 'Bob', 'age' => 24, 'dept' => 'Marketing'], ['name' => 'Carol', 'age' => 28, 'dept' => 'Engineering'], ['name' => 'Dave', 'age' => 45, 'dept' => 'Marketing'], ]); // filter + map + sortBy chain (lazy — no iteration until toArray/first/etc.) $result = $users ->filter(fn($u) => $u['age'] >= 25) ->map(fn($u) => array_merge($u, ['senior' => $u['age'] > 30])) ->sortBy('age', SORT_ASC) ->toArray(); // groupBy $byDept = (new Collection($users))->groupBy('dept')->toArray(); // ['Engineering' => [...], 'Marketing' => [...]] // indexBy — key by a unique field $byName = (new Collection($users))->indexBy('name')->toArray(); // ['Alice' => [...], 'Bob' => [...], ...] // Aggregates $avg = (new Collection($users))->avg('age'); // 32.25 $sum = (new Collection($users))->sumOf('age'); // 129 $max = (new Collection($users))->max('age'); // ['name' => 'Dave', ...] // countBy $counts = (new Collection($users))->countBy('dept')->toArray(); // ['Engineering' => 2, 'Marketing' => 2] // extract dot-path values $names = (new Collection($users))->extract('name')->toList(); // ['Alice', 'Bob', 'Carol', 'Dave'] // combine — build key=>value map $nameToAge = (new Collection($users))->combine('name', 'age')->toArray(); // ['Alice' => 32, 'Bob' => 24, ...] // nest flat data into a tree $categories = new Collection([ ['id' => 1, 'parent_id' => null, 'label' => 'Root'], ['id' => 2, 'parent_id' => 1, 'label' => 'Child A'], ['id' => 3, 'parent_id' => 1, 'label' => 'Child B'], ]); $tree = $categories->nest('id', 'parent_id')->toArray(); ``` --- ## Validation `Cake\Validation\Validator` builds chainable validation rule sets that can be applied to arbitrary arrays of data. Used directly or integrated via `Table::validationDefault()`. ```php use Cake\Validation\Validator; $validator = (new Validator()) ->requirePresence('email') ->email('email', false, 'Please enter a valid email address') ->notEmptyString('email') ->requirePresence('username') ->alphaNumeric('username', 'Username must be alphanumeric') ->lengthBetween('username', [3, 20], 'Username must be 3–20 characters') ->requirePresence('password') ->minLength('password', 8, 'Password must be at least 8 characters') ->add('age', 'validAge', [ 'rule' => ['range', 18, 120], 'message' => 'You must be at least 18 years old', ]); $data = ['email' => 'not-an-email', 'username' => 'ab', 'password' => 'short', 'age' => 15]; $errors = $validator->validate($data); // $errors = [ // 'email' => ['email' => 'Please enter a valid email address'], // 'username' => ['lengthBetween' => 'Username must be 3–20 characters'], // 'password' => ['minLength' => 'Password must be at least 8 characters'], // 'age' => ['validAge' => 'You must be at least 18 years old'], // ] if ($errors) { foreach ($errors as $field => $fieldErrors) { foreach ($fieldErrors as $rule => $message) { echo "[$field] $message\n"; } } } ``` --- ## HTTP Client `Cake\Http\Client` is a PSR-18-compatible HTTP client supporting GET, POST, PUT, PATCH, DELETE, JSON, file uploads, authentication (Basic, Digest, OAuth), cookie management, and scoped client instances. ```php use Cake\Http\Client; $http = new Client([ 'host' => 'api.example.com', 'scheme' => 'https', 'headers' => ['Accept' => 'application/json'], 'auth' => ['type' => 'bearer', 'token' => 'my-api-token'], ]); // GET with query parameters $response = $http->get('/v1/articles', ['status' => 'published', 'page' => 2]); echo $response->getStatusCode(); // 200 $data = $response->getJson(); // decoded JSON as array // POST JSON body $response = $http->post('/v1/articles', json_encode([ 'title' => 'New Article', 'body' => 'Content here', ]), ['type' => 'json']); if ($response->isOk()) { $created = $response->getJson(); echo 'Created ID: ' . $created['id']; } // File upload (multipart/form-data) $response = $http->post('/v1/uploads', [ 'title' => 'My File', 'file' => ['file' => fopen('/path/to/image.jpg', 'r'), 'mimetype' => 'image/jpeg', 'filename' => 'image.jpg'], ]); // PUT / PATCH / DELETE $http->put('/v1/articles/1', json_encode(['title' => 'Updated']), ['type' => 'json']); $http->patch('/v1/articles/1', ['published' => true]); $http->delete('/v1/articles/1'); // Error handling $response = $http->get('/v1/missing'); if (!$response->isOk()) { echo 'Error: ' . $response->getStatusCode() . ' ' . $response->getStringBody(); } ``` --- ## HTTP Server and Middleware `Cake\Http\Server` handles incoming PSR-7 server requests through a PSR-15 middleware queue before dispatching to the application. Middleware classes receive the request, can short-circuit, or pass to `$handler->handle()`. ```php // webroot/index.php require dirname(__DIR__) . '/vendor/autoload.php'; use App\Application; use Cake\Http\Server; $server = new Server(new Application()); $server->emit($server->run()); // src/Application.php namespace App; use Cake\Core\HttpApplicationInterface; use Cake\Http\MiddlewareQueue; use Cake\Http\Middleware\BodyParserMiddleware; use Cake\Http\Middleware\CsrfProtectionMiddleware; use Cake\Http\Middleware\HttpsEnforcerMiddleware; use Cake\Routing\Middleware\RoutingMiddleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; class Application implements HttpApplicationInterface { public function bootstrap(): void { // Load plugins, configure services } public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue { $middlewareQueue ->add(new HttpsEnforcerMiddleware()) // force HTTPS ->add(new BodyParserMiddleware()) // parse JSON/XML bodies ->add(new CsrfProtectionMiddleware()) // CSRF tokens ->add(new RoutingMiddleware($this)); // route matching return $middlewareQueue; } public function handle(ServerRequestInterface $request): ResponseInterface { // Fallback handler — normally the routing middleware dispatches to a controller return new \Cake\Http\Response(['body' => 'Hello!', 'status' => 200]); } } ``` --- ## Routing `Cake\Routing\Router` / `RouteBuilder` connect URL patterns to controller actions. Routes support prefixes, named parameters, REST resources, scoped groups, and URL generation. ```php // config/routes.php use Cake\Routing\Route\DashedRoute; use Cake\Routing\RouteBuilder; /** @var RouteBuilder $routes */ $routes->setRouteClass(DashedRoute::class); // Simple named route $routes->connect('/', ['controller' => 'Pages', 'action' => 'home'], ['_name' => 'home']); // Static page $routes->connect('/about', ['controller' => 'Pages', 'action' => 'about']); // RESTful resource routes (generates index/view/add/edit/delete) $routes->resources('Articles'); // Scoped prefix for admin area $routes->prefix('admin', function (RouteBuilder $builder): void { $builder->resources('Users'); $builder->connect('/dashboard', ['controller' => 'Dashboard', 'action' => 'index']); }); // Scoped plugin routes $routes->plugin('ContactManager', function (RouteBuilder $builder): void { $builder->resources('Contacts'); }); // Generate URLs (in controllers or views) use Cake\Routing\Router; $url = Router::url(['controller' => 'Articles', 'action' => 'view', 42]); // => '/articles/view/42' $adminUrl = Router::url(['prefix' => 'admin', 'controller' => 'Users', 'action' => 'index']); // => '/admin/users' $namedUrl = Router::url(['_name' => 'home']); // => '/' ``` --- ## Controllers `Cake\Controller\Controller` receives a dispatched `ServerRequest`, calls the matching action method, interacts with Table objects (via `LocatorAwareTrait`), and returns a `Response`. Lifecycle callbacks — `beforeFilter`, `beforeRender`, `afterFilter` — hook into the request cycle. ```php namespace App\Controller; use Cake\Event\EventInterface; use Cake\Http\Exception\NotFoundException; class ArticlesController extends \Cake\Controller\Controller { public function initialize(): void { parent::initialize(); $this->loadComponent('Flash'); $this->loadComponent('FormProtection'); } public function beforeFilter(EventInterface $event): void { parent::beforeFilter($event); // Runs before every action in this controller } public function index(): void { $articles = $this->getTableLocator()->get('Articles'); $this->set('articles', $articles->find('published')->all()); } public function view(int $id): void { $articles = $this->getTableLocator()->get('Articles'); $article = $articles->find()->where(['id' => $id])->contain(['Users', 'Comments', 'Tags'])->first(); if (!$article) { throw new NotFoundException('Article not found'); } $this->set(compact('article')); } public function add(): ?\Cake\Http\Response { $articles = $this->getTableLocator()->get('Articles'); $article = $articles->newEmptyEntity(); if ($this->getRequest()->is('post')) { $article = $articles->patchEntity($article, $this->getRequest()->getData()); if ($articles->save($article)) { $this->Flash->success('Article saved!'); return $this->redirect(['action' => 'index']); } $this->Flash->error('Could not save article.'); } $this->set(compact('article')); return null; } // JSON API action public function apiIndex(): \Cake\Http\Response { $articles = $this->getTableLocator()->get('Articles') ->find('published')->limit(20)->all(); return $this->getResponse() ->withType('application/json') ->withStringBody(json_encode($articles->toList())); } } ``` --- ## Events `Cake\Event\EventManager` provides a pub/sub system used throughout the framework (Table lifecycle hooks, Controller callbacks, etc.) and available for application-level decoupled communication. ```php use Cake\Event\Event; use Cake\Event\EventDispatcherTrait; use Cake\Event\EventManager; class OrderService { use EventDispatcherTrait; public function placeOrder(array $orderData): array { // … business logic … $order = ['id' => 99, 'total' => 150.00, 'items' => $orderData]; // Dispatch a domain event $event = new Event('Orders.afterPlace', $this, ['order' => $order]); $this->getEventManager()->dispatch($event); return $order; } } $service = new OrderService(); // Register a listener on the instance-level manager $service->getEventManager()->on('Orders.afterPlace', function (Event $event): void { $order = $event->getData('order'); // Send confirmation email, update stock, etc. echo "Order #{$order['id']} placed for \${$order['total']}\n"; }); // Register a listener on the global manager (receives all events) EventManager::instance()->on('Orders.afterPlace', function (Event $event): void { // log to audit trail }); $service->placeOrder([['sku' => 'ABC', 'qty' => 2]]); // Output: Order #99 placed for $150.00 ``` --- ## Logging `Cake\Log\Log` routes PSR-3 log messages to one or more named backends filtered by level and/or scope. Built-in engines include `FileLog` and `SyslogLog`; any PSR-3 logger is accepted. ```php use Cake\Log\Log; use Cake\Log\Engine\SyslogLog; Log::setConfig('app', [ 'className' => 'File', 'path' => '/var/log/myapp/', 'levels' => ['notice', 'info', 'debug'], 'file' => 'app', ]); Log::setConfig('errors', [ 'className' => SyslogLog::class, 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], ]); // Scoped logging — only the 'payments' engine receives this Log::setConfig('payments', [ 'className' => 'File', 'path' => '/var/log/myapp/', 'levels' => ['info', 'warning', 'error'], 'scopes' => ['payments'], 'file' => 'payments', ]); Log::info('User logged in', ['scope' => ['auth'], 'user_id' => 42]); Log::warning('Suspicious login attempt', ['scope' => ['auth', 'security'], 'ip' => '1.2.3.4']); Log::error('Payment gateway timeout', ['scope' => ['payments'], 'amount' => 99.99]); Log::debug('Query executed', ['sql' => 'SELECT ...', 'time' => 0.003]); ``` --- ## Internationalization (I18n) `Cake\I18n\I18n` manages locale selection and translation bundles. The `__()` helper function translates strings using ICU message format, supporting pluralization, number, date, and currency formatting. ```php use Cake\I18n\I18n; use Cake\I18n\Number; // Set locale globally I18n::setLocale('fr_FR'); // Basic translation (looks up locale/fr_FR/default.po) echo __('Hello world'); // Bonjour le monde // ICU message format with placeholders echo __('Hi {0,string}, your balance is {1,number,currency}', ['Alice', 1354.37]); // Hi Alice, your balance is 1 354,37 € // Pluralization echo __n('{0} comment', '{0} comments', 3, [3]); // 3 comments // Domain-based translation (locale/fr_FR/animals.po) echo __d('animals', 'Dog'); // Chien // Custom in-memory translator use Cake\I18n\Package; I18n::translator('ui', 'es_ES', function () { $pkg = new Package('default', 'default'); $pkg->setMessages(['Save' => 'Guardar', 'Cancel' => 'Cancelar']); return $pkg; }); I18n::setLocale('es_ES'); echo __d('ui', 'Save'); // Guardar // Number formatting echo Number::format(1234567.89); // 1,234,567.89 (en_US) echo Number::currency(99.5, 'EUR'); // €99.50 echo Number::toReadableSize(1048576); // 1 MB ``` --- ## Console Commands `Cake\Console\CommandRunner` dispatches CLI arguments to `BaseCommand` subclasses. Commands define their own option/argument parsers and use `ConsoleIo` for styled output. ```php // bin/cake.php entry point #!/usr/bin/env php <?php require dirname(__DIR__) . '/vendor/autoload.php'; use App\Application; use Cake\Console\CommandRunner; exit((new CommandRunner(new Application(), 'cake'))->run($argv)); // src/Command/SendNewsletterCommand.php namespace App\Command; use Cake\Console\Arguments; use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; class SendNewsletterCommand extends BaseCommand { protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { return $parser ->setDescription('Send newsletter to subscribers.') ->addArgument('edition', ['required' => true, 'help' => 'Newsletter edition slug']) ->addOption('dry-run', ['boolean' => true, 'default' => false, 'help' => 'Preview without sending']) ->addOption('limit', ['default' => 100, 'help' => 'Max recipients per run']); } public function execute(Arguments $args, ConsoleIo $io): ?int { $edition = $args->getArgument('edition'); $dryRun = $args->getOption('dry-run'); $limit = (int)$args->getOption('limit'); $io->info("Preparing newsletter: <info>{$edition}</info>"); if ($dryRun) { $io->warning('DRY RUN — no emails will be sent.'); } // Simulate sending $io->out("Sending to up to {$limit} subscribers..."); $io->success("Newsletter '{$edition}' dispatched successfully."); return static::CODE_SUCCESS; } } // Register in Application::console() // $commands->add('send_newsletter', \App\Command\SendNewsletterCommand::class); // Run: php bin/cake.php send_newsletter weekly-digest --limit=50 ``` --- ## Core Configuration (Configure) `Cake\Core\Configure` is a global key-value store for application configuration, loaded from PHP config files. It supports dot-notation paths and multiple named engines. ```php use Cake\Core\Configure; use Cake\Core\Configure\Engine\PhpConfig; // Register a config engine pointing at /config/ Configure::config('default', new PhpConfig(CONFIG)); // Load config/app.php (merges array returned from file) Configure::load('app', 'default', false); // Load an environment-specific overlay (config/app_local.php) Configure::load('app_local', 'default'); // Read and write using dot-notation Configure::write('App.debug', false); Configure::write('Email.transport.default', ['host' => 'smtp.example.com', 'port' => 587]); $debug = Configure::read('App.debug'); // false $smtpHost = Configure::read('Email.transport.default.host'); // 'smtp.example.com' $missing = Configure::read('App.nonexistent', 'fallback'); // 'fallback' // Check and delete if (Configure::check('App.debug')) { Configure::delete('App.debug'); } // Dump current in-memory config back to a file Configure::dump('runtime_config', 'default', ['App', 'Email']); ``` --- ## Utility Classes ### Hash — Array DSL extraction ```php use Cake\Utility\Hash; $data = [ ['User' => ['id' => 1, 'name' => 'Alice', 'age' => 32]], ['User' => ['id' => 2, 'name' => 'Bob', 'age' => 17]], ['User' => ['id' => 3, 'name' => 'Carol', 'age' => 25]], ]; // Extract names of adults $adults = Hash::extract($data, '{n}.User[age>=18].name'); // ['Alice', 'Carol'] // Combine into id => name map $map = Hash::combine($data, '{n}.User.id', '{n}.User.name'); // [1 => 'Alice', 2 => 'Bob', 3 => 'Carol'] // Merge deeply nested arrays $merged = Hash::merge(['a' => ['x' => 1]], ['a' => ['y' => 2]]); // ['a' => ['x' => 1, 'y' => 2]] ``` ### Inflector — String manipulation ```php use Cake\Utility\Inflector; echo Inflector::pluralize('Article'); // Articles echo Inflector::singularize('People'); // Person echo Inflector::camelize('my_field'); // MyField echo Inflector::underscore('MyField'); // my_field echo Inflector::dasherize('MyField'); // my-field echo Inflector::humanize('my_field'); // My Field echo Inflector::tableize('Article'); // articles echo Inflector::classify('articles'); // Article ``` ### Security — Encryption ```php use Cake\Utility\Security; $key = Security::getSalt(); // or provide your own 256-bit key $plaintext = 'sensitive data'; $encrypted = Security::encrypt($plaintext, $key); $decrypted = Security::decrypt($encrypted, $key); // $decrypted === 'sensitive data' $hash = Security::hash($plaintext, 'sha256', true); // salted SHA-256 ``` --- ## Summary CakePHP is best suited for building full-stack web applications and JSON/REST APIs where developer productivity, conventions, and a rich built-in ecosystem are priorities. Its ORM layer — with datamapper entities, association loading, lifecycle callbacks, and rules checking — handles everything from simple CRUD to complex multi-table operations. The HTTP layer's PSR compliance makes it easy to adopt any PSR-7/PSR-15 middleware, while the built-in client handles outbound API calls. The Collection and Hash utilities eliminate boilerplate for data transformation, and the event system enables clean separation of concerns across the application. Integration patterns fall into two broad categories: convention-based full-framework usage (MVC with the app skeleton, automatic class discovery, and plugin system) and standalone sub-package usage (e.g., using only `cakephp/orm`, `cakephp/cache`, or `cakephp/collection` in non-CakePHP projects via their individual Composer packages). The framework's static service locators (`Cache`, `Log`, `ConnectionManager`, `Configure`) make it straightforward to wire up in legacy codebases, while the PSR interfaces throughout ensure compatibility with container-managed, test-friendly injection patterns. Comprehensive PHPStan level-8 type coverage and PHPUnit integration make testing first-class throughout the stack.