e107inc / e107

e107 Bootstrap CMS (Content Management System) v2 with PHP, MySQL, HTML5, jQuery and Twitter Bootstrap. Issue Discussion Room: https://gitter.im/e107inc/e107
https://e107.org
GNU General Public License v3.0
318 stars 212 forks source link

[Feature request]: Use a cleaner format in e107_config.php #5120

Open CaMer0n opened 7 months ago

CaMer0n commented 7 months ago

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

Deltik commented 7 months ago

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:

Goals

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:

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:

Although not in the scope of this RFC, this plug-in architecture could be extended to support more database types. Hypothetical examples could be:

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.

CaMer0n commented 7 months ago

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:

  1. 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', '');
  1. Joomla
public $dbtype = 'mysqli';
public $host = 'localhost';
public $user = 'database_user';
public $password = 'database_password';
public $db = 'database_name';
public $dbprefix = 'jos_';
  1. Drupal
    
    <?php

/**

/**

/**

/**

...
'db' => [
  'table_prefix' => '',
  'connection' => [
    'default' => [
      'host' => 'localhost',
      'dbname' => 'magento',
      'username' => 'root',
      'password' => ‘root’,
      'model' => 'mysql4',
      'engine' => 'innodb',
      'initStatements' => 'SET NAMES utf8;',
      'active' => '1'
    ]
  ]
],
...
  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_');
  1. 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_');
  1. 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';
  1. 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'] = '';
  1. 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');
  1. 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"
  1. 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
Jimmi08 commented 7 months ago

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;