danc0 / gimliduck-php

GimliDuck 是一个适应性强的微型 PHP 框架,尽量不干扰你的工作。

v0.13.5 2024-09-15 06:16 UTC

README

一个适应性强的微型 PHP 框架,尽量不干扰你的工作。

这是一个仍在进行中的项目,使用时请自担风险...

死亡的确定性。成功的小概率。我们还在等什么?

安装

composer require danc0/gimliduck-php

使用以下命令创建一个骨架项目: composer create-project danc0/gimli-skeleton

使用以下命令添加 devtools: composer require --dev danc0/gimliduck-devtools

创建一个类似以下内容的 .htaccess 文件,以便将请求指向您的 index.php 文件

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]

使用方法

创建 GimliDuck 应用程序非常简单

declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';

use Gimli\Application;
use Gimli\Router\Route;

$App = Application::create(__DIR__, $_SERVER);

Route::get('/', function(){
	echo "Hello World";
});

$App->run();

这就是您开始所需的所有内容。您可以添加更多内容,例如模板引擎、配置文件等,但您不必这么做。

一个更复杂的示例

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';

use Gimli\Application;
use App\Core\Config;
use App\Core\Cache;

define('APP_ROOT', __DIR__);

$App = Application::create(APP_ROOT, $_SERVER);

// set up your config and add it to the Application
$config_file = parse_ini_file(APP_ROOT . '/App/Core/config.ini', true);
$App->Config = $App->Injector->resolveFresh(Config::class, ['config' => $config_file]);

// Register a cache class with the Injector
$App->Injector->register(Cache::class, Cache::getCache($App->Config->admin_cache));

// Run Application
$App->run();

当创建时,Application 类也会注册基本事件处理程序和 Session 类。另外,如果您的配置包含 enable_latte,则 Latte 模板引擎将使用配置中的 template_base_dir 值作为模板目录添加到 Application 实例中。

声明路由

默认情况下,Gimli 会要求加载 App/Routes 目录下的任何文件。您可以通过在配置文件中将 autoload_routes 设置为 false 来禁用此功能。您可以通过在配置文件中将 route_directory 的值来更改目录。您还可以使用以下方法加载额外的路由文件

// Load routes from a file(s)
$App->loadRouteFiles([
	'App/routes/web.php',
]);

您可以在路由回调中做几件事情...传递一个字符串、一个可调用对象或一个数组。

Route::get('/', function(){
	echo "Hello World"
});

// Single action controller, must use __invoke method
Route::get('/', Home_Controller::class);
// cli routes are single action Job classes
Route::cli('build-cache', Cache_Job::class);

Route::get('/', Home_Controller::class . '@homePage');

Route::get('/', [Home_Controller::class, 'homePage']);

这些都可以,具体取决于您如何操作。

如果需要额外的防御,您可以添加中间件

Route::get('/', [Home_Controller::class, 'homePage'])->addMiddleware(Logged_In_Middleware::class);

这应该是一个实现了 Gimli\Middleware\Middleware_Interface 的类,该接口需要一个返回 Gimli\Middleware\Middleware_Responseprocess 方法。中间件可以访问 Application 实例,包括注入器以及您决定设置的任何其他内容。

您还可以添加组,定义一个默认的路由文件,以及加载额外的路由文件以帮助组织路由。

您的路由也可以包含符合以下模式的可变参数

protected array $patterns = [
	':all' => "([^/]+)",
	':alpha' => "([A-Za-z_-]+)",
	':alphanumeric' => "([\w-]+)",
	':integer' => "([0-9_-]+)",
	':numeric' => "([0-9_-.]+)",
	':id' => "([0-9_-]+)",
	':slug' => "([A-Za-z0-9_-]+)",
];

您需要使用 # 符号在路由定义中添加它们的变量名称。

Route::get('/user/:integer#id', [User_Controller::class, 'getUser']);

此变量名称传递给路由器,并将其设置为控制器方法的依赖项。您应该在控制器方法中使用定义的变量名称作为参数。值将根据可用的 settype 类型转换为类型,可能的类型

integer or int
float or double
string
array
object
boolean or bool

示例控制器方法

public function getUser(Response $Response, int $id): Response {
	// do something with $id
}

控制器应返回一个 Gimli\Http\Response 对象。有一些辅助方法可以返回格式化的 Response 对象,以帮助减少一些条件逻辑。

  • response 基本响应
  • redirect 重定向响应
  • redirect_on_success 如果响应成功则重定向
  • redirect_on_failure 如果响应不成功则重定向
  • json_response JSON 响应

作业文件也提供了以下参数 subcommandoptionsflagsoptions 参数是一个包含名称和值的数组数组。 flags 参数是包含给定标志的数组。如果提供了子命令,则 subcommand 参数仅是一个字符串。

依赖注入

您可以使用内置的注入器来绑定或注册依赖项。您还可以从注入器中解析依赖项。您可以将任何需要的内容添加到注入器中,并通过 Application 实例在您的应用程序中访问它。

内置的注入器会自动连接类并按需解析依赖项。如果您需要在返回类之前进行一些设置或注册已创建的对象,您还可以将一个类绑定到一个闭包中。以下示例展示了从 Router 类解析的单个动作控制器。构造函数的参数是通过注入器解析的。

<?php
declare(strict_types=1);
namespace App\Controllers;

use App\Logic\Dashboard_Logic;
use Gimli\Http\Response;
use Gimli\Application;
use Gimli\View\Latte_Engine;

class Dashboard_Landing_Controller {

	/**
	 * Constructor
	 *
	 * @param Application $Application
	 */
	public function __construct(
		public Application $Application,
		protected Dashboard_Logic $Dashboard_Logic,
		protected Latte_Engine $View
	){
		//
	}

	/**
	 * Single action controller call
	 *
	 * @return Response
	 */
	public function __invoke(Response $Response): Response {		
		$template_data = $this->Dashboard_Logic->getTemplateData();
		return $Response->setResponse($this->View->render('dashboard/dashboard.latte', $template_data));
	}
}

当路由分发方法时,注入器也会解析方法参数。

还有一些注入器辅助方法可以减少一些内联代码。通常,如果您想要内联注入一个类,可以使用 $this->Application->Injector->resolve(Some_Class::class)Application::get()->Injector->resolve(Some_Class::class)resolveresolve_fresh 方法可用于减少内联代码。

数据库

有一个基本的 PDO 包装器 Database 以及一个 Pdo_Manager 类,您可以使用它来管理数据库查询。Pdo_Manager 类返回一个 PDO 实例,可以用于直接运行查询。Database 类是 Pdo_Manager 类的包装器,提供了一些基本的查询方法。还有一个非常基本的 Model 基类和一些用于 Database 的辅助方法,如其他地方,这些方法处理依赖注入并调用注入的 Database 类的方法。辅助方法包括:

  • fetch_column
  • fetch_row
  • fetch_all
  • row_exists

模型种子器

有一个基本的种子器类,可以用于对您的数据库进行种子。这依赖于在模型类中放置的属性,以指导 Seeder_Factory 如何创建数据。

<?php
declare(strict_types=1);

namespace Gimli\Database;

use Gimli\Database\Model;
use Gimli\Database\Seed;


class User_Model extends Model {
	
	/**
	 * @var string $table_name
	 */
	protected string $table_name = 'users';

	
	/**
	 * ID
	 * 
	 * @var int $id 
	 */
	public $id;

	/**
	 * Unique_Id
	 * 
	 * @var string $unique_id 
	 */
	#[Seed(type: 'unique_id', args: ['length' => 12])]
	public $unique_id;

	/**
	 * Username
	 * 
	 * @var string $username 
	 */
	#[Seed(type: 'username')]
	public $username;

	/**
	 * Email
	 * 
	 * @var string $email 
	 */
	#[Seed(type: 'email')]
	public $email;

	/**
	 * Password
	 * 
	 * @var string $password 
	 */
	#[Seed(type: 'password')]
	public $password;

	/**
	 * Is_Active
	 * 
	 * @var int $is_active 
	 */
	#[Seed(type: 'tiny_int')]
	public $is_active;

	/**
	 * First Name
	 * 
	 * @var string $first_name 
	 */
	#[Seed(type: 'first_name')]
	public $first_name;

	/**
	 * Last Name
	 * 
	 * @var string $last_name 
	 */
	#[Seed(type: 'last_name')]
	public $last_name;

	/**
	 * Status
	 * 
	 * @var int $status 
	 */
	#[Seed(type: 'one_of', args: [0,1])]
	public $status;

	/**
	 * Created_At
	 * 
	 * @var string $created_at 
	 */
	#[Seed(type: 'date', args: ['format' => 'Y-m-d H:i:s', 'min' => '2021-01-01', 'max' => '2021-04-01 00:00:00'])]
	public $created_at;

	/**
	 * Updated_At
	 * 
	 * @var string $updated_at 
	 */
	#[Seed(type: 'date', args: ['format' => 'Y-m-d H:i:s', 'min' => '2021-04-01 00:02:00'])]
	public $updated_at;

	/**
	 * bio
	 * 
	 * @var string $about 
	 */
	#[Seed(type: 'paragraph', args: ['count' => 1])]
	public $about;
}

然后可以使用以下代码对数据库进行种子:

Seeder_Factory::make(User_Model::class)
	->seed(123)
	->count(1)
	->create();

除了创建,您还可以调用 getSeededData 来获取将插入数据库的数据。这在测试或手动加载模型而不保存它时很有用。您还可以传递一个回调方法,该方法将接收初始模型的数据。这有助于种子相关数据。回调应返回一个 Seeder_Factory 实例的数组。

Seeder_Factory::make(User_Model::class)
	->seed(123)
	->count(1)
	->callback(function($data) {
		return [
			Seeder_Factory::make(User_Hobby_Model::class)->with(['user_id' => $data['id']]),
			Seeder_Factory::make(User_Group_Model::class)->with(['user_id' => $data['id']]),
		]
	})
	->create();

传递的种子确保每次运行种子器时数据都保持一致,从而产生可重复的数据集。Seeder_Factory 类有一个 getRandomSeed 方法,该方法将返回一个随机种子值。这对于创建不需要重复的随机数据很有用,或者生成可以复制并使用的随机种子。

配置辅助工具

还有一些配置辅助工具

  • get_config 获取整个配置数组
  • get_config_value 从配置数组中获取特定值
  • config_has 检查配置数组中是否存在键