dulivu / irbis
README
PHP MVC 微型框架。易于使用,专注于模块化开发,同时结合 MVC 模式。
安装
- Composer: composer require dulivu/irbis
- 手动安装:下载源代码并将框架文件夹放置到您的项目中,重命名为 'Irbis'
如何使用,主要注意事项
如果您进行手动安装,在其入口文件 'index.php' 中,您需要调用主文件 'Server'。该文件将使用预配置的自动加载器来调用框架中的所有类。
require('Irbis/Server.php');
如果您正在使用 Composer,它已经实现了自动加载,因此只需调用 composer 的自动加载器,如其 文档 中所述。
require('vendor/autoload.php');
应用程序的入口点始终是 'index.php' 或您在 Web 服务器中配置的等效文件。其主文件如下所示。
index.php
// Aquí llamamos a nuestro autocargador: require('Irbis/Server.php') ó require('vendor/autoload.php') // Obtenemos la instancia única del servidor // esta debe ser siempre nuestra primera línea de código $server = \Irbis\Server::getInstance(); // añadimos nuestro primer módulo llamado 'Test' // dentro de cada módulo siempre debe haber una clase controladora // podemos llamarla como queramos, pero por convención la llamamos 'Controller' $server->addController(new \Test\Controller); // la lógica de nuestra aplicación irá dentro de estos controladores // Por último brindamos la respuesta de la petición // esta debe ser siempre nuestra última línea de código $server->respond();
目录和模块
一个基本模块包括一个包含控制器文件的目录,例如
- Irbis (框架,如果我们使用 composer,它将在 'vendor' 中)
- Test (模块)
- views (用于我们视图的目录)
- index.html
- contact.html
- Controller.php (Test 模块的控制器)
- views (用于我们视图的目录)
- index.php (入口点)
模块、控制器和自动加载器
一个模块是一个包含控制器文件的目录,可以组织每个模块的子目录和文件;例如,一个 'views' 文件夹,用于存储模块使用的所有视图。
控制器将是一个由 'Server' 实例管理的对象。它必须继承自基类 \Irbis\Controller,并且可以包含响应客户端请求的路由的方法。
/Test/Controller.php
namespace Test; use Irbis\Controller as iController; class Controller extends iController { // si nuestro controlador debe registrar sus métodos como rutas de petición // este atributo se debe declarar 'verdadero' public $routes = true; /** * Este método responderá a la ruta base * localhost ó localhost/index.php * @route / */ public function index () { return 'Hola mundo!'; } }
注释中的 '@route' 指令指示该方法应响应的路径,注释应使用 标准 php 格式(如示例所示,使用 // 或 # 将不起作用)。
注意,模块的 Controller 类位于与目录同名的命名空间中,自动加载器 将使用与目录路径相同的命名空间来搜索未注册的类并将其添加到执行中。
完成以上步骤后,您应该能够在浏览器中看到 "Hello World!" (https://)。
路由方法
声明响应客户端请求的路径的方法,在注释中使用 @route 指令,紧跟的值是路径,可以使用 3 个通配符来指定相对路径。示例
@route / => 路径到域名根 https:// 或 https:///index.php。
@route /users => 路径到 https:///index.php/users。
可以省略 'index.php',如果配置了 .htaccess 文件并启用了 MOD_REWRITE = true。
@route /users/(:num),对于数字,路径到 https:///index.php/users/1。
@route /users/(:any),对于字符串或数字,路径到 https:///index.php/users/jhon。
@route /users/(:all), 支持字符串或数字(包括特殊符号如:'/'),路由到类似 https:///index.php/users/jhon/5/admin 的地址。
@route /users/(:any)/(:num), 我们也可以组合通配符(https:///index.php/users/jhon/5),注意通配符(:all)可能会导致与其他路由冲突。根据此示例,此路由永远不会满足,因为前一个示例中也完全相同,一切取决于我们注册模块的顺序。
如果我们的方法响应相对路径,我们可以使用对象 $request 和其方法 'path' 获取通配符值,'path' 方法接收一个参数,表示通配符所在的索引,就像在数组中一样。
$val = $request->path(0); // 1, para el ejemplo 3 $val = $request->path(0); // 'jhon', para el ejemplo 4 $val = $request->path(0); // 'jhon/5/admin', para el ejemplo 5
$val1 = $request->path(0); // 'jhon', para el ejemplo 6 $val2 = $request->path(1); // 5, para el ejemplo 6
管理请求和响应
每个响应客户端请求的方法接收两个参数,按顺序是 $request 和 $response。如果我们创建一个 HTML 表单并将其数据发送到某个路由,这些数据将通过对象 $request 获取,而为了向客户端发送数据,我们使用对象 $response,以及其他一些特性。
/Test/views/index.html
<form method="POST"> <input type="text" name="username"/> <input type="submit"/> </form> <!-- Esta variable se creará automáticamente desde el controlador al hacer POST --> <span><?php echo $greeting ?? ''; ?></span>
/Test/Controller.php
namespace Test; use Irbis\Controller as iController; class Controller extends iController { public $routes = true; /** * @route / */ public function index ($request, $response) { // validamos que el verbo de la petición sea POST if ($request->isMethod('POST')) { // el objeto '$response' tiene una propiedad '$data' que es un arreglo asociativo // podemos ir agregando todos los datos que necesitemos mostrar al cliente a dicho arreglo $response->data['greeting'] = 'Hola '.$request->input('username'); // el método 'input' del objeto '$request' obtiene los valores enviados por POST // si queremos obtener valores enviados por GET usamos el método 'query' // ambos reciben como parámetro el nombre del valor enviado } // Para mostrar una vista usamos la propiedad '$view' del objeto '$response' // le asignamos la ruta donde se encuentra la vista a mostrar $response->view = 'Test/views/index.html'; // finalmente los datos que fuimos agregando podrán ser utilizados como variables en la vista } }
如果我们的 路由方法 没有返回任何值,则将使用传入的 $response 对象作为响应。否则,将创建一个新的 $response 对象,它将具有 '$data' 属性,其值为方法的返回值。因此,在我们的前一个示例中,当我们返回一个字符串(return 'hola mundo!')时,这个字符串会直接显示给客户端。
数据库连接
我们使用一个继承自 PDO 类的类,因此可以根据已实现的数据库引擎连接到不同的数据库,例如,我们将使用 MySQL。
/Test/views/persons.html
<table> <tbody> <?php foreach ($persons as $person): ?> <tr> <td><?= $person['nombre'] ?></td> <td><?= $person['apellido'] ?></td> <td><?= $person['telefono'] ?></td> </tr> <?php endforeach; ?> </tbody> </table>
/Test/Controller.php
namespace Test; use Irbis\Controller as iController; use Irbis\DataBase as DB; class Controller extends iController { public $routes = true; /** * @route / */ public function index ($request, $response) { if ($request->isMethod('POST')) { $response->data['greeting'] = 'Hola '.$request->input('username'); } $response->view = 'Test/views/index.html'; } /** * para el ejemplo debe tener una base de datos * y una tabla llamada 'persons' con registros * @route /persons */ public function persons ($request, $response) { // getInstance(), devuelve una conexión a base de datos // utiliza el nombre registrado en el archivo 'database.ini' $db = DB::getInstance('main'); $stmt = $db->query("SELECT * FROM `persons`"); $response->data['persons'] = $stmt->fetchAll(); // para devolver la ruta de la vista a usar, podemos usar // la propieda del controlador '$dir' es la ruta del // directorio donde se encuetra el controlador actual $response->view = $this->dir.'/views/persons.html'; } }
在开始之前,我们必须在项目根目录下有一个配置文件(database.ini),建议使用服务器端访问规则来防止意外访问这些文件以保障安全。
database.ini
[main] dsn = "mysql:host=127.0.0.1;dbname=test" user = root pass = ****
对于 Apache,可以使用以下安全规则来避免访问配置文件
<Files ~ "\.ini$"> Order allow,deny Deny from all </Files>
例如,如果我们在本地上传到 https:///index.php/persons,我们将能够查看注册的人员列表。
模块化
最后,框架的目标是模块化,能够通过模块层生成代码,最大程度地避免对先前代码的修改。为了示例,我们将添加另一个模块来覆盖 '/persons' 路径并添加一个用于添加人员的表单。首先,我们创建一个新目录 'Test2'(新的模块)并在其中创建一个控制器文件 'Controller.php'。我们的项目结构如下
目录
- Irbis (框架,如果我们使用 composer,它将在 'vendor' 中)
- Test (模块 1)
- views
- index.html
- persons.html
- Controller.php
- views
- Test2 (模块 2)
- views
- persons.html
- Controller.php
- views
- index.php (入口点)
- database.ini (我们的配置文件)
在我们的 'index.php' 文件中,我们将注册新模块并添加相应的控制器。
index.php
require('Irbis/Server.php'); $server = \Irbis\Server::getInstance(); $server->addController(new \Test\Controller); // registramos nuestro nuevo controlador $server->addController(new \Test2\Controller); $server->respond();
/Test2/Controller.php
namespace Test2; use Irbis\Server; use Irbis\Controller as iController; use Irbis\DataBase as DB; class Controller extends iController { public $routes = true; /** * el método responderá a la misma ruta que en el otro controlador, al ser este * módulo el último registrado su método será el que tenga preferencia para responder * @route /persons */ public function persons ($request, $response) { $db = DB::getInstance('main'); if ($request->isMethod('POST')) { $stmt = $db->prepare("INSERT INTO `persons` VALUES (?, ?, ?)"); // el método 'input' del objeto 'request' puede recibir un arreglo // con los nombres de los valores que queremos obtener del POST $stmt->execute($request->input(['nombre', 'apellido', 'telefono'])); } // el método 'getServer' del controlador nos devuelve la instancia única del objeto 'Server' // el método 'respond' del servidor, al ser llamado nuevamente dentro de un controlador // ejecutará la lógica del modulo anteriormente registrado, en este caso 'Test' y devolverá // el objeto '$response' que procesó // por último le cambiamos la vista por la nueva y devolvemos el nuevo objeto $response $response = $this->super(); $response->view = 'Test2/views/persons.html'; // si en el método enrutado devolvemos un objeto '$response' diferente // este será el que se procesará para la respuesta al cliente return $response; } }
/Test2/views/persons.html
<!-- formulario para registrar personas nuevas --> <form method="POST"> <p>Nombre: <input type="text" name="nombre"/></p> <p>Apellido: <input type="text" name="apellido"/></p> <p>Telefono: <input type="text" name="telefono"/></p> <p><input type="submit"/></p> </form> <!-- añadimos la vista del primer módulo --> <?php include('Test/views/persons.html'); ?>
这样,如果新模块出现代码问题或我们需要将系统恢复到先前的版本,我们只需注释掉添加此新模块的行即可。最后,一切将取决于我们如何解耦我们的模块。
require('Irbis/Server.php'); $server = \Irbis\Server::getInstance(); $server->addController(new \Test\Controller); // registramos nuestro nuevo controlador // $server->addController(new \Test2\Controller); $server->respond();
注意:模块添加到系统的顺序很重要,最后添加的模块中的路由方法将优先响应客户端。当再次调用 'respond()' 方法时,将按顺序执行每个路由的方法。调用 'respond()' 方法不是强制性的,这通常是在需要执行前置逻辑时进行的。
常量
框架声明了一些常量,以防这些常量尚未声明,我们可以在我们的输入文件 'index.php' 中控制它们的值。在调用主文件 'server.php' 之前,我们必须声明它们。这些常量用于修改框架的一些行为。
index.php
// aquí podremos declarar nuestras constantes. define('MOD_REWRITE', true); require('Irbis/Server.php'); $server = \Irbis\Server::getInstance(); $server->addController(new \Test\Controller); $server->respond();
MOD_REWRITE(默认为 false),如果为 true,则路径不需要显式声明 'index.php' 文件。要使用此功能,首先必须配置服务器(例如,对于 Apache,是 .htaccess 文件)。
.htaccess
Options +FollowSymLinks RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php?$1 [QSA,L]
DB_INI(默认为 'database.ini'),指示数据库配置文件的路径。
REQUEST_EMULATION(默认为 false),如果为 true,则 $request->isMethod($string) 方法也将验证文档体中的 PUT 和 DELETE 请求动词,这些动词在 '_method' 变量中。
DEBUG_MODE(默认为 false),如果为 true,则会在响应中显示更多错误信息。
DEFAULT_VIEW(默认为 'index'),可以动态返回或分配响应视图的路径。如果没有从客户端发送值,则将使用此常量的值。
/** * @route / */ public function index ($request, $response) { // la ruta de la vista se armará de forma dinámica en función de lo enviado por GET // ejem. /?view=persons, la vista será /Test/persons.html return '/Test/{view}.html'; // si el valor de 'view' no es enviado se usará 'index' por defecto // ejem. para / ó /?param=val la vista será /Test/index.html }
/** * @route /(:all) */ public function index ($request, $response) { // la ruta de la vista se armará de forma dinámica en función de la petición // ejem. /, la vista será /Test/index.html // ejem. /persons, la vista será /Test/persons.html // ejem. /users/jhon, la vista será /Test/users/jhon.html return '/Test/(0).html'; // este caso sólo aplica para el comodín (:all) que captura todo lo registrado // el comodín (:any) siempre buscará un valor, por lo que no coíncide con la ruta / }
BASE_PATH(默认为应用程序所在的目录),不建议更改此值。
CRYP_KEY,用于加密和解密的方法密钥。
CRYP_METHOD,要使用的加密方法。
$val = encrypt('hola mundo'); // valor encriptado $val = decrypt($val); // recuperando valor
模型管理
创建和管理模型的主题在以下 指南 中详细说明。