e107
e107 copied to clipboard
[Feature request]: Use a cleaner format in e107_config.php
Motivation
Currently it's a mess of different formats.
Proposed Solution
Return a multi-dimensional array. As an example:
return [
'mySQL' => [
'server' => '127.0.0.1',
'user' => 'root',
'password' => 'root',
'defaultdb'=> 'e107v2',
'prefix' => 'e107_',
'charset' => 'utf8mb4',
'port' => 3306,
],
'directories' => [
'admin' => 'e107_admin/',
'files' => 'e107_files/',
'images' => 'e107_images/',
'themes' => 'e107_themes/',
'plugins' => 'e107_plugins/',
'handlers' => 'e107_handlers/',
'languages' => 'e107_languages/',
'help' => 'e107_docs/help/',
'downloads' => 'e107_downloads/',
'uploads' => 'e107_uploads/',
'system' => 'e107_system/',
'media' => 'e107_media/',
'cache' => 'e107_cache/',
'logs' => 'e107_logs/',
'core' => 'e107_core/',
'web' => 'e107_web/',
],
];
or an Object:
<?php
class e107_config
{
public static function directories()
{
return [
'admin' => 'e107_admin/',
'files' => 'e107_files/',
'images' => 'e107_images/',
'themes' => 'e107_themes/',
'plugins' => 'e107_plugins/',
'handlers' => 'e107_handlers/',
'languages' => 'e107_languages/',
'help' => 'e107_docs/help/',
'system' => 'e107_system/',
'media' => 'e107_media/',
];
}
public static function database()
{
return [
'server' => '127.0.0.1',
'user' => 'root',
'password' => 'root',
'defaultdb'=> 'e107v2',
'prefix' => 'e107_',
'charset' => 'utf8mb4',
];
}
public static function other()
{
return [
'site_path' => '000000test'
];
}
}
return new e107_config;
Alternatives
Additional Context
No response
RFC: e107_config.php v3 Format
This request for comments proposes a new format for the e107_config.php file, which is used to store the configuration of an e107 installation.
Motivation
Status Quo
The e107 v2 configuration file is made of unstructured and inflexible variables and some additional code that defines constants to modify global behavior.
This is the template of the e107 v2 configuration file:
<?php
/**
* e107 website system
*
* Copyright (C) 2008-{$copyrightYear} e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
*
* e107 configuration file
*
* This file has been generated by the installation script on {$rfc2822Date}.
*/
$mySQLserver = '{{ mySQLserver }}';
$mySQLuser = '{{ mySQLuser }}';
$mySQLpassword = '{{ mySQLpassword }}';
$mySQLdefaultdb = '{{ mySQLdefaultdb }}';
$mySQLport = '{{ mySQLport }}';
$mySQLprefix = '{{ mySQLprefix }}';
$mySQLcharset = 'utf8mb4';
$ADMIN_DIRECTORY = 'e107_admin/';
$FILES_DIRECTORY = 'e107_files/';
$IMAGES_DIRECTORY = 'e107_images/';
$THEMES_DIRECTORY = 'e107_themes/';
$PLUGINS_DIRECTORY = 'e107_plugins/';
$HANDLERS_DIRECTORY = 'e107_handlers/';
$LANGUAGES_DIRECTORY = 'e107_languages/';
$HELP_DIRECTORY = 'e107_docs/help/';
$MEDIA_DIRECTORY = 'e107_media/';
$SYSTEM_DIRECTORY = 'e107_system/';
// -- Optional --
// define('e_DEBUG', true); // Enable debug mode to allow displaying of errors
// define('e_HTTP_STATIC', 'https://static.mydomain.com/'); // Use a static subdomain for js/css/images etc.
// define('e_MOD_REWRITE_STATIC', true); // Rewrite static image urls.
// define('e_LOG_CRITICAL', true); // log critical errors but do not display them to user.
// define('e_GIT', 'path-to-git'); // Path to GIT for developers
// define('X-FRAME-SAMEORIGIN', false); // Option to override X-Frame-Options
// define('e_PDO, true); // Enable PDO mode (used in PHP > 7 and when mysql_* methods are not available)
This template has the following limitations:
- The database configuration is defined by a set of variables that are not grouped together.
- Only one kind of database configuration is supported.
- The directories are also defined by a set of variables that are not grouped together.
- Arbitrary code is loaded in an uncontrolled manner.
- There is no schema to validate the configuration file.
- The variables that are set have to be retrieved and are not passed to the caller, making the handoff unstructured, too.
Goals
- Plug-in architecture for the database configuration to support multiple database types or connection methods
- Validatable schema for the configuration file
- Logical grouping of configuration options
- Structured handoff of the configuration to the caller that imports the configuration file
- Deferred loading of configuration options for tighter control over the loading process
- Adapter for the old configuration file format to allow for a smooth transition
Proposal
New Configuration File Template
The equivalent configuration file in the new format would look like this:
<?php
/**
* e107 website system
*
* Copyright (C) 2008-{$copyrightYear} e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (https://www.gnu.org/licenses/gpl.txt)
*
* Site configuration file
*/
class E107Config extends BaseSiteConfig
{
function getDatabase(): DatabaseConnector
{
return new MysqlTcpConnector(
user: '{{ mySQLuser }}',
password: '{{ mySQLpassword }}',
host: '{{ mySQLserver }}',
port: '{{ mySQLport }}',
dbname: '{{ mySQLdefaultdb }}',
charset: 'utf8mb4',
prefix: '{{ mySQLprefix }}',
);
}
#[ArrayShape(SiteConfig::PATHS_ARRAY_SHAPE)]
function getPaths(): array
{
return [
'admin' => 'e107_admin/',
'core' => 'e107_system/',
'handlers' => 'e107_handlers/',
'images' => 'e107_images/',
'languages' => 'e107_languages/',
'media' => 'e107_media/',
'plugins' => 'e107_plugins/',
'system' => 'e107_system/',
'themes' => 'e107_themes/',
'web' => 'e107_web/',
'docs' => 'e107_docs/',
'files' => 'e107_files/',
];
}
function defineGlobals(): void
{
// define('e_DEBUG', true); // Enable debug mode to allow displaying of errors
// define('e_HTTP_STATIC', 'https://static.mydomain.com/'); // Use a static subdomain for js/css/images etc.
// define('e_MOD_REWRITE_STATIC', true); // Rewrite static image urls.
// define('e_LOG_CRITICAL', true); // log critical errors but do not display them to user.
// define('e_GIT', 'path-to-git'); // Path to GIT for developers
// define('X-FRAME-SAMEORIGIN', false); // Option to override X-Frame-Options
// define('e_PDO, true); // Enable PDO mode (used in PHP > 7 and when mysql_* methods are not available)
}
}
return new E107Config();
This template has the following advantages:
- The database configuration can be defined flexibly by implementing the
DatabaseConnectorinterface. Plugins or other core components can provide their own implementations of the interface to support different database types or connection methods. - The system paths are grouped together in a single method.
- The provider pattern allows us to defer the loading of the configuration until each scope (paths, database, or globals) is needed.
- Arbitrary (legacy) code moved into the
defineGlobals()method allows us to defer and disable the execution of this code in the future. Administrators can then test their installation with the legacy code disabled and report any issues. - The configuration follows a clearly defined schema that can be validated with static code analysis.
- The configuration is passed to the caller as an object, which allows for a structured handoff.
- With that ending return, the caller can also detect whether the old or the new configuration format is used and select the appropriate adapter.
To use this structured approach, the following interface is defined prior to loading e107_config.php:
/**
* Interface for the return value of including e107_config.php
*/
interface SiteConfig
{
const PATHS_ARRAY_SHAPE = [
// Current paths
'admin' => 'string', // Admin area files
'core' => 'string', // System assets
'handlers' => 'string', // System backend framework
'images' => 'string', // System images
'languages' => 'string', // Locale files
'media' => 'string', // Uploaded files
'plugins' => 'string', // System plugins
'system' => 'string', // Runtime-generated site files
'themes' => 'string', // System themes
'web' => 'string', // Frontend web libraries
// Legacy paths
'docs' => 'string', // Admin documentation
'files' => 'string', // e107 v1/v2 supplementary files
];
/**
* Base Paths
*
* Relative paths to the directories of each kind of e107 resource
*
* e107 will append site-specific paths to these base paths.
*
* @return array Associative array of relative paths
*/
#[ArrayShape(SiteConfig::PATHS_ARRAY_SHAPE)]
function getPaths(): array;
/**
* Database Connection Settings
*
* How to connect to the database
*
* @return DatabaseConnector The database connection settings
*/
function getDatabase(): DatabaseConnector;
/**
* Set legacy global constants
*
* @deprecated v2.4.0 Global constants are being phased out in favor of configurable settings elsewhere.
*
* @example
* define('e_DEBUG', true); // Enable debug mode to allow displaying of errors
* define('e_HTTP_STATIC', 'https://static.mydomain.com/'); // Use a static subdomain for js/css/images etc.
* define('e_MOD_REWRITE_STATIC', true); // Rewrite static image urls.
* define('e_LOG_CRITICAL', true); // log critical errors but do not display them to user.
* define('e_GIT', 'path-to-git'); // Path to GIT for developers
* define('X-FRAME-SAMEORIGIN', false); // Option to override X-Frame-Options
* define('e_PDO, true); // Enable PDO mode (used in PHP > 7 and when mysql_* methods are not available)
*/
function defineGlobals(): void;
}
To shield the concrete implementation from new methods that might be added to the interface in the future, the interface is extended by an abstract class:
/**
* Dummy class implementing the {@see SiteConfig} interface
*/
abstract class BaseSiteConfig implements SiteConfig
{
/**
* @inheritDoc
*/
abstract function getDatabase(): DatabaseConnector;
/**
* @inheritDoc
*/
abstract function getPaths(): array;
/**
* @inheritDoc
*/
abstract function defineGlobals(): void;
}
Optional Sensible Defaults
We could go a step further and inherit sensible defaults, but this would obfuscate what configurable options are available in the concrete implementation in e107_config.php.
Such a default implementation could look like this:
/**
* Partial {@see SiteConfig} implementation with default paths and no global constants
*
* {@see SiteConfig::getDatabase()} must be implemented by the concrete class.
*/
abstract class DefaultSiteConfig implements SiteConfig
{
const DEFAULT_PATH_ADMIN = 'admin/';
const DEFAULT_PATH_CORE = 'core/';
const DEFAULT_PATH_HANDLERS = 'handlers/';
const DEFAULT_PATH_IMAGES = 'images/';
const DEFAULT_PATH_LANGUAGES = 'languages/';
const DEFAULT_PATH_MEDIA = 'media/';
const DEFAULT_PATH_PLUGINS = 'plugins/';
const DEFAULT_PATH_SYSTEM = 'system/';
const DEFAULT_PATH_THEMES = 'themes/';
const DEFAULT_PATH_WEB = 'web/';
const DEFAULT_PATH_DOCS = 'docs/';
const DEFAULT_PATH_FILES = 'files/';
protected function pathPrefix(): string
{
return "e107_";
}
/**
* @inheritDoc
*/
#[ArrayShape(SiteConfig::PATHS_ARRAY_SHAPE)]
function getPaths(): array
{
$pathPrefix = $this->pathPrefix();
return [
'admin' => $pathPrefix . self::DEFAULT_PATH_ADMIN,
'core' => $pathPrefix . self::DEFAULT_PATH_CORE,
'handlers' => $pathPrefix . self::DEFAULT_PATH_HANDLERS,
'images' => $pathPrefix . self::DEFAULT_PATH_IMAGES,
'languages' => $pathPrefix . self::DEFAULT_PATH_LANGUAGES,
'media' => $pathPrefix . self::DEFAULT_PATH_MEDIA,
'plugins' => $pathPrefix . self::DEFAULT_PATH_PLUGINS,
'system' => $pathPrefix . self::DEFAULT_PATH_SYSTEM,
'themes' => $pathPrefix . self::DEFAULT_PATH_THEMES,
'web' => $pathPrefix . self::DEFAULT_PATH_WEB,
'docs' => $pathPrefix . self::DEFAULT_PATH_DOCS,
'files' => $pathPrefix . self::DEFAULT_PATH_FILES,
];
}
/**
* @inheritDoc
*/
function defineGlobals(): void
{
}
}
DatabaseConnector
The DatabaseConnector is a simple interface that returns the database abstraction layer (DBAL):
interface DatabaseConnector
{
/**
* Get a new database connection
*
* @return e_db The database abstraction layer
*/
function connect(): e_db;
}
Implementations will be autoloaded from the core in e107_handlers/Database/Connection or from plugins.
The core will provide the following implementations:
MysqlConnector < DatabaseConnectorfor MySQL or MariaDB connectionsMysqlTcpConnector < MysqlConnectorfor MySQL connections via TCPMysqlTlsConnector < MysqlTcpConnectorfor encrypted MySQL connections via TCP
MysqlSocketConnector < MysqlConnectorfor MySQL connections via a UNIX socket
DsnConnector < DatabaseConnectorfor connections from a DSN string as an alternative and compact way to define the database connection. (A DSN string might look like:mysql://user:password@localhost:3306/database?charset=utf8mb4)
Although not in the scope of this RFC, this plug-in architecture could be extended to support more database types. Hypothetical examples could be:
PgsqlConnector < DatabaseConnectorfor PostgreSQL connectionsSqliteConnector < DatabaseConnectorfor SQLite file connectionsSqlsrvConnector < DatabaseConnectorfor Microsoft SQL Server connectionsOracleConnector < DatabaseConnectorfor Oracle connectionsDb2Connector < DatabaseConnectorfor IBM DB2 connections
Backwards-Compatibility Adapter
Using just the new configuration file format in the core would be simple:
$config = include(e_ROOT . 'e107_config.php');
// Connect to the database
$db = $config->getDatabase()->connect();
// Get the handler path
$handlerPath = $config->getPaths()['handlers'];
To transition from the old configuration file format to the new one, we will need to detect which format is used and wrap the old configuration file values in an adapter:
$config = include(e_ROOT . 'e107_config.php');
if (!($config instanceof SiteConfig)) {
$config = new V2ConfigAdapter();
}
V2ConfigAdapter will implement the SiteConfig interface and wrap the old configuration file values:
/**
* Adapter for the old e107 v2 configuration file format
*/
class V2ConfigAdapter implements SiteConfig
{
protected const DATABASE_CREDENTIAL_DEFAULTS = [
'prefix' => '',
'port' => '3306',
'charset' => 'utf8mb4',
];
protected const LEGACY_DATABASE_VARIABLES = [
'prefix' => 'mySQLprefix',
'host' => 'mySQLserver',
'port' => 'mySQLport',
'name' => 'mySQLdefaultdb',
'username' => 'mySQLuser',
'password' => 'mySQLpassword',
'charset' => 'mySQLcharset',
];
protected const LEGACY_PATH_VARIABLES = [
'admin' => 'ADMIN_DIRECTORY',
'core' => 'CORE_DIRECTORY',
'docs' => 'DOCS_DIRECTORY',
'help' => 'HELP_DIRECTORY',
'files' => 'FILES_DIRECTORY',
'handlers' => 'HANDLERS_DIRECTORY',
'images' => 'IMAGES_DIRECTORY',
'languages' => 'LANGUAGES_DIRECTORY',
'media' => 'MEDIA_DIRECTORY',
'plugins' => 'PLUGINS_DIRECTORY',
'system' => 'SYSTEM_DIRECTORY',
'themes' => 'THEMES_DIRECTORY',
'web' => 'WEB_DIRECTORY',
];
/**
* @inheritDoc
*/
function getDatabase(): DatabaseConnector
{
$credentials = self::DATABASE_CREDENTIAL_DEFAULTS;
foreach (self::LEGACY_DATABASE_VARIABLES as $key => $legacyVariable) {
$credentials[$key] = $$legacyVariable;
}
return new DsnConnector(
dsn: sprintf(
'mysql://%s:%s@%s:%s/%s?charset=%s',
$credentials['username'],
$credentials['password'],
$credentials['host'],
$credentials['port'],
$credentials['name'],
$credentials['charset'],
),
prefix: $credentials['prefix'],
);
}
/**
* @inheritDoc
*/
#[ArrayShape(SiteConfig::PATHS_ARRAY_SHAPE)]
function getPaths(): array
{
$paths = [];
foreach (self::LEGACY_PATH_VARIABLES as $key => $legacyVariable) {
$paths[$key] = constant($legacyVariable);
}
return $paths;
}
/**
* @inheritDoc
*/
function defineGlobals(): void
{
include_once(e_ROOT . 'e107_config.php');
}
}
Migration
The old configuration file format can likely be migrated to the new one automatically by a script that reads the old configuration file and replaces it with the new one.
A PHP parser is not required because the configuration loaded by the adapter (V2ConfigAdapter) can be rewritten into the new format template, and all the other code can be wrapped within the SiteConfig::defineGlobals() method.
Thank you for this @Deltik .
Since e107's inception by Jalist over 20 years ago, I, and the other developers through the years, have tried our best to follow his lead, by maintaining the KISS principal "keep-it-simple, stupid!" that he worked by. Not just with the user experience of e107, but also in how it is coded. The goal has always been to minimize complexity, take a minimalist approach, while still trying to maintain a balance, so that the CMS remains both flexible and powerful. Particularly in the areas of plugins, themes and files that users may need to edit, we've always strived to reduce complexity, in order to make the code feel intuitive, even to a non-coder or novice.
So, while I appreciate all the time and effort that must have gone into your proposal, after careful consideration, I don’t find it to be a good fit for e107.
btw. I also took some time to research the format of similar config files for other CMSes while considering your proposal. Some of them are quite powerful, and function with multiple types of databases. Here were the results:
- WordPress
/** The name of the database for WordPress */
define('DB_NAME', 'database_name');
/** MySQL database username */
define('DB_USER', 'database_username');
/** MySQL database password */
define('DB_PASSWORD', 'database_password');
/** MySQL hostname */
define('DB_HOST', 'localhost');
/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8');
/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', '');
- Joomla
public $dbtype = 'mysqli';
public $host = 'localhost';
public $user = 'database_user';
public $password = 'database_password';
public $db = 'database_name';
public $dbprefix = 'jos_';
- Drupal
<?php
/**
* Database settings:
*/
$databases['default']['default'] = [
'database' => 'databasename',
'username' => 'username',
'password' => 'password',
'host' => 'localhost',
'port' => '3306',
'driver' => 'mysql',
'prefix' => '',
];
/**
* Salt for one-time login links, cancel links, form tokens, etc.
*/
$settings['hash_salt'] = 'YOUR_HASH_SALT';
/**
* Access control for update.php script
*/
$settings['update_free_access'] = FALSE;
/**
* Default theme
*/
$settings['default_theme'] = 'bartik';
- Magento
...
'db' => [
'table_prefix' => '',
'connection' => [
'default' => [
'host' => 'localhost',
'dbname' => 'magento',
'username' => 'root',
'password' => ‘root’,
'model' => 'mysql4',
'engine' => 'innodb',
'initStatements' => 'SET NAMES utf8;',
'active' => '1'
]
]
],
...
- PrestaShop
define('_DB_SERVER_', 'localhost');
define('_DB_NAME_', 'database_name');
define('_DB_USER_', 'database_user');
define('_DB_PASSWD_', 'database_password');
define('_DB_PREFIX_', 'ps_');
- OpenCart
// DB
define('DB_DRIVER', 'mysqli');
define('DB_HOSTNAME', 'localhost');
define('DB_USERNAME', 'user');
define('DB_PASSWORD', 'pass');
define('DB_DATABASE', 'opencart');
define('DB_PORT', '3306');
define('DB_PREFIX', 'oc_');
- Typo3
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'] = 'your-database-name';
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'] = 'database-user';
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] = 'password';
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['port'] = '3306';
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] = 'localhost';
- ExpressionEngine
ExpressionEngine utilizes a config.php file for management of many settings. Entries look similar to this:
$config['app_version'] = "305";
$config['install_lock'] = "";
$config['license_number'] = "";
$config['debug'] = "1";
$config['cp_url'] = "";
$config['doc_url'] = "http://ellislab.com/expressionengine/user-guide/";
$config['site_label'] = 'My Site';
$config['cookie_prefix'] = '';
- Concrete5
In Concrete5, the configuration details are stored as PHP constants inside the site.php file located in the config directory. Entries look like the following:
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'your_db_username');
define('DB_PASSWORD', 'your_db_password');
define('DB_DATABASE', 'your_database');
define('PASSWORD_SALT', 'random_string_for_encryption');
- Craft CMS
Craft CMS utilizes a .env file to store sensitive configuration details. Entries in the .env file are typically as follows:
# The environment Craft is currently running in
ENVIRONMENT="dev"
# The secure key Craft will use for hashing and encrypting data
SECURITY_KEY="your_security_key"
# The data source name (DSN) for connecting to the database
DB_DSN="mysql:host=localhost;dbname=craft"
# The database username
DB_USER="your_db_username"
# The database password
DB_PASSWORD="your_db_password"
# The database table prefix
DB_TABLE_PREFIX="craft"
- Grav CMS
In this flat-file CMS, the system configuration is stored as a YAML file in system/config/system.yaml. An example looks as follows:
home:
alias: '/home'
cache:
enabled: true
system:
pages:
theme: mytheme
cache:
gzip: true
Be aware that f.e. lgsl plugin uses e107_config this way:
case "e107":
@include "{$lgsl_file_path}../../../e107_config.php";
$lgsl_config['db']['server'] = $mySQLserver;
$lgsl_config['db']['user'] = $mySQLuser;
$lgsl_config['db']['pass'] = $mySQLpassword;
$lgsl_config['db']['db'] = $mySQLdefaultdb;
$lgsl_config['db']['prefix'] = $mySQLprefix;
break;