jemdev/dbrm

数据库关系映射,访问库。

2.1 2021-08-06 08:08 UTC

This package is auto-updated.

Last update: 2024-09-04 02:37:24 UTC


README

安装

使用 Composer,在 composer.json 的 require 部分添加以下内容

{
  "jemdev/dbrm": "dev-master"
}

介绍和运行原理。

该包允许访问关系型数据库的数据。其核心思想是,可以进行多表的读取,但只能一次在单个表上执行写入。因此,可以创建动态对象以进行写入操作的每个表。

一些相对简单的方法可以执行准备好的查询以收集数据。对于写入,其他方法允许创建一个实例以初始化给定表的行,并将所需的值分配给该行的不同列。根据是否提供了行标识符,写入将是一个创建、修改或删除。

为了能够创建这些动态实例,一个系统可以建立一种数据模式映射,详细列出表、关系表和视图。基于这些信息,为给定表定义的实例通过读取列列表、它们的类型和其他实用信息来设置属性。

在连接时,如果配置文件不存在,它将被自动创建。随后,如果修改了模式结构,即使是仅添加、修改或从表中删除列,都有一个方法可以重新生成该配置文件。发现每次需要为每个表创建一个类非常不方便,这些修改导致某些类需要部分重写。因此,这些类是动态管理的,实际上它们是虚拟类。

获取连接对象

配置连接

必须创建一个包含 SGBDR 连接参数的文件。该文件必须命名为 dbCnxConf.php,并按照以下格式进行格式化

<?php
/**
 * Fichier de configuration des paramètres de connexion à la base de données.
 * Ce fichier est généré automatiquement lors de la phase initiale d'installation.
 */
$db_app_server  = 'localhost';          // Adresse du serveur de base de données
$db_app_schema  = 'testjem';            // Schéma à cartographier (base de données de l'application)
$db_app_user    = 'testjem';            // Utilisateur de l'application pouvant se connecter au SGBDR
$db_app_mdp     = 'testjem';            // Mot-de-passe de l'utilisateur de l'application
$db_app_type    = 'pgsql';              // Type de SGBDR, MySQL, PostGreSQL, Oracle, etc..
$db_app_port    = '5432';               // Port sur lequel on peut se connecter au serveur
$db_meta_schema = 'INFORMATION_SCHEMA'; // Schéma où pourront être collectées les informations sur le schéma de travail
/**
 * Création des constantes globales de l'application
 * NE PAS MODIFIER LES LIGNES SUIVANTES
 */
defined("DB_ROOT_SERVER")       || define("DB_ROOT_SERVER",         $db_app_server);
defined("DB_ROOT_USER")         || define("DB_ROOT_USER",           $db_app_user);
defined("DB_ROOT_MDP")          || define("DB_ROOT_MDP",            $db_app_mdp);
defined("DB_ROOT_SCHEMA")       || define("DB_ROOT_SCHEMA",         $db_meta_schema);
defined("DB_ROOT_TYPEDB")       || define("DB_ROOT_TYPEDB",         $db_app_type);
defined("DB_ROOT_DBPORT")       || define("DB_ROOT_DBPORT",         $db_app_port);
defined("DB_APP_SCHEMA")        || define("DB_APP_SCHEMA",          $db_app_schema);
defined("DB_APP_SERVER")        || define("DB_APP_SERVER",          $db_app_server);
defined("DB_APP_USER")          || define("DB_APP_USER",            $db_app_user);
defined("DB_APP_PASSWORD")      || define("DB_APP_PASSWORD",        $db_app_mdp);

支持的 SGBDR 类型

目前,仅可使用 MySQL 和 PostGreSQL。我没有测试过除了 MariaDb 以外的 MySQL 分支(Percona 等),但由于它们是兼容的,所以不应出现障碍。用于变量 $db_app_type 的值

  • MySQL:mysql(适用于此类型的 MariaDB)
  • PostGreSQL:pgsql

此文件应放置在您的配置文件所在目录中,根据应用程序的架构。随后,您将能够提供指向此文件的绝对路径。如果一开始不存在,则会自动生成另一个配置文件,这对于包的正常运行是必不可少的。此文件将生成两个版本,第一个命名为 dbConf.php,任何开发者都很容易阅读,第二个命名为 dbConf_compact.php,是供应用程序使用的主要版本,内容完全相同但已压缩成一行。该文件详细描述了整个数据结构,包括表、关系表和视图、列、键和其他详细信息。它将由所有用于写入、插入、更新或删除操作的对象使用。

可访问的全局方法

以下两种基本方法是必不可少的

  • setRequete($sql, $aParams = array(), $cache = true) 定义要执行的查询,可选地,可以在关联数组中指定参数,其中每个索引是sql变量的名称,该变量将分配给相应的值,第三个参数允许启用结果的缓存,缓存默认是关闭的;
  • execute() 此方法可以直接执行使用 setRequete() 定义的函数。然后可以发送查询、存储过程调用或用户函数,甚至可以是写入查询,尽管不推荐使用此选项(请参阅下文的数据写入)

以下两种方法在这里特别重要,并且仅在需要记录数据创建或修改时使用

  • startTransaction() 如果表使用事务性引擎,则开始事务。然后所有后续的查询都将包含在事务中,直到调用 finishTransaction() 方法;
  • finishTransaction($bOk) 结束事务:期望的参数是布尔值,TRUE 将执行 COMMIT,FALSE 将执行 ROLLBACK;

在应用程序开发阶段,另一种方法可能会很有用

  • getErreurs() 以数组的形式返回遇到的错误列表

数据读取

目前没有查询生成器。我们需要自己编写查询,这些查询将被执行以收集数据。

每个查询都可以参数化,并使用 PDO 执行:它将返回单个数据、数据行或数据表,甚至可以是对象。我们将依赖于预先定义的 jemdev\dbrm\vue 类的实例。

示例:按照惯例,连接实例将是变量“$oVue”,并且已经在上文中定义(请理解“视图”一词在 SQL 中的含义)。

<?php
/* Définition de la requête */
$sql = "SELECT utl_id, utl_nom, utl_prenom, utl_dateinscription ".
       "FROM t_utilisateur_utl ".
       "WHERE utl_dateinscription > :p_utl_dateinscription ".
       "ORDER BY utl_nom, utl_prenom";
/* Initialisation de paramètre(s) */
$params = array(':p_utl_dateinscription' => '2015-10-15');
/* Initialisation de la requête */
$oVue->setRequete($sql, $params);
/* Récupération des données */
$infosUtilisateur = $oVue->fetchAssoc();

注意

在此示例中,$oVue 对象的名称并非无关紧要。这里所说的“视图”一词是指 SQL 中的含义。数据库中的视图是查询的摘要。它可以被视为一个虚拟表,由一个查询定义。

可访问的方法

方法名称借鉴了与 MySQL 扩展一起使用的方法。返回值在形式上也是相似的。

  • fetchAssoc() 返回一个关联数组的结果,其中索引是查询中确定的列名或别名;
  • fetchArray() 返回一个数组,其中每列都由两个索引表示,一个是数字,另一个是关联的,表示列名;
  • fetchObject() 返回一个对象,其中每列都是一个属性;
  • fetchLine($out = 'array') 返回一行数据。可以通过传递以下常量之一来指定结果的形式
    • vue::JEMDB_FETCH_OBJECT = 'object' : 返回对象形式的数据;
    • vue::JEMDB_FETCH_ARRAY = 'array' : 返回包含数字和关联索引的数组;
    • vue::JEMDB_FETCH_ASSOC = 'assoc' : 返回关联数组;
    • vue::JEMDB_FETCH_NUM = 'num' : 返回数字索引的数组;
  • fetchOne() 返回唯一数据;

数据写入

可访问的方法

在给定实例上,您有如下方法

  • init($aPk = null) : 初始化行实例。在后台,对象将根据目标表的列动态构建属性;
  • sauvegarder() : 根据是否确定了主键,通过INSERT或UPDATE请求记录对属性的修改;
  • supprimer() : 通过DELETE请求从表中删除行。如果使用事务引擎并且定义了参照完整性约束(CONSTRAINT),当存在引用要删除行的相关表中的数据时,此方法可能会返回错误;
  • startTransaction() : 启动SQL事务;
  • finishTransaction($bOk) : 结束SQL事务,期望参数是一个布尔值,指示是否执行COMMIT或ROLLBACK;

实践应用

如引言所述,我们一次写入一个表的数据。为此,我们创建一个表示该表行的对象。以下是一个示例:我们示例所依赖的表将具有以下形式

  1. 表 t_interlocuteur_int
+---------------------+-------------------------+------+-----+---------+----------------+
| Field               | Type                    | Null | Key | Default | Extra          |
+---------------------+-------------------------+------+-----+---------+----------------+
| int_id              | int(10) unsigned        | NO   | PRI | NULL    | auto_increment |
| adr_id              | int(10) unsigned        | YES  | MUL | NULL    |                |
| int_nom             | varchar(255)            | NO   |     | NULL    |                |
| int_prenom          | varchar(255)            | NO   |     | NULL    |                |
| int_dateinscription | date                    | YES  |     | NULL    |                |
+---------------------+-------------------------+------+-----+---------+----------------+
  1. 表 t_adresse_adr
+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| adr_id            | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| adr_numero        | varchar(16)      | YES  |     | NULL    |                |
| adr_libelle_1     | varchar(128)     | NO   |     | NULL    |                |
| adr_libelle_2     | varchar(128)     | YES  |     | NULL    |                |
| adr_codepostal    | varchar(16)      | NO   |     | NULL    |                |
| adr_commune       | varchar(128)     | NO   |     | NULL    |                |
+-------------------+------------------+------+-----+---------+----------------+

重要提示 : 您无法在插入新行数据时自行定义主键的值。此值将由数据库管理系统(SGBD)自动生成,前提是该列已设置为 auto-increment(MySQL和某些SGBDR。对于没有此功能的数据库(如Oracle),建议为每个表设置一个序列和一个 before insert 触发器,以自动生成主键)。当初始化数据行时,如果知道主键值,可以选择指定该值:如果指定了该值,则将使用相应的值填充列,否则,我们将得到一个空行,准备好进行填充。现在,让我们写入一行数据

<?php
/* On crée l'instance de la ligne à partir du nom de la table cible */
$oAdresse = $oDbrm->getLigneInstance('t_adresse_adr');
/*
 * On détermine si l'on dispose ou non de la clé primaire de la ligne
 * et on stocke ça dans un tableau associatif
 */
$aPk = (!empty($adr_id)) ? array('adr_id' => $adr_id) ? null;
/* On initialise l'instance */
$oAdresse->init($aPk);
/*
 * Dès cet instant, notre objet présente chaque colonne de la
 * table t_interlocuteur_int comme des propriétés qu'on peut modifier
 */
$oAdresse->adr_numero       = $adr_numero;
$oAdresse->adr_libelle_1    = $adr_libelle_1;
// ... etc...

/* On peut maintenant sauvegarder ces informations */
$enreg = $oAdresse->sauvegarder();
/*
 * Terminé, les écritures pour cette ligne sont terminées.
 * On peut récupérer la valeur de la clé primaire si nécessaire et s'il
 * s'agissait d'une création. Cette clé primaire est automatiquement gérée
 * et initialisée dans l'instance.
 * S'il y a eu une erreur, la méthode sauvegarder retournera l'erreur, sinon
 * elle retournera TRUE
 */
if(true == $enreg)
{
    /*
     * Ici, si par exemple vous avez d'autres données à enregistrer, données qui
     * dépendent la la réussite de ce premier enregistrement, vous continuez
     * sur l'enregistrement suivant, exemple :
     */
    $adr_id         = $oAdresse->adr_id;
    $oInterlocuteur = $oDbrm->getLigneInstance('t_interlocuteur_int');
    /*
     * On détermine si l'on dispose ou non de la clé primaire de la ligne
     * et on stocke ça dans un tableau associatif
     */
    $aPk = (!empty($int_id)) ? array('int_id' => $int_id) : null;
    /* On initialise l'instance */
    $oInterlocuteur->init($aPk);
    /*
     * Dès cet instant, notre objet présente chaque colonne de la
     * table t_interlocuteur_int comme des propriétés qu'on peut modifier
     */
    $oInterlocuteur->adr_id     = $adr_id;      // Ici, on alimente la clé étrangère définie en enregistrant l'adresse.
    $oInterlocuteur->int_nom    = $int_nom;
    $oInterlocuteur->int_prenom = $int_prenom;
    if(!is_null($int_dateinscription))
    {
        $oInterlocuteur->int_dateinscription = $int_dateinscription;
    }
    /* On peut maintenant sauvegarder ces informations */
    $enreg = $oInterlocuteur->sauvegarder();
    
    // etc... suite selon les besoins.
}
else
{
    // Ici, le code permettant la gestion de l'erreur selon vos propres manières de faire.
}

可能出现的错误

在插入数据时,将自动执行检查。如果某个列必须要有值而未提供,将引发异常。数据类型也将进行检查,并且如果数据不符合定义的数据模型,则可能会引发异常。

总结:表的实例

当您为某个表创建一个实例时,该表的每一列都成为该实例的属性。因此,列 int_nom 是对象 $oInterlocuteur 的属性,因此可以像公开对象属性一样调用它。如果您尝试将值分配给在考虑的表中不存在的列,将引发异常。同样,您不能随意分配主键的值。但是,在涉及关系表复合键的情况下,存在可以自行定义主键值的情况。因此,如果我们有如示例中的直接联系人和地址之间的关系表(例如 r_int_has_adr_iha),并且该关系表的主键由列 int_idadr_id 组成,我们将按以下方式修改前面的代码

<?php
/* Définition des valeurs de bases des identifiants */
$int_id = null;
$adr_id = null;

/* On crée l'instance de la ligne à partir du nom de la table cible */
$oAdresse = $oDbrm->getLigneInstance('t_adresse_adr');
/*
 * On détermine si l'on dispose ou non de la clé primaire de la ligne
 * et on stocke ça dans un tableau associatif
 */
$aPk = (!empty($adr_id)) ? array('adr_id' => $adr_id) ? null;
/* On initialise l'instance */
$oAdresse->init($aPk);
/*
 * Dès cet instant, notre objet présente chaque colonne de la
 * table t_interlocuteur_int comme des propriétés qu'on peut modifier
 */
$oAdresse->adr_numero       = $adr_numero;
$oAdresse->adr_libelle_1    = $adr_libelle_1;
// ... etc...

/* On peut maintenant sauvegarder ces informations */
$enreg = $oAdresse->sauvegarder();
/*
 * Terminé, les écritures pour cette ligne sont terminées.
 * On peut récupérer la valeur de la clé primaire si nécessaire et s'il
 * s'agissait d'une création. Cette clé primaire est automatiquement gérée
 * et initialisée dans l'instance.
 * S'il y a eu une erreur, la méthode sauvegarder retournera l'erreur, sinon
 * elle retournera TRUE
 */
if(true == $enreg)
{
    $adr_id         = $oAdresse->adr_id;
    // etc... suite selon les besoins.
}
else
{
    // Ici, le code permettant la gestion de l'erreur selon vos propres manières de faire.
}
$oInterlocuteur = $oDbrm->getLigneInstance('t_interlocuteur_int');
/*
 * On détermine si l'on dispose ou non de la clé primaire de la ligne
 * et on stocke ça dans un tableau associatif
 */
$aPk = (!empty($int_id)) ? array('int_id' => $int_id) : null;
/* On initialise l'instance */
$oInterlocuteur->init($aPk);
/*
 * Dès cet instant, notre objet présente chaque colonne de la
 * table t_interlocuteur_int comme des propriétés qu'on peut modifier
 */
$oInterlocuteur->adr_id     = $adr_id;      // Ici, on alimente la clé étrangère définie en enregistrant l'adresse.
$oInterlocuteur->int_nom    = $int_nom;
$oInterlocuteur->int_prenom = $int_prenom;
if(!is_null($int_dateinscription))
{
    $oInterlocuteur->int_dateinscription = $int_dateinscription;
}
/* On peut maintenant sauvegarder ces informations */
$enreg = $oInterlocuteur->sauvegarder();
if(true == $enreg)
{
    $int_id         = $oInterlocuteur->int_id;
    // etc... suite selon les besoins.
}
else
{
    // Ici, le code permettant la gestion de l'erreur selon vos propres manières de faire.
}
if(!is_null($int_id) && !is_null($adr_id))
{
    /* Maintenant, on peut alimenter la tablea relationnelle */
    $oAdresseInt = $oDbrm->getLigneInstance('r_int_has_adr_iha');
    /* On définit les éléments de la clé primaire composite */
    $aPk = array(
        'int_id' => $int_id,
        'adr_id' => $adr_id
    );
    /* On initialise l'instance de l'objet */
    $oAdresseInt->init($aPk);
    /* On peut sauvegarder */
    $enreg = $oAdresseInt->sauvegarder();
    if(true !== $enreg)
    {
        // Ici, le code permettant la gestion de l'erreur selon vos propres manières de faire.
    }
}

然后,在分配主键值时没有发生阻塞。

在实践中

实际使用可能会引导您将写操作请求分散到不同的表,这些表在不同的函数/方法中被调用,从单一位置调用。这样,您可以在需要时使用事务模式。开始事务时,执行每个记录,如果某个方法因为错误返回 FALSE,您可以中断记录的连续性,并通过 ROLLBACK 完成事务,从而避免用孤立的或不一致的数据污染您的表。

(目前)无法做的事情

目前,还有一些元素在 待办事项列表 中,特别是在写入数据时,不能赋值一个值,而是一个 SQL 函数的调用。例如,如果您想使用 MySQL 内置的加密函数来赋值,目前还不能这样做。

$instanceLigne->nom_colonne = "AES_ENCRYPT('valeur', 'Clé de chiffrement')";

如何绕过这个问题。

对于日常使用来说,这并不是一个真正的问题,这种特定情况相对较少。如果您确实需要执行此类操作,您有两个选择。

  • 第一个是通过发送明文值,并在表上添加一个 BEFORE INSERT 触发器,该触发器将执行要应用到值上的 SQL 函数以将其赋值给列。但这种方法可能会在默认禁用用户函数、存储过程和触发器的共享服务器上被阻塞;
  • 第二个是您自己编写写操作 INSERT 或 UPDATE 请求,并使用以下方式通过 execute() 方法执行
<?php
/* On a d'abord besoin d'une instance de jemdev\dbrm\vue */
$oVue = $oDb->getDbrmInstance();
/* On définit la requête SQL d'insertion */
$sql  = "INSERT INTO matable (col_login, col_motdepasse)".  
        "VALUES('Toto', AES_ENCRYPT('valeur', 'Clé de chiffrement'))";  
$oVue->setRequete($sql);  
/* Exécution de la requête. */
$enreg = $oVue->execute();

代码的其余部分没有变化。

一个实例 = 一行

目前尚未计划实现多行更新或删除的功能,因为更新将仅根据主键值进行。多年来我一直在这个管理应用中使用这个包,实际上我从未需要实现这个功能。而且对于偶尔需要这种情况,我可以通过收集要考虑的主键列表来解决这个问题,并在循环中逐行处理。

查询执行时间

这个小系统依赖于 PHP 的原生 error_log 方法来记录慢查询。在开发应用程序的过程中,识别减慢应用程序的瓶颈可能很有用。我们可以简单地激活一个测量系统,该系统将自动计时所有查询。我们可以通过以下方式配置系统:

  • 日志记录类型:“php”、“文件”或“电子邮件”;
  • 从哪个最小秒数开始记录请求;
  • 可选:如果已定义“文件”模式,记录信息到的绝对文件路径;
  • 可选:如果已定义“电子邮件”模式,将警告消息发送到的电子邮件地址。

实现非常简单,例如

$mode = 'fichier';
$maxtime = 1;
$fichier = 'app/tmp/logs/journaldb.log';
$this->_oDb->activerModeDebug($mode, $maxtime, $fichier);

就是这样:一旦启动,只需正常浏览应用程序,特别是显示明显延迟的部分,然后检查日志文件以查找可能需要优化以加快速度的查询。

对于“文件”模式,如果文件不存在,将会创建。

动态缓存管理

一个访问我在其上工作的 MySQL 服务器配置的问题阻止了我配置内置缓存甚至简单地将其默认启用。我希望能够有一个请求缓存管理系统,因此我添加了用于管理此方面的类。

总体来说,每个读取请求在缓存激活的情况下,可以将结果存储在文件缓存中,甚至如果启用了 MemCache 扩展,还可以存储在 MemCache 中。对任何表的写入操作都会重新生成涉及该表的请求的缓存。因此,缓存的有效期取决于新的写入操作,而不是预定义的持续时间。如果请求的结果在三个分钟内有效,并且发生了写入操作,则缓存会更新;如果相同的在三个星期后仍然有效,那么重新生成它是完全没有必要的。

某些方法允许手动重新生成某些表的缓存。例如,如果在表的写入操作中触发器会触发执行存储过程,创建对其他表的写入,那么在这些其他表上重新生成缓存将是重要的。在 PHP 中无法检测这些写入操作,因为这是数据库管理系统直接处理的。同样,如果 CRON 作业触发写入操作而不通过 PHP,那么也无法拦截这些信息来更新相应的缓存,因此需要编写 PHP 代码来执行此清理,该代码需要在一个添加到 CronTab 中的任务中执行。

默认情况下,缓存未激活,如果您有管理您的关系型数据库管理系统(RDBMS)内置缓存的能力,那么这将是一个更优且性能更好的解决方案。

Symfony 集成

对于那些想使用 Symfony 并获取数据库数据而不使用 Doctrine 的人来说,这个解决方案提供了一个可行的替代方案,尽管技术上可能要求更高。这里,不能使用这个库来修改数据库结构:这是数据库管理员(DBA)的职责,并且不允许开发者修改这个结构。然而,jemdev\dbrm 系统本身允许数据结构的变化,并且必须生成一个配置文件。Symfony 的命令行界面(CLI)系统对于此非常有用。因此,我们将查看如何通过单行命令生成此文件。

注意:以下仅是一个可行的建议,您可以选择以其他方式实现。

配置文件

在 Symfony 应用程序的 /config 目录中,我们将在 /config/packages 中创建一个名为 /dbrm 的子目录。

在这个新目录中,我们将创建两个基本文件,appConf.phpdbConf.php,并记录一些基本元素以使配置系统生效。

appConf.php 文件

<?php
$mode = (defined('APP_ENV')) ? APP_ENV : 'dev';
defined("MODE")                 || define("MODE",                   $mode);
defined("DS")                   || define("DS",                     DIRECTORY_SEPARATOR);
defined("REP_ROOT")             || define("REP_ROOT",               dirname(dirname(dirname(__DIR__))). DS);
/**
 * Paramètres de connexion root pour pouvoir récupérer si nécessaire
 * les informations de base de données et construire le fichier dbConf.php
 */
$db_server_type = 'mysql';
switch (MODE)
{
    case 'dev':
        $db_app_server = 'localhost';
        $db_app_port   = '3306';
        $db_app_schema = 'monshema';
        $db_app_user   = 'monutilisateur';
        $db_app_pwd    = 'abc123';
        $db_bin_path   = '/var/lib/mysql/bin';
        $db_root_schema = "INFORMATION_SCHEMA";
        break;
    case 'test':
        $db_app_server = 'localhost';
        $db_app_port   = '3306';
        $db_app_schema = 'monshematest';
        $db_app_user   = 'monutilisateurtest';
        $db_app_pwd    = 'abc123test';
        $db_bin_path   = '/var/lib/mysql/bin';
        $db_root_schema = "INFORMATION_SCHEMA";
        break;
    case 'prd':
    case 'prod':
        $db_app_server = 'localhost';
        $db_app_port   = '3306';
        $db_app_schema = 'monshema';
        $db_app_user   = 'monutilisateur';
        $db_app_pwd    = 'abc123';
        $db_bin_path   = '/var/lib/mysql/bin';
        $db_root_schema = "INFORMATION_SCHEMA";
        break;
}
defined("DB_ROOT_SERVER")       || define("DB_ROOT_SERVER",         $db_app_server);
defined("DB_ROOT_USER")         || define("DB_ROOT_USER",           $db_app_user);
defined("DB_ROOT_MDP")          || define("DB_ROOT_MDP",            $db_app_pwd);
defined("DB_ROOT_SCHEMA")       || define("DB_ROOT_SCHEMA",         $db_root_schema);
defined("DB_ROOT_TYPEDB")       || define("DB_ROOT_TYPEDB",         $db_server_type);
defined("DB_ROOT_DBPORT")       || define("DB_ROOT_DBPORT",         $db_app_port);
defined("DB_APP_SCHEMA")        || define("DB_APP_SCHEMA",          $db_app_schema);
defined("DB_APP_SERVER")        || define("DB_APP_SERVER",          $db_app_server);
defined("DB_APP_USER")          || define("DB_APP_USER",            $db_app_user);
defined("DB_APP_PASSWORD")      || define("DB_APP_PASSWORD",        $db_app_pwd);
defined("DB_BIN_PATH")          || define("DB_BIN_PATH",            $db_bin_path);

目前我们只需要这个文件中的内容。

dbConf.php 文件

此文件通常由包中的某个库生成,但通过提供基本框架,我们将简化这些事情。

<?php
/**
 * @package     jemdev
 *
 * Ce code est fourni tel quel sans garantie.
 * Vous avez la liberté de l'utiliser et d'y apporter les modifications
 * que vous souhaitez. Vous devrez néanmoins respecter les termes
 * de la licence CeCILL dont le fichier est joint à cette librairie.
 * {@see http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html}
 *
 * Date de génération du fichier : 12/05/2023 11:05:36
 */

/**
 * Définition des constantes sur les types de données
 */
defined('TYPE_INTEGER') || define('TYPE_INTEGER', 'INT');
defined('TYPE_VARCHAR') || define('TYPE_VARCHAR', 'VARCHAR');
defined('TYPE_ENUM') || define('TYPE_ENUM', 'ENUM');
defined('TYPE_FLOAT') || define('TYPE_FLOAT', 'FLOAT');
defined('TYPE_TINYINT') || define('TYPE_TINYINT', 'TINYINT');
defined('TYPE_DATE') || define('TYPE_DATE', 'DATE');
defined('TYPE_MEDIUMINT') || define('TYPE_MEDIUMINT', 'MEDIUMINT');
defined('TYPE_BLOB') || define('TYPE_BLOB', 'BLOB');
/**
 * Description détaillée des schémas
 */
$dbConf = array(
    0 => array(
        'schema' => array(
            'name'   => DB_APP_SCHEMA,
            'SGBD'   => DB_ROOT_TYPEDB,
            'server' => DB_APP_SERVER,
            'port'   => DB_ROOT_DBPORT,
            'user'   => DB_APP_USER,
            'mdp'    => DB_APP_PASSWORD,
            'pilote' => DB_ROOT_TYPEDB
        ),
        'tables' => array(),
        'relations' => array(),
        'vues' => array()
    )
);

不要修改此基本框架。以后也永远不要手动修改此文件。

设置命令

在我们的类中,我们为命令定义了一个名称,“app:dbrmconf”。

现在打开控制台,在应用程序目录下,创建命令

$ php bin/console make:command app:dbrmconf

如果一切顺利,您将看到以下内容显示

 created: src/Command/DbrmconfCommand.php

           
  Success! 
           

 Next: open your new command class and customize it!
 Find the documentation at https://symfony.com.cn/doc/current/console.html

因此,在 /src/Command 目录中已创建了一个类,我们将对其进行一些修改。编辑这个类。

命令类配置

以下是必要的代码

<?php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use jemdev\dbrm\init\genereconf;
use jemdev\dbrm\vue;

/**
 * Commande personnalisée.
 *
 * Ce fichier va permettre de déclencher la (re)génération du fichier de configuration
 * de la base de données telle que requise par la librairie jemdev\dbrm
 *
 * En lançant simplement en console la commande suivante :
 * php bin/console app:dbrmconf
 *
 * @author JEM-Developpement Ltd
 */
class DbrmconfCommand extends Command
{
    protected static $defaultName = 'app:dbrmconf';
    protected static $defaultDescription = 'Génération du fichier de configuration de la base de données';
    protected static $dirconf;

    protected function configure(): void
    {
        $this->setHelp('Cette commande permet de générer le fichier de configuration de la base de données pour le package jemdev\dbrm');
        self::$dirconf = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR ."config". DIRECTORY_SEPARATOR ."packages". DIRECTORY_SEPARATOR ."dbrm". DIRECTORY_SEPARATOR;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        include(self::$dirconf ."appConf.php");
        $resetDbConf = $this->resetDbConf(DB_ROOT_USER, DB_ROOT_MDP);
        $msg = (true == $resetDbConf)
            ? "Terminé, le fichier a été correctement généré"
            : "Terminé avec des erreurs !";
        $io->success($msg);

        return Command::SUCCESS;
    }

    public function resetDbConf($dbuser,$dbmdp)
    {
        include(self::$dirconf ."appConf.php");
        // Création du fichier de configuration de la base.
        $cible      = REP_ROOT . DIRECTORY_SEPARATOR ."config". DIRECTORY_SEPARATOR ."packages". DIRECTORY_SEPARATOR ."dbrm". DIRECTORY_SEPARATOR ."dbConf.php";
        $oDbInit    = new genereconf(DB_APP_SCHEMA,DB_ROOT_USER,DB_ROOT_MDP,$dbuser,$dbmdp,DB_ROOT_TYPEDB,DB_ROOT_SERVER,DB_ROOT_DBPORT);
        $aConfIS    = array(
            'schema' => array(
                'name'   => DB_ROOT_SCHEMA,
                'SGBD'   => DB_ROOT_TYPEDB,
                'server' => DB_ROOT_SERVER,
                'port'   => DB_ROOT_DBPORT,
                'user'   => DB_ROOT_USER,
                'mdp'    => DB_ROOT_MDP,
                'pilote' => DB_ROOT_TYPEDB
            ),
            'tables'     => array(),
            'relations'  => array(),
            'vues'       => array()
        );
        $oVue       = vue::getInstance($aConfIS);
        $bGenConf   = $oDbInit->genererConf($oVue, $cible);
        return $bGenConf;
    }
}

DbrmconfCommand::execute 方法将启动我们添加的 DbrmconfCommand::resetDbConf 方法的执行,并且上述 dbConf.php 文件将根据在数据库中收集到的信息重新生成配置文件。

我们通过请求现有命令的列表来验证这个新命令的存在,但限制在 app 命名空间中。

$ php bin/console list app

正常结果应该包含以下内容

. . . .

Available commands for the "app" namespace:
  app:dbrmconf  Génération du fichier de configuration de la base de données

我们可以在底部清楚地看到我们新的命令,以及它所携带的消息,告知了该命令的性质,正如我们在新类中定义的那样。

让我们试试看

$ php bin/console app:dbrmconf

如果一切顺利,你应该会看到以下内容

[OK] Terminé, le fichier a été correctement généré 

正如我们在命令类中的 execute 方法所定义的那样。

注意,jemdev\dbrm 不是 Doctrine

此包永远不会取代 Doctrine,它不是 ORM。它可以收集数据、修改或删除数据。但它不允许修改数据结构,也不允许生成预填充表单和其他类似功能。它也不强制使用 PHP 和 SQL 之间的某种混合语言:如果你没有基本的 SQL 语言掌握,请继续使用 Doctrine。

结论

此包的设计旨在易于使用,以便开发者不会陷入实现复杂性中,同时无需关心所使用的数据库服务器类型,无论是 MySQL/MariaDb 还是 PostgreSQL。

即将推出

还有待开发代码,以支持除 MySQL 或 PostgreSQL 之外的其他数据库管理系统,这些代码目前还不存在。这是构建数据模式配置表的代码。MySQL 和 PostgreSQL 实现了 INFORMATION_SCHEMA,这极大地简化了这项工作,但并非所有数据库管理系统都实现了它,例如 Oracle。然而,有其他方法可以收集这些信息,以达到相同的结果。

随后,基于 PDO 的功能,jemdev\dbrm 的集成可以在任何项目中完成。

长期项目

自动生成查询的想法已经存在了一段时间,但遗憾的是我在这方面没有数学知识。这涉及到基于图论来确定需要建立哪些连接,以避免定义表中的列,从而使引擎自动构建正确的路径。配置文件已经可以创建一个矩阵(代码尚未集成到包中,但已经准备好并可以运行),现在需要定义适当的算法来构建符合最高标准的查询。

欢迎对此做出贡献。