diff --git a/config/.env.example b/.env.example
similarity index 73%
rename from config/.env.example
rename to .env.example
index 793f83b10..ba2382539 100644
--- a/config/.env.example
+++ b/.env.example
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-# Used as a default to seed config/.env which
+# Used as a default to seed `.env` which
# enables you to use environment variables to configure
# the aspects of your application that vary by
# environment.
@@ -7,8 +7,9 @@
# Having this file in production is considered a **SECURITY RISK** and also decreases
# the bootstrap performance of your application.
#
-# To use this file, first copy it into `config/.env`. Also ensure the related
-# code block for loading this file is uncommented in `config/bootstrap.php`
+# To use this file, first copy it into `.env` in the project root. It is loaded via
+# `config/bootstrap.php`. Optional PHP overrides: copy `config/app_local.example.php`
+# to `config/app_local.php` (not created on install).
#
# In development .env files are parsed by PHP
# and set into the environment. This provides a simpler
@@ -30,11 +31,17 @@ export SECURITY_SALT="__SALT__"
#export CACHE_CAKEMODEL_URL="file:///path/to/tmp/cache/models?prefix=${APP_NAME}_cake_model_&serialize=true&duration=${CACHE_DURATION}"
# Uncomment these to define email transport configuration via environment variables.
+#export EMAIL_HOST="localhost"
+#export EMAIL_PORT="25"
#export EMAIL_TRANSPORT_DEFAULT_URL=""
# Uncomment these to define database configuration via environment variables.
-#export DATABASE_URL="mysql://my_app:secret@localhost/${APP_NAME}?encoding=utf8&timezone=UTC&cacheMetadata=true"eIdentifiers=false&persistent=false"
-#export DATABASE_TEST_URL="mysql://my_app:secret@localhost/test_${APP_NAME}?encoding=utf8&timezone=UTC&cacheMetadata=true"eIdentifiers=false&persistent=false"
+export DB_HOST="localhost"
+export DB_USERNAME="my_app"
+export DB_PASSWORD="secret"
+export DB_DATABASE="my_app"
+export DATABASE_URL="mysql://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_DATABASE}?encoding=utf8mb4&timezone=UTC&cacheMetadata=true"eIdentifiers=false&persistent=false"
+export DATABASE_TEST_URL="sqlite://127.0.0.1/tmp/tests.sqlite"
# Uncomment these to define logging configuration via environment variables.
#export LOG_DEBUG_URL="file:///path/to/logs/?levels[]=notice&levels[]=info&levels[]=debug&file=debug"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f221f290a..f2b102bd0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,7 +29,7 @@ jobs:
dependencies: 'highest'
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -58,7 +58,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Setup PHP
uses: shivammathur/setup-php@v2
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index b450d4bc4..49b62ef67 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v10
+ - uses: actions/stale@v11
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open for 120 days with no activity. Remove the `stale` label or comment or this will be closed in 15 days'
diff --git a/.gitignore b/.gitignore
index 03ee77c4f..92ca4c98f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
# CakePHP specific files #
##########################
/config/app_local.php
-/config/.env
+/.env
/logs/*
/tmp/*
/vendor/*
diff --git a/README.md b/README.md
index 5a614fa26..59837b2f2 100644
--- a/README.md
+++ b/README.md
@@ -47,9 +47,20 @@ automated upgrades, so you have to do any updates manually.
## Configuration
-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`.
+```text
+.env infrastructure vars
+ └─ app.php application config (reads `.env` via env())
+ └─ app_local.php local application overrides (gitignored, manual)
+```
+
+| File | Role |
+|------|------|
+| `.env.example` → `.env` | Environment variables: database, salt, app name, URLs (created on install) |
+| `config/app.php` | Base application config; reads `.env` through `env()` |
+| `config/app_local.example.php` → `app_local.php` | Optional local overrides on top of `app.php` (stock CakePHP) |
+
+**`.env`** — infrastructure and deployment variables.
+**`app_local.php`** — application tuning (debug defaults, datasource/email overrides). Not created on install; copy from `config/app_local.example.php` if needed.
## Layout
diff --git a/composer.json b/composer.json
index cd196be4f..9ec38086a 100644
--- a/composer.json
+++ b/composer.json
@@ -6,7 +6,7 @@
"homepage": "https://cakephp.org",
"require": {
"php": ">=8.2",
- "cakephp/cakephp": "5.3.*",
+ "cakephp/cakephp": "5.4.*",
"cakephp/migrations": "^5.0",
"cakephp/plugin-installer": "^2.0",
"mobiledetect/mobiledetectlib": "^4.8.03"
diff --git a/config/app.php b/config/app.php
index 9876cf421..e841847c6 100644
--- a/config/app.php
+++ b/config/app.php
@@ -226,8 +226,8 @@
* The keys host, port, timeout, username, password, client and tls
* are used in SMTP transports
*/
- 'host' => 'localhost',
- 'port' => 25,
+ 'host' => env('EMAIL_HOST', 'localhost'),
+ 'port' => env('EMAIL_PORT', 25),
'timeout' => 30,
/*
* It is recommended to set these options through your environment or app_local.php
@@ -265,6 +265,8 @@
* Connection information used by the ORM to connect
* to your application's datastores.
*
+ * Values are read from the root `.env` file via env(). Override in app_local.php.
+ *
* ### Notes
* - Drivers include Mysql Postgres Sqlite Sqlserver
* See vendor\cakephp\cakephp\src\Database\Driver for the complete list
@@ -281,15 +283,18 @@
*
* The values in app_local.php will override any values set here
* and should be used for local and per-environment configurations.
- *
- * Environment variable-based configurations can be loaded here or
- * in app_local.php depending on the application's needs.
*/
'default' => [
'className' => Connection::class,
'driver' => Mysql::class,
'persistent' => false,
'timezone' => 'UTC',
+ 'host' => env('DB_HOST', 'localhost'),
+ 'port' => env('DB_PORT'),
+ 'username' => env('DB_USERNAME', 'my_app'),
+ 'password' => env('DB_PASSWORD', 'secret'),
+ 'database' => env('DB_DATABASE', env('APP_NAME', 'my_app')),
+ 'url' => env('DATABASE_URL', null),
/*
* For MariaDB/MySQL the internal default changed from utf8 to utf8mb4, aka full utf-8 support
@@ -339,6 +344,7 @@
'quoteIdentifiers' => false,
'log' => false,
//'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
+ 'url' => env('DATABASE_TEST_URL', 'sqlite://127.0.0.1/tmp/tests.sqlite'),
],
],
diff --git a/config/bootstrap.php b/config/bootstrap.php
index 82a92c60f..9268b8b7d 100644
--- a/config/bootstrap.php
+++ b/config/bootstrap.php
@@ -55,8 +55,7 @@
/*
* See https://github.com/josegonzalez/php-dotenv for API details.
*
- * Uncomment block of code below if you want to use `.env` file during development.
- * You should copy `config/.env.example` to `config/.env` and set/modify the
+ * You should copy `.env.example` to `.env` and set/modify the
* variables as required.
*
* The purpose of the .env file is to emulate the presence of the environment
@@ -66,13 +65,16 @@
* security risks. See https://github.com/josegonzalez/php-dotenv#general-security-information
* for more information for recommended practices.
*/
-// if (!env('APP_NAME') && file_exists(CONFIG . '.env')) {
-// $dotenv = new \josegonzalez\Dotenv\Loader([CONFIG . '.env']);
-// $dotenv->parse()
-// ->putenv()
-// ->toEnv()
-// ->toServer();
-// }
+if (!env('APP_NAME') && is_readable(ROOT . DS . '.env')) {
+ if (class_exists(\josegonzalez\Dotenv\Loader::class)) {
+ (new \josegonzalez\Dotenv\Loader([ROOT . DS . '.env']))
+ ->parse()
+ ->skipExisting()
+ ->putenv()
+ ->toEnv()
+ ->toServer();
+ }
+}
/*
* Initializes default Config store and loads the main configuration file (app.php)
diff --git a/src/Application.php b/src/Application.php
index 98ef0b958..619028491 100644
--- a/src/Application.php
+++ b/src/Application.php
@@ -35,8 +35,6 @@
*
* This defines the bootstrapping logic and middleware layers you
* want to use in your application.
- *
- * @extends \Cake\Http\BaseApplication<\App\Application>
*/
class Application extends BaseApplication
{
diff --git a/src/Console/Installer.php b/src/Console/Installer.php
index 6ef3c2dd8..0052bf74d 100644
--- a/src/Console/Installer.php
+++ b/src/Console/Installer.php
@@ -61,7 +61,7 @@ public static function postInstall(Event $event): void
$rootDir = dirname(__DIR__, 2);
- static::createAppLocalConfig($rootDir, $io);
+ static::createEnvFile($rootDir, $io);
static::createWritableDirectories($rootDir, $io);
static::setFolderPermissions($rootDir, $io);
@@ -73,19 +73,19 @@ public static function postInstall(Event $event): void
}
/**
- * Create config/app_local.php file if it does not exist.
+ * Create .env file if it does not exist.
*
* @param string $dir The application's root directory.
* @param \Composer\IO\IOInterface $io IO interface to write to console.
* @return void
*/
- public static function createAppLocalConfig(string $dir, IOInterface $io): void
+ public static function createEnvFile(string $dir, IOInterface $io): void
{
- $appLocalConfig = $dir . '/config/app_local.php';
- $appLocalConfigTemplate = $dir . '/config/app_local.example.php';
- if (!file_exists($appLocalConfig)) {
- copy($appLocalConfigTemplate, $appLocalConfig);
- $io->write('Created `config/app_local.php` file');
+ $envFile = $dir . '/.env';
+ $envTemplate = $dir . '/.env.example';
+ if (!file_exists($envFile)) {
+ copy($envTemplate, $envFile);
+ $io->write('Created `.env` file');
}
}
@@ -183,7 +183,7 @@ public static function setFolderPermissions(string $dir, IOInterface $io): void
public static function setSecuritySalt(string $dir, IOInterface $io): void
{
$newKey = hash('sha256', Security::randomBytes(64));
- static::setSecuritySaltInFile($dir, $io, $newKey, 'app_local.php');
+ static::setSecuritySaltInFile($dir, $io, $newKey, '.env');
}
/**
@@ -197,10 +197,10 @@ public static function setSecuritySalt(string $dir, IOInterface $io): void
*/
public static function setSecuritySaltInFile(string $dir, IOInterface $io, string $newKey, string $file): void
{
- $config = $dir . '/config/' . $file;
+ $config = $dir . '/' . $file;
$content = file_get_contents($config);
if ($content === false) {
- $io->write('Config file not readable or not found: config/' . $file);
+ $io->write('Config file not readable or not found: ' . $file);
return;
}
@@ -215,7 +215,7 @@ public static function setSecuritySaltInFile(string $dir, IOInterface $io, strin
$result = file_put_contents($config, $content);
if ($result) {
- $io->write('Updated Security.salt value in config/' . $file);
+ $io->write('Updated Security.salt value in ' . $file);
return;
}
@@ -233,10 +233,10 @@ public static function setSecuritySaltInFile(string $dir, IOInterface $io, strin
*/
public static function setAppNameInFile(string $dir, IOInterface $io, string $appName, string $file): void
{
- $config = $dir . '/config/' . $file;
+ $config = $dir . '/' . $file;
$content = file_get_contents($config);
if ($content === false) {
- $io->write('Config file not readable or not found: config/' . $file);
+ $io->write('Config file not readable or not found: ' . $file);
return;
}
@@ -251,7 +251,7 @@ public static function setAppNameInFile(string $dir, IOInterface $io, string $ap
$result = file_put_contents($config, $content);
if ($result) {
- $io->write('Updated __APP_NAME__ value in config/' . $file);
+ $io->write('Updated __APP_NAME__ value in ' . $file);
return;
}
diff --git a/templates/Pages/home.php b/templates/Pages/home.php
index 4b38d0f0e..710ad2f03 100644
--- a/templates/Pages/home.php
+++ b/templates/Pages/home.php
@@ -39,7 +39,7 @@
}
if ($name === 'debug_kit') {
$error = 'Try adding your current top level domain to the
- DebugKit.safeTld
+ DebugKit.safeTld
config and reload.';
if (!in_array('sqlite', \PDO::getAvailableDrivers())) {
$error .= '
You need to install the PHP extension pdo_sqlite so DebugKit can work properly.';