diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d190130f40..d25ecf973b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,8 +3,6 @@ name: CI
on:
push:
branches:
- - '5.x'
- - '5.next'
- '6.x'
pull_request:
branches:
@@ -21,10 +19,8 @@ jobs:
fail-fast: false
matrix:
include:
- - php-version: '8.2'
- dependencies: 'lowest'
- php-version: '8.5'
- dependencies: 'highest'
+ dependencies: 'lowest'
- php-version: '8.5'
dependencies: 'highest'
@@ -63,7 +59,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
- php-version: '8.2'
+ php-version: '8.5'
extensions: mbstring, intl
coverage: none
tools: cs2pr, phpstan:2.3
diff --git a/.htaccess b/.htaccess
index 54b08e82e1..4e2d24528d 100644
--- a/.htaccess
+++ b/.htaccess
@@ -7,6 +7,6 @@
RewriteEngine on
RewriteRule ^(\.well-known/.*)$ $1 [L]
- RewriteRule ^$ webroot/ [L]
- RewriteRule (.*) webroot/$1 [L]
+ RewriteRule ^$ public/ [L]
+ RewriteRule (.*) public/$1 [L]
diff --git a/README.md b/README.md
index 5a614fa263..499aa499f6 100644
--- a/README.md
+++ b/README.md
@@ -1,32 +1,37 @@
# CakePHP Application Skeleton
-
+
[](https://packagist.org/packages/cakephp/app)
[](https://github.com/phpstan/phpstan)
-A skeleton for creating applications with [CakePHP](https://cakephp.org) 5.x.
+A skeleton for creating applications with [CakePHP](https://cakephp.org) 6.x, requiring PHP 8.5 or higher.
+
+This branch tracks CakePHP 6 development and allows development dependencies while preferring stable packages.
+Migrations is tracked on `6.x-dev`. Installation and attribute routing currently depend on
+[Migrations 6 compatibility](https://github.com/cakephp/migrations/pull/1121) and the
+[repeated route attribute fix](https://github.com/cakephp/cakephp/pull/19663).
The framework source code can be found here: [cakephp/cakephp](https://github.com/cakephp/cakephp).
## Installation
1. Download [Composer](https://getcomposer.org/doc/00-intro.md) or update `composer self-update`.
-2. Run `php composer.phar create-project --prefer-dist cakephp/app [app_name]`.
+2. Run `php composer.phar create-project --prefer-dist cakephp/app myapp "6.x-dev"`.
If Composer is installed globally, run
```bash
-composer create-project --prefer-dist cakephp/app
+composer create-project --prefer-dist cakephp/app myapp "6.x-dev"
```
In case you want to use a custom app dir name (e.g. `/myapp/`):
```bash
-composer create-project --prefer-dist cakephp/app myapp
+composer create-project --prefer-dist cakephp/app /myapp "6.x-dev"
```
-You can now either use your machine's webserver to view the default home page, or start
-up the built-in webserver with:
+Configure your webserver's document root to the application's `public/` directory,
+which contains the front controller and static assets. You can also start the built-in webserver with:
```bash
bin/cake server -p 8765
@@ -36,7 +41,7 @@ Then visit `http://localhost:8765` to see the welcome page.
## Demo app
-Check out the [5.x-demo branch](https://github.com/cakephp/app/tree/5.x-demo), which contains demo migrations and a seeder.
+The [5.x-demo branch](https://github.com/cakephp/app/tree/5.x-demo) is a CakePHP 5 example containing demo migrations and a seeder.
See the [README](https://github.com/cakephp/app/blob/5.x-demo/README.md) on how to get it running.
## Update
@@ -51,6 +56,27 @@ Read and edit the environment specific `config/app_local.php` and set up the
`'Datasources'` and any other configuration relevant for your application.
Other environment agnostic settings can be changed in `config/app.php`.
+The test suite uses PHPUnit 13.
+
+## Routing
+
+Routes are declared explicitly using [PHP attributes](https://book.cakephp.org/6.x/development/attribute-routing.html)
+on controller actions. The home page and `/pages/*` use `#[Get]` attributes on `PagesController::display()`.
+Catchall routes are not enabled: add attributes to new controllers, including controllers generated by Bake,
+or connect explicit routes in `config/routes.php` or `Application::routes()`.
+Attribute routes are bootstrapped in `Application::routes()`. The included `config/routes.php`
+contains a commented example of conventional routing and is loaded before the attribute routes.
+You can delete this file if you only use attribute routing.
+Controllers inherit `DashedRoute` from the `#[RouteClass]` attribute on `AppController`.
+
+The attribute resolver scans application controllers, including nested controller directories.
+Attribute metadata uses the `_cake_attributes_` PHP cache in production and is not persistently cached in debug mode.
+Clear the metadata cache after deploying controller or route changes:
+
+```bash
+bin/cake cache clear _cake_attributes_
+```
+
## Layout
The app skeleton uses [Milligram](https://milligram.io/) (v1.3) minimalist CSS
diff --git a/composer.json b/composer.json
index 9ec38086ab..86437a459d 100644
--- a/composer.json
+++ b/composer.json
@@ -5,18 +5,18 @@
"type": "project",
"homepage": "https://cakephp.org",
"require": {
- "php": ">=8.2",
- "cakephp/cakephp": "5.4.*",
- "cakephp/migrations": "^5.0",
+ "php": ">=8.5",
+ "cakephp/cakephp": "^6.0",
+ "cakephp/migrations": "6.x-dev",
"cakephp/plugin-installer": "^2.0",
"mobiledetect/mobiledetectlib": "^4.8.03"
},
"require-dev": {
- "cakephp/bake": "^3.6",
- "cakephp/cakephp-codesniffer": "^5.3",
- "cakephp/debug_kit": "^5.2",
+ "cakephp/bake": "^4.0",
+ "cakephp/cakephp-codesniffer": "^6.0",
+ "cakephp/debug_kit": "^6.0",
"josegonzalez/dotenv": "^4.0",
- "phpunit/phpunit": "^11.5.3 || ^12.1.3 || ^13.0"
+ "phpunit/phpunit": "^13.0"
},
"suggest": {
"cakephp/repl": "Console tools for a REPL interface for CakePHP applications.",
@@ -53,5 +53,7 @@
"cs-check": "phpcs --colors -p",
"cs-fix": "phpcbf --colors -p",
"test": "phpunit --colors=always"
- }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
}
diff --git a/config/app.php b/config/app.php
index 9876cf421c..9ab30f5b39 100644
--- a/config/app.php
+++ b/config/app.php
@@ -1,6 +1,7 @@
env('APP_DEFAULT_TIMEZONE', 'UTC'),
'base' => false,
'dir' => 'src',
- 'webroot' => 'webroot',
+ 'webroot' => 'public',
'wwwRoot' => WWW_ROOT,
//'baseUrl' => env('SCRIPT_NAME'),
'fullBaseUrl' => env('APP_FULL_BASE_URL', false),
@@ -133,6 +134,42 @@
'duration' => '+1 years',
'url' => env('CACHE_CAKEMODEL_URL', null),
],
+
+ // Attribute metadata is cached indefinitely in production. Clear at deploy time.
+ '_cake_attributes_' => [
+ 'className' => PhpEngine::class,
+ 'prefix' => 'myapp_attributes_',
+ 'path' => CACHE . 'attributes' . DS,
+ 'duration' => 0,
+ ],
+ ],
+
+ /*
+ * Configure attribute discovery and metadata caching.
+ * The default configuration scans application controllers and is used by attribute routing.
+ *
+ * Options:
+ *
+ * - `paths` - string[] - File glob patterns relative to `basePath`; `**` scans recursively.
+ * - `basePath` - string - Absolute base directory. Defaults to APP.
+ * - `excludePaths` - string[] - Glob patterns to skip. Defaults to [].
+ * - `excludeAttributes` - string[] - Fully qualified attribute classes to ignore. Defaults to [].
+ * - `cache` - string|false - Cache configuration name, or false to disable caching.
+ * - `validateFiles` - bool - Re-scan when cached files change. Defaults to false.
+ * Enable for development when caching is used; leave disabled in production
+ * to avoid file modification checks on every request.
+ *
+ * bootstrap.php disables caching when debug = true. In production, use PhpEngine
+ * and refresh metadata during deployment with `bin/cake attributes warm`.
+ *
+ * See https://book.cakephp.org/6.x/core-libraries/attribute-resolver.html
+ */
+ 'AttributeResolver' => [
+ 'default' => [
+ 'paths' => ['Controller/*Controller.php', 'Controller/**/*Controller.php'],
+ 'basePath' => APP,
+ 'cache' => '_cake_attributes_',
+ ],
],
/*
diff --git a/config/bootstrap.php b/config/bootstrap.php
index 82a92c60fa..26b5762164 100644
--- a/config/bootstrap.php
+++ b/config/bootstrap.php
@@ -32,6 +32,7 @@
*/
require CORE_PATH . 'config' . DS . 'bootstrap.php';
+use Cake\AttributeResolver\AttributeResolver;
use Cake\Cache\Cache;
use Cake\Core\Configure;
use Cake\Core\Configure\Engine\PhpConfig;
@@ -102,6 +103,7 @@
if (Configure::read('debug')) {
Configure::write('Cache._cake_model_.duration', '+1 minute');
Configure::write('Cache._cake_translations_.duration', '+1 minute');
+ Configure::write('AttributeResolver.default.cache', false);
}
/*
@@ -184,6 +186,7 @@
* This will also remove the loaded config data from memory.
*/
Cache::setConfig(Configure::consume('Cache'));
+AttributeResolver::setConfig(Configure::consume('AttributeResolver'));
ConnectionManager::setConfig(Configure::consume('Datasources'));
TransportFactory::setConfig(Configure::consume('EmailTransport'));
Mailer::setConfig(Configure::consume('Email'));
@@ -210,7 +213,7 @@
* You can enable default locale format parsing by adding calls
* to `useLocaleParser()`. This enables the automatic conversion of
* locale specific date formats when processing request data. For details see
- * @link https://book.cakephp.org/5/en/core-libraries/internationalization-and-localization.html#parsing-localized-datetime-data
+ * @link https://book.cakephp.org/6.x/core-libraries/internationalization-and-localization.html#parsing-localized-datetime-data
*/
// \Cake\Database\TypeFactory::build('time')->useLocaleParser();
// \Cake\Database\TypeFactory::build('date')->useLocaleParser();
@@ -231,7 +234,7 @@
// \Cake\Utility\Inflector::rules('uninflected', ['dontinflectme']);
// set a custom date and time format
-// see https://book.cakephp.org/5/en/core-libraries/time.html#setting-the-default-locale-and-format-string
+// see https://book.cakephp.org/6.x/core-libraries/time.html#setting-the-default-locale-and-format-string
// and https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
// \Cake\I18n\Date::setToStringFormat('dd.MM.yyyy');
// \Cake\I18n\Time::setToStringFormat('dd.MM.yyyy HH:mm');
diff --git a/config/paths.php b/config/paths.php
index 37a24819d7..4afde80949 100644
--- a/config/paths.php
+++ b/config/paths.php
@@ -47,13 +47,13 @@
define('CONFIG', ROOT . DS . 'config' . DS);
/*
- * File path to the webroot directory.
+ * File path to the public directory.
*
- * To derive your webroot from your webserver change this to:
+ * To derive this path from your webserver change this to:
*
* `define('WWW_ROOT', rtrim($_SERVER['DOCUMENT_ROOT'], DS) . DS);`
*/
-define('WWW_ROOT', ROOT . DS . 'webroot' . DS);
+define('WWW_ROOT', ROOT . DS . 'public' . DS);
/*
* Path to the tests directory.
diff --git a/config/plugins.php b/config/plugins.php
index 6be3095e01..28de4ef15c 100644
--- a/config/plugins.php
+++ b/config/plugins.php
@@ -4,7 +4,7 @@
*
* In this file, you configure which plugins are loaded in the different states your app can be.
* It's loaded via the `parent::bootstrap();` call inside your `Application::bootstrap()` method.
- * For more information see https://book.cakephp.org/5/en/plugins.html#loading-plugins-via-configuration-array
+ * For more information see https://book.cakephp.org/6.x/plugins.html#loading-plugins-via-configuration-array
*
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
diff --git a/config/routes.php b/config/routes.php
index 7a8d2b7aef..f21b570df8 100644
--- a/config/routes.php
+++ b/config/routes.php
@@ -2,13 +2,6 @@
/**
* Routes configuration.
*
- * In this file, you set up routes to your controllers and their actions.
- * Routes are very important mechanism that allows you to freely connect
- * different URLs to chosen controllers and their actions (functions).
- *
- * It's loaded within the context of `Application::routes()` method which
- * receives a `RouteBuilder` instance `$routes` as method argument.
- *
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
@@ -21,76 +14,10 @@
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
-use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
-/*
- * This file is loaded in the context of the `Application` class.
- * So you can use `$this` to reference the application class instance
- * if required.
- */
-return function (RouteBuilder $routes): void {
- /*
- * The default class to use for all routes
- *
- * The following route classes are supplied with CakePHP and are appropriate
- * to set as the default:
- *
- * - Route
- * - InflectedRoute
- * - DashedRoute
- *
- * If no call is made to `Router::defaultRouteClass()`, the class used is
- * `Route` (`Cake\Routing\Route\Route`)
- *
- * Note that `Route` does not do any inflections on URLs which will result in
- * inconsistently cased URLs when used with `{plugin}`, `{controller}` and
- * `{action}` markers.
- */
- $routes->setRouteClass(DashedRoute::class);
-
- $routes->scope('/', function (RouteBuilder $builder): void {
- /*
- * Here, we are connecting '/' (base path) to a controller called 'Pages',
- * its action called 'display', and we pass a param to select the view file
- * to use (in this case, templates/Pages/home.php)...
- */
- $builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
-
- /*
- * ...and connect the rest of 'Pages' controller's URLs.
- */
- $builder->connect('/pages/*', 'Pages::display');
-
- /*
- * Connect catchall routes for all controllers.
- *
- * The `fallbacks` method is a shortcut for
- *
- * ```
- * $builder->connect('/{controller}', ['action' => 'index']);
- * $builder->connect('/{controller}/{action}/*', []);
- * ```
- *
- * It is NOT recommended to use fallback routes after your initial prototyping phase!
- * See https://book.cakephp.org/5/en/development/routing.html#fallbacks-method for more information
- */
- $builder->fallbacks();
- });
-
- /*
- * If you need a different set of middleware or none at all,
- * open new scope and define routes there.
- *
- * ```
- * $routes->scope('/api', function (RouteBuilder $builder): void {
- * // No $builder->applyMiddleware() here.
- *
- * // Parse specified extensions from URLs
- * // $builder->setExtensions(['json', 'xml']);
- *
- * // Connect API actions here.
- * });
- * ```
- */
+return static function (RouteBuilder $routes): void {
+ // Attribute routes are connected in Application::routes().
+ // You can also connect explicit routes here, for example:
+ // $routes->get('/contact', ['controller' => 'Pages', 'action' => 'display', 'contact'], 'contact');
};
diff --git a/index.php b/index.php
index 4591769163..71e4484aec 100644
--- a/index.php
+++ b/index.php
@@ -13,4 +13,4 @@
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
-require 'webroot' . DIRECTORY_SEPARATOR . 'index.php';
+require 'public' . DIRECTORY_SEPARATOR . 'index.php';
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 38c7d781be..bed23120af 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -5,7 +5,7 @@
stopOnFailure="false"
bootstrap="tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
- xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd">
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/13.0/phpunit.xsd">
diff --git a/webroot/.htaccess b/public/.htaccess
similarity index 100%
rename from webroot/.htaccess
rename to public/.htaccess
diff --git a/webroot/css/cake.css b/public/css/cake.css
similarity index 100%
rename from webroot/css/cake.css
rename to public/css/cake.css
diff --git a/webroot/css/fonts.css b/public/css/fonts.css
similarity index 100%
rename from webroot/css/fonts.css
rename to public/css/fonts.css
diff --git a/webroot/css/home.css b/public/css/home.css
similarity index 100%
rename from webroot/css/home.css
rename to public/css/home.css
diff --git a/webroot/css/milligram.min.css b/public/css/milligram.min.css
similarity index 100%
rename from webroot/css/milligram.min.css
rename to public/css/milligram.min.css
diff --git a/webroot/css/normalize.min.css b/public/css/normalize.min.css
similarity index 100%
rename from webroot/css/normalize.min.css
rename to public/css/normalize.min.css
diff --git a/webroot/favicon.ico b/public/favicon.ico
similarity index 100%
rename from webroot/favicon.ico
rename to public/favicon.ico
diff --git a/webroot/font/Raleway-License.txt b/public/font/Raleway-License.txt
similarity index 100%
rename from webroot/font/Raleway-License.txt
rename to public/font/Raleway-License.txt
diff --git a/webroot/font/cakedingbats-webfont.eot b/public/font/cakedingbats-webfont.eot
similarity index 100%
rename from webroot/font/cakedingbats-webfont.eot
rename to public/font/cakedingbats-webfont.eot
diff --git a/webroot/font/cakedingbats-webfont.svg b/public/font/cakedingbats-webfont.svg
similarity index 100%
rename from webroot/font/cakedingbats-webfont.svg
rename to public/font/cakedingbats-webfont.svg
diff --git a/webroot/font/cakedingbats-webfont.ttf b/public/font/cakedingbats-webfont.ttf
similarity index 100%
rename from webroot/font/cakedingbats-webfont.ttf
rename to public/font/cakedingbats-webfont.ttf
diff --git a/webroot/font/cakedingbats-webfont.woff b/public/font/cakedingbats-webfont.woff
similarity index 100%
rename from webroot/font/cakedingbats-webfont.woff
rename to public/font/cakedingbats-webfont.woff
diff --git a/webroot/font/cakedingbats-webfont.woff2 b/public/font/cakedingbats-webfont.woff2
similarity index 100%
rename from webroot/font/cakedingbats-webfont.woff2
rename to public/font/cakedingbats-webfont.woff2
diff --git a/webroot/font/raleway-400-cyrillic-ext.woff2 b/public/font/raleway-400-cyrillic-ext.woff2
similarity index 100%
rename from webroot/font/raleway-400-cyrillic-ext.woff2
rename to public/font/raleway-400-cyrillic-ext.woff2
diff --git a/webroot/font/raleway-400-cyrillic.woff2 b/public/font/raleway-400-cyrillic.woff2
similarity index 100%
rename from webroot/font/raleway-400-cyrillic.woff2
rename to public/font/raleway-400-cyrillic.woff2
diff --git a/webroot/font/raleway-400-latin-ext.woff2 b/public/font/raleway-400-latin-ext.woff2
similarity index 100%
rename from webroot/font/raleway-400-latin-ext.woff2
rename to public/font/raleway-400-latin-ext.woff2
diff --git a/webroot/font/raleway-400-latin.woff2 b/public/font/raleway-400-latin.woff2
similarity index 100%
rename from webroot/font/raleway-400-latin.woff2
rename to public/font/raleway-400-latin.woff2
diff --git a/webroot/font/raleway-400-vietnamese.woff2 b/public/font/raleway-400-vietnamese.woff2
similarity index 100%
rename from webroot/font/raleway-400-vietnamese.woff2
rename to public/font/raleway-400-vietnamese.woff2
diff --git a/webroot/font/raleway-700-cyrillic-ext.woff2 b/public/font/raleway-700-cyrillic-ext.woff2
similarity index 100%
rename from webroot/font/raleway-700-cyrillic-ext.woff2
rename to public/font/raleway-700-cyrillic-ext.woff2
diff --git a/webroot/font/raleway-700-cyrillic.woff2 b/public/font/raleway-700-cyrillic.woff2
similarity index 100%
rename from webroot/font/raleway-700-cyrillic.woff2
rename to public/font/raleway-700-cyrillic.woff2
diff --git a/webroot/font/raleway-700-latin-ext.woff2 b/public/font/raleway-700-latin-ext.woff2
similarity index 100%
rename from webroot/font/raleway-700-latin-ext.woff2
rename to public/font/raleway-700-latin-ext.woff2
diff --git a/webroot/font/raleway-700-latin.woff2 b/public/font/raleway-700-latin.woff2
similarity index 100%
rename from webroot/font/raleway-700-latin.woff2
rename to public/font/raleway-700-latin.woff2
diff --git a/webroot/font/raleway-700-vietnamese.woff2 b/public/font/raleway-700-vietnamese.woff2
similarity index 100%
rename from webroot/font/raleway-700-vietnamese.woff2
rename to public/font/raleway-700-vietnamese.woff2
diff --git a/webroot/img/cake-logo.png b/public/img/cake-logo.png
similarity index 100%
rename from webroot/img/cake-logo.png
rename to public/img/cake-logo.png
diff --git a/webroot/img/cake.icon.png b/public/img/cake.icon.png
similarity index 100%
rename from webroot/img/cake.icon.png
rename to public/img/cake.icon.png
diff --git a/webroot/img/cake.logo.svg b/public/img/cake.logo.svg
similarity index 100%
rename from webroot/img/cake.logo.svg
rename to public/img/cake.logo.svg
diff --git a/webroot/img/cake.power.gif b/public/img/cake.power.gif
similarity index 100%
rename from webroot/img/cake.power.gif
rename to public/img/cake.power.gif
diff --git a/webroot/index.php b/public/index.php
similarity index 100%
rename from webroot/index.php
rename to public/index.php
diff --git a/webroot/js/.gitkeep b/public/js/.gitkeep
similarity index 100%
rename from webroot/js/.gitkeep
rename to public/js/.gitkeep
diff --git a/src/Application.php b/src/Application.php
index 619028491b..e61282929a 100644
--- a/src/Application.php
+++ b/src/Application.php
@@ -17,8 +17,8 @@
namespace App;
use App\Middleware\HostHeaderMiddleware;
+use Cake\Container\ContainerInterface;
use Cake\Core\Configure;
-use Cake\Core\ContainerInterface;
use Cake\Datasource\FactoryLocator;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Event\EventManagerInterface;
@@ -29,6 +29,7 @@
use Cake\ORM\Locator\TableLocator;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
+use Cake\Routing\RouteBuilder;
/**
* Application setup class.
@@ -86,11 +87,11 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
// Parse various types of encoded request bodies so that they are
// available as array through $request->getData()
- // https://book.cakephp.org/5/en/controllers/middleware.html#body-parser-middleware
+ // https://book.cakephp.org/6.x/controllers/middleware.html#body-parser-middleware
->add(new BodyParserMiddleware())
// Cross Site Request Forgery (CSRF) Protection Middleware
- // https://book.cakephp.org/5/en/security/csrf.html#cross-site-request-forgery-csrf-middleware
+ // https://book.cakephp.org/6.x/security/csrf.html#cross-site-request-forgery-csrf-middleware
->add(new CsrfProtectionMiddleware([
'httponly' => true,
]));
@@ -98,12 +99,27 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
return $middlewareQueue;
}
+ /**
+ * Connect routes declared on controller actions using PHP attributes.
+ *
+ * @param \Cake\Routing\RouteBuilder $routes The route builder.
+ * @return void
+ * @link https://book.cakephp.org/6.x/development/attribute-routing.html
+ */
+ public function routes(RouteBuilder $routes): void
+ {
+ parent::routes($routes);
+ $routes->connectAttributes();
+
+ // Additional explicit routes and route-scoped middleware can be configured here.
+ }
+
/**
* Register application container services.
*
- * @param \Cake\Core\ContainerInterface $container The Container to update.
+ * @param \Cake\Container\ContainerInterface $container The Container to update.
* @return void
- * @link https://book.cakephp.org/5/en/development/dependency-injection.html#dependency-injection
+ * @link https://book.cakephp.org/6.x/development/dependency-injection.html#dependency-injection
*/
public function services(ContainerInterface $container): void
{
@@ -116,7 +132,7 @@ public function services(ContainerInterface $container): void
*
* @param \Cake\Event\EventManagerInterface $eventManager
* @return \Cake\Event\EventManagerInterface
- * @link https://book.cakephp.org/5/en/core-libraries/events.html#registering-listeners
+ * @link https://book.cakephp.org/6.x/core-libraries/events.html#registering-listeners
*/
public function events(EventManagerInterface $eventManager): EventManagerInterface
{
diff --git a/src/Console/Installer.php b/src/Console/Installer.php
index 6ef3c2dd84..a8f8660042 100644
--- a/src/Console/Installer.php
+++ b/src/Console/Installer.php
@@ -41,6 +41,7 @@ class Installer
'logs',
'tmp',
'tmp/cache',
+ 'tmp/cache/attributes',
'tmp/cache/models',
'tmp/cache/persistent',
'tmp/cache/views',
diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php
index 1b427f01f7..32e39f2b63 100644
--- a/src/Controller/AppController.php
+++ b/src/Controller/AppController.php
@@ -17,6 +17,8 @@
namespace App\Controller;
use Cake\Controller\Controller;
+use Cake\Routing\Attribute\RouteClass;
+use Cake\Routing\Route\DashedRoute;
/**
* Application Controller
@@ -24,8 +26,9 @@
* Add your application-wide methods in the class below, your controllers
* will inherit them.
*
- * @link https://book.cakephp.org/5/en/controllers.html#the-app-controller
+ * @link https://book.cakephp.org/6.x/controllers.html#the-app-controller
*/
+#[RouteClass(DashedRoute::class)]
class AppController extends Controller
{
/**
@@ -45,7 +48,7 @@ public function initialize(): void
/*
* Enable the following component for recommended CakePHP form protection settings.
- * see https://book.cakephp.org/5/en/controllers/components/form-protection.html
+ * see https://book.cakephp.org/6.x/controllers/components/form-protection.html
*/
//$this->loadComponent('FormProtection');
}
diff --git a/src/Controller/PagesController.php b/src/Controller/PagesController.php
index 99247c1a35..3b17f780e5 100644
--- a/src/Controller/PagesController.php
+++ b/src/Controller/PagesController.php
@@ -20,6 +20,7 @@
use Cake\Http\Exception\ForbiddenException;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Response;
+use Cake\Routing\Attribute\Get;
use Cake\View\Exception\MissingTemplateException;
/**
@@ -27,7 +28,7 @@
*
* This controller will render views from templates/Pages/
*
- * @link https://book.cakephp.org/5/en/controllers/pages-controller.html
+ * @link https://book.cakephp.org/6.x/controllers/pages-controller.html
*/
class PagesController extends AppController
{
@@ -43,6 +44,8 @@ class PagesController extends AppController
* be found and not in debug mode.
* @throws \Cake\View\Exception\MissingTemplateException In debug mode.
*/
+ #[Get('/', name: 'home', defaults: ['path' => 'home'], pass: ['path'])]
+ #[Get('/pages/*', name: 'pages')]
public function display(string ...$path): ?Response
{
if (!$path) {
diff --git a/src/View/AppView.php b/src/View/AppView.php
index 1bfd5dc18a..be98d626eb 100644
--- a/src/View/AppView.php
+++ b/src/View/AppView.php
@@ -22,7 +22,7 @@
*
* Your application's default view class
*
- * @link https://book.cakephp.org/5/en/views.html#the-app-view
+ * @link https://book.cakephp.org/6.x/views.html#the-app-view
*/
class AppView extends View
{
diff --git a/templates/Pages/home.php b/templates/Pages/home.php
index 710ad2f03d..3eb308333d 100644
--- a/templates/Pages/home.php
+++ b/templates/Pages/home.php
@@ -81,7 +81,7 @@
- Welcome to CakePHP = h(Configure::version()) ?> Chiffon (🍰)
+ Welcome to CakePHP = h(Configure::version()) ?> (🍰)
@@ -97,8 +97,8 @@
@@ -109,10 +109,10 @@
Environment
- =')) : ?>
- - Your version of PHP is 8.1.0 or higher (detected = PHP_VERSION ?>).
+ =')) : ?>
+ - Your version of PHP is 8.5.0 or higher (detected = PHP_VERSION ?>).
- - Your version of PHP is too low. You need PHP 8.1.0 or higher to use CakePHP (detected = PHP_VERSION ?>).
+ - Your version of PHP is too low. You need PHP 8.5.0 or higher to use CakePHP (detected = PHP_VERSION ?>).
@@ -207,8 +207,8 @@
@@ -226,7 +226,7 @@
Docs and Downloads
CakePHP API
The Bakery
- CakePHP Documentation
+ CakePHP Documentation
CakePHP plugins repo
CakePHP Code
CakePHP Awesome List
diff --git a/templates/layout/default.php b/templates/layout/default.php
index 84d321705d..3a3e3d9ff5 100644
--- a/templates/layout/default.php
+++ b/templates/layout/default.php
@@ -39,7 +39,7 @@
CakePHP
diff --git a/tests/TestApp/Application.php b/tests/TestApp/Application.php
new file mode 100644
index 0000000000..46f5ca391b
--- /dev/null
+++ b/tests/TestApp/Application.php
@@ -0,0 +1,23 @@
+post('/csrf-test', ['controller' => 'Pages', 'action' => 'display', 'home']);
+ }
+}
diff --git a/tests/TestCase/ApplicationTest.php b/tests/TestCase/ApplicationTest.php
index 63bc3d1bbb..8715affdb8 100644
--- a/tests/TestCase/ApplicationTest.php
+++ b/tests/TestCase/ApplicationTest.php
@@ -23,6 +23,7 @@
use Cake\Http\MiddlewareQueue;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
+use Cake\Routing\Router;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
@@ -85,4 +86,56 @@ public function testMiddleware()
$middleware->seek(3);
$this->assertInstanceOf(RoutingMiddleware::class, $middleware->current());
}
+
+ /**
+ * Optional route files load alongside the controller attributes.
+ *
+ * @return void
+ */
+ public function testOptionalRouteConfiguration(): void
+ {
+ $configDir = TMP . 'route-configuration' . DS;
+ mkdir($configDir);
+ $routesFile = $configDir . 'routes.php';
+ file_put_contents($routesFile, <<<'PHP'
+get('/custom', ['controller' => 'Pages', 'action' => 'display', 'custom'], 'custom');
+};
+PHP);
+
+ try {
+ $application = new Application($configDir);
+ $application->routes(Router::createRouteBuilder('/'));
+
+ $this->assertSame('/custom', Router::url(['_name' => 'custom']));
+ $this->assertSame('/', Router::url(['_name' => 'home']));
+ } finally {
+ unlink($routesFile);
+ rmdir($configDir);
+ }
+ }
+
+ /**
+ * Controller attributes work without a routes configuration file.
+ *
+ * @return void
+ */
+ public function testRoutingWithoutConfigurationFile(): void
+ {
+ $configDir = TMP . 'route-configuration' . DS;
+ mkdir($configDir);
+
+ try {
+ $application = new Application($configDir);
+ $application->routes(Router::createRouteBuilder('/'));
+
+ $this->assertSame('/', Router::url(['_name' => 'home']));
+ $this->assertSame('/pages/home', Router::url(['_name' => 'pages', 'home']));
+ } finally {
+ rmdir($configDir);
+ }
+ }
}
diff --git a/tests/TestCase/Controller/PagesControllerTest.php b/tests/TestCase/Controller/PagesControllerTest.php
index f0f72c9d09..91f48c0c16 100644
--- a/tests/TestCase/Controller/PagesControllerTest.php
+++ b/tests/TestCase/Controller/PagesControllerTest.php
@@ -16,8 +16,10 @@
*/
namespace App\Test\TestCase\Controller;
+use App\Test\TestApp\Application;
use Cake\Core\Configure;
-use Cake\TestSuite\Constraint\Response\StatusCode;
+use Cake\Routing\Route\DashedRoute;
+use Cake\Routing\Router;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
@@ -33,7 +35,7 @@ class PagesControllerTest extends TestCase
*
* @return void
*/
- public function testDisplay()
+ public function testDisplay(): void
{
Configure::write('debug', true);
$this->get('/pages/home');
@@ -42,12 +44,56 @@ public function testDisplay()
$this->assertResponseContains('');
}
+ /**
+ * Test the named attribute route for the home page.
+ *
+ * @return void
+ */
+ public function testHome(): void
+ {
+ Configure::write('debug', true);
+ $this->get('/');
+ $this->assertResponseOk();
+ $this->assertResponseContains('CakePHP');
+ $this->assertResponseContains('8.5.0 or higher');
+ $this->assertSame('/', Router::url(['_name' => 'home']));
+ $this->assertSame('/pages/home', Router::url(['_name' => 'pages', 'home']));
+ $routes = Router::getRouteCollection()->named();
+ $this->assertInstanceOf(DashedRoute::class, $routes['home']);
+ $this->assertInstanceOf(DashedRoute::class, $routes['pages']);
+ }
+
+ /**
+ * Test that fallback URLs are not connected.
+ *
+ * @return void
+ */
+ public function testNoFallbackRoutes(): void
+ {
+ Configure::write('debug', true);
+ $this->get('/unrouted/index');
+ $this->assertResponseCode(404);
+ $this->assertResponseContains('Missing Route');
+ }
+
+ /**
+ * Test that static pages do not accept POST requests, even with a CSRF token.
+ *
+ * @return void
+ */
+ public function testPostNotRouted(): void
+ {
+ $this->enableCsrfToken();
+ $this->post('/pages/home');
+ $this->assertResponseCode(404);
+ }
+
/**
* Test that missing template renders 404 page in production
*
* @return void
*/
- public function testMissingTemplate()
+ public function testMissingTemplate(): void
{
Configure::write('debug', false);
$this->get('/pages/not_existing');
@@ -61,7 +107,7 @@ public function testMissingTemplate()
*
* @return void
*/
- public function testMissingTemplateInDebug()
+ public function testMissingTemplateInDebug(): void
{
Configure::write('debug', true);
$this->get('/pages/not_existing');
@@ -72,12 +118,25 @@ public function testMissingTemplateInDebug()
$this->assertResponseContains('not_existing.php');
}
+ /**
+ * Test that wildcard routes retain nested page paths.
+ *
+ * @return void
+ */
+ public function testNestedMissingTemplateInDebug(): void
+ {
+ Configure::write('debug', true);
+ $this->get('/pages/nested/not_existing');
+ $this->assertResponseFailure();
+ $this->assertResponseContains('nested/not_existing.php');
+ }
+
/**
* Test directory traversal protection
*
* @return void
*/
- public function testDirectoryTraversalProtection()
+ public function testDirectoryTraversalProtection(): void
{
$this->get('/pages/../Layout/ajax');
$this->assertResponseCode(403);
@@ -89,9 +148,10 @@ public function testDirectoryTraversalProtection()
*
* @return void
*/
- public function testCsrfAppliedError()
+ public function testCsrfAppliedError(): void
{
- $this->post('/pages/home', ['hello' => 'world']);
+ $this->configApplication(Application::class, [CONFIG]);
+ $this->post('/csrf-test', ['hello' => 'world']);
$this->assertResponseCode(403);
$this->assertResponseContains('CSRF');
@@ -102,12 +162,14 @@ public function testCsrfAppliedError()
*
* @return void
*/
- public function testCsrfAppliedOk()
+ public function testCsrfAppliedOk(): void
{
+ Configure::write('debug', true);
+ $this->configApplication(Application::class, [CONFIG]);
$this->enableCsrfToken();
- $this->post('/pages/home', ['hello' => 'world']);
+ $this->post('/csrf-test', ['hello' => 'world']);
- $this->assertThat(403, $this->logicalNot(new StatusCode($this->_response)));
+ $this->assertResponseOk();
$this->assertResponseNotContains('CSRF');
}
}