alphadevx/alpha

Alpha 框架是一个全栈 MVC 框架,适用于 PHP

4.0.0 2024-02-19 15:36 UTC

README

Install and unit tests codecov

Alpha 框架 (4.0.0)

简介

Alpha 框架是一个全栈 MVC 框架,适用于 PHP。它提供了以下特性

  • 一组具有内置验证检查、数据库字段类型映射和 UI 小部件的复杂数据类型。
  • 支持 MySQL 和 SQLite 的活动记录接口。
  • 支持 HTML 和 JSON(用于开发 API)的渲染接口。
  • 支持 APCu、Redis 和 Memcache 的缓存接口。
  • 用于处理大多数数据管理需求的 CRUD 控制器。
  • 前端控制器、抽象控制器和优雅的请求路由接口。
  • 请求/响应类。
  • 完整的 admin UI 和安装 UI。
  • RSS 和 Atom 源生成。
  • 内置基于 Markdown 的 CMS,具有文章投票和评论功能。
  • 内置具有标签支持(包括标签云、全文搜索、查找相似...)的搜索引擎。
  • 强大的安全功能(短生命周期表单加密令牌以防止跨站请求、图片上的加密令牌以防止跨站嵌入、HTTP 请求过滤器以阻止代理或 IP...)。
  • 完整的日志解决方案,包括可选的用户审计跟踪。
  • 内置应用程序备份任务,以及其他自定义任务的支持。

状态

当前的最新稳定版本已通过单元测试和手动测试进行测试,并认为适合生产使用。

安装

Composer

Alpha 应该使用 Composer 安装。以下是一个安装当前版本的 composer.json 文件的最小示例

{
    "minimum-stability": "dev",
    "prefer-stable": true,
    "require": {
        "alphadevx/alpha": "4.*"
    },
    "autoload": {
        "psr-0": {
            "": "src/"
        }
    },
    "scripts": {
        "post-update-cmd": [
            "cp -R vendor/twbs/bootstrap/dist/* public/bootstrap"
        ],
        "post-install-cmd": [
            "rm -rf public/bootstrap",
            "mkdir -p public/bootstrap",
            "cp -R vendor/twbs/bootstrap/dist/* public/bootstrap"
        ]
    }
}

数据库

Alpha 在关系型数据库中存储其记录,目前通过可注入提供程序支持 MySQL 和 SQLite(未来将支持更多)。对于 SQLite,将根据您的配置将文件写入文件系统(请参阅下一节),但对于 MySQL,您需要手动创建数据库

MariaDB [(none)]> CREATE DATABASE alphaframework DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;
Query OK, 1 row affected (0.00 sec)

您还应该确保您将用于访问该数据库的 MySQL 用户账户具有正确的权限。

配置

Alpha 配置分为三个部署环境,每个环境都有自己的配置文件

从由 Composer 创建的 vendor 目录,您应将这些文件复制到您的应用程序根目录中

$ cp -R vendor/alphadevx/alpha/config .

Alpha 根据当前服务器的主机名来选择要加载的正确文件。这种主机名到配置环境的映射发生在 config/servers.ini 文件中。以下是该文件的示例

; The server names for the three environments
dev = localhost,alphaframework.dev
pro = alphaframework.org
test = unittests,qa.alphaframework.org

每个环境可以添加多个服务器主机名,用逗号分隔。

配置文件本身有很好的注释,但以下是您在继续安装之前应该配置的最小设置列表

请查看配置文件中的注释以获取其他可选设置。

引导

Alpha 使用前端控制器将所有流量路由到您的 Web 服务器配置中的单个物理文件。该文件 index.php 包含在框架的 public 目录中。将此目录复制到您的应用程序根目录中

$ cp -R vendor/alphadevx/alpha/public .

现在在您的编辑器中打开该文件进行审查:您将使用该文件向自定义控制器添加客户路由。然后您需要配置您的Web服务器,将所有流量发送到您的 public/index.php 文件(除也位于 public 中的静态文件外),以下是一个示例Apache虚拟主机配置

<VirtualHost alphaframework.dev:80>
   DocumentRoot "/home/john/Git/alphaframework/public"
   ServerName alphaframework.dev


   <Directory "/home/john/Git/alphaframework/public">
      Options FollowSymLinks
      Require all granted

      RewriteEngine On
      RewriteBase /
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteCond %{DOCUMENT_ROOT}/index\.php -f
      RewriteRule ^(.*)$  /index.php?page=$1 [QSA]
   </Directory>
</VirtualHost>

运行安装程序

一旦您已配置应用程序、Web服务器并创建了一个空数据库,您应该启动Web和数据库服务器,并导航到应用程序的根URL。Alpha会自动检测它尚未安装,并将重定向到 Alpha\Controller\InstallController

您将看到的是要求输入用户名和密码的提示,您应该输入应用程序配置文件中的 app.install.usernameapp.install.password 的值以获取访问权限。

InstallController将为您创建所需的所有目录(用于存储日志文件、文件缓存、文章附件等),此外,它还将创建支持我们的活动记录所需的数据库表、索引和外键。一旦所有这些工作完成,将显示管理后端链接。

一旦您的应用程序在生产环境中安装,您应该将 app.check.installed 配置值设置为false,以消除在每次请求中检查应用程序是否安装的负担。

模型

数据类型

Alpha的模型层由一个活动记录实现和一组数据类型组成。数据类型旨在无缝映射到相应的数据库类型,并提供验证和前端小部件。以下数据类型包括

ActiveRecord

活动记录使用这些数据类型来存储我们在数据库中关心的对象的属性。例如,以下是如何我们的 Person 活动记录使用这些复杂类型

<?php

namespace Alpha\Model;

// ...

class Person extends ActiveRecord
{
    /**
     * The forum display name of the person.
     *
     * @var \Alpha\Model\Type\SmallText
     *
     * @since 1.0
     */
    protected $username;

    /**
     * The email address for the person.
     *
     * @var \Alpha\Model\Type\SmallText
     *
     * @since 1.0
     */
    protected $email;

    /**
     * The password for the person.
     *
     * @var \Alpha\Model\Type\SmallText
     *
     * @since 1.0
     */
    protected $password;

    // ...

    /**
     * An array of data display labels for the class properties.
     *
     * @var array
     *
     * @since 1.0
     */
    protected $dataLabels = array('ID' => 'Member ID#',
                                    'username' => 'Username',
                                    'email' => 'E-mail Address',
                                    'password' => 'Password',
                                    'state' => 'Account state',
                                    'URL' => 'Your site address',
                                    'rights' => 'Rights Group Membership',
                                    'actions' => 'Actions', );
    /**
     * The name of the database table for the class.
     *
     * @var string
     *
     * @since 1.0
     */
    const TABLE_NAME = 'Person';

    /**
     * The state of the person (account status).
     *
     * @var \Aplha\Model\Type\Enum
     *
     * @since 1.0
     */
    protected $state;

	// ...

    /**
     * Constructor for the class that populates all of the complex types with default values.
     *
     * @since 1.0
     */
    public function __construct()
    {
        // ...

        $this->username = new SmallText();
        $this->username->setRule(Validator::REQUIRED_USERNAME);
        $this->username->setSize(70);
        $this->username->setHelper('Please provide a name for display on the website (only letters, numbers, and .-_ characters are allowed!).');

        $this->email = new SmallText();
        $this->email->setRule(Validator::REQUIRED_EMAIL);
        $this->email->setSize(70);
        $this->email->setHelper('Please provide a valid e-mail address as your username.');

        $this->password = new SmallText();
        $this->password->setSize(70);
        $this->password->setHelper('Please provide a password for logging in.');
        $this->password->isPassword(true);

        $this->state = new Enum(array('Active', 'Disabled'));
        $this->state->setValue('Active');

        // ...
    }

    // ...
}

如上例所示,Alpha\Model\Person 记录的每个属性都是使用构造函数中的数据类型定义的,包括任何附加的验证规则、大小和帮助文本,如果验证规则因用户输入而违反,则显示给用户。每个属性将映射到数据库中 Person 表的适当列和数据类型,Alpha将为您创建该表。

ActiveRecord CRUD方法

一旦定义了活动记录并设置了数据库,执行典型的CRUD操作变得非常简单

$record = new Person();
$record->set('email', 'some@user.com');
$record->save();

$record->load(25);
$record->loadByAttribute('email', 'some@user.com');
$records = $record->loadAll(0, 25, 'created_ts'); // load the first 25 Person records sorted by created timestamp
$records = $record->loadAllByAttribute('state', 'Active', 0, 25, 'created_ts');

$record->delete();

有关支持的所有CRUD和表管理方法的完整列表,请参阅 Alpha/Model/ActiveRecordProviderInterface 的注释。

事务和错误处理

Alpha通过 ActiveRecord 类上的多个静态方法支持数据库事务。以下是一个典型的示例

try {
    ActiveRecord::begin();
    $cart = new Cart();
    $cart->load(100);
    $items = $cart->loadItems();

    foreach ($items as $item) {
    	$item->set('amount', $item->get('ammount') - 1);
    	$item->save();
    }

    ActiveRecord::commit();
} catch (AlphaException $e) {
    self::$logger->error($e->getMessage());
    ActiveRecord::rollback();
    // ...
}

对象锁定和版本控制

Alpha中的每个活动记录都维护一个版本号,每次记录保存时都会自动递增。为了防止两个或更多单独的线程尝试保存相同的记录时发生竞争条件,在保存之前执行版本检查,如果正在保存的版本号比数据库中当前版本号旧,则抛出 Alpha\Exception\LockingException。当然,在您的应用程序中,您应该捕获该异常并优雅地处理它。

try {
	$record->save();
} catch (LockingException $e) {
    // Reload updated record from the database, display something useful to the user and
    // then let them try to save the record again...
}

历史记录

Alpha可以选择为您维护每个活动记录的历史记录,通过将记录上的 maintainHistory 属性设置为 true。然后,Alpha将为记录类型创建并维护一个第二个数据库表,后缀为 "_history",其中它将保存每次更新记录时的记录副本。这意味着如果要在应用程序中这样做,可以无限期地维护对记录应用的所有更改的完整历史记录。

$record = new Person();
$record->setMaintainHistory(true);
$record->set('email', 'one@test.com');
$record->save();

$record->set('email', 'two@test.com');
$record->save();

echo $record->getHistoryCount(); // should be 2

// load version 1 of person record 10
$record->load(10, 1);
echo $record->get('email'); // one@test.com

// load version 2 of person record 10
$record->load(10, 2);
echo $record->get('email'); // two@test.com

控制器

ControllerInterface

Alpha中的所有控制器应继承自Controller抽象类并实现ControllerInterface接口。该接口定义了处理HTTP请求的所有方法,包括针对每个HTTP动词(如doGET、doPOST、doPUT等)的方法。每个方法只接受一个Request对象作为参数,并返回一个Response对象。以下是一个简单示例:

<?php

namespace My\App\Controller;

use Alpha\Controller\Controller;
use Alpha\Controller\ControllerInterface;
use Alpha\Util\Http\Request;
use Alpha\Util\Http\Response;

class HelloController extends Controller implements ControllerInterface
{
	public function doGET($request): \Alpha\Util\Http\Response
	{
		$name = $request->getParam('name');

		return new Response(200, 'Hello '.$name, array('Content-Type' => 'text/plain'));
	}
}

路由

将HTTP请求路由到正确的控制器由FrontController处理。有两种路由请求的方法:使用用户友好的URL,或使用包含加密令牌的安全URL,该令牌包含请求的参数和控制器的名称。

用户友好的URL

在您的index.php引导文件中,您应该实例化一个新的FrontController来处理所有请求。然后可以像这样将路由添加到FrontController中:

use Alpha\Controller\Front\FrontController;
use My\App\Controller\HelloController;

// ...

$front = new FrontController();

$this->addRoute('/hello/{name}', function ($request) {
    $controller = new HelloController();

    return $controller->process($request);
});

$request = new Request(); // this will build the request from super global data.
$response = $front->process($request); // this will map the requested route to the correct controller
echo $response->send(); // send the response to the client

FrontController::process()方法将负责将请求的路由映射到正确的控制器,而addRoute闭包中调用的Controller::process()方法将HTTP请求动词(例如GET)映射到正确的控制器方法(例如doGET)。

请注意,您还可以在路由中定义默认请求参数,使它们成为可选的。

$this->addRoute('/hello/{name}', function ($request) {
    $controller = new HelloController();

    return $controller->process($request);
})->value('name', 'unknown'); // if the client requests /hello, return "hello unknown"

安全令牌URL

如果您担心通过查询字符串或路由参数传递敏感信息,您可以生成一个安全的URL来调用控制器,如下所示:

$url = FrontController::generateSecureURL('act=My\App\Controller\HelloController&name=bob');
// $url now something like "http://www.myapp.com/tk/gKwbKqR7uQq-d07z2y8Nnm1JnW_ZTKIUpT-KUJ7pYHxMouGoosktcIUiLKFz4uR8"

请注意,当请求时,FrontController将自动解码和解密URL生成器,使用的是在安装时在配置文件中设置的密钥。

视图

Alpha中的视图由两部分组成:负责将活动记录映射到视图的视图类,以及负责定义实际视图内容的视图模板

视图类

视图类应扩展Alpha\View\View类,该类定义了视图可用的标准方法(如createView()、editView()、listView()等)。您可以在需要执行特定操作时覆盖这些方法。通常,视图与其渲染的相应活动记录之间存在一对一的映射,视图负责将记录数据映射到底层模板。

以下是一个示例,其中我们注入了给定购物车中项目的数量,以便在模板中稍后使用以显示购物车详细信息:

<?php

use Alpha\View\View;
use My\App\Model\Cart;
use My\App\Model\Item;
// ...

class CartView extends View
{
    // ...

    public function detailedView($fields = array()): string
    {
        // this->record will be set to our cart at this stage during factory instantiation
        $items = $this->record->getItems();

        $fields['itemCount'] = count($items);

        // ...

        $html = $this->loadTemplate($this->record, 'detail', $fields);

        return $html;
    }
}

视图模板

虽然您可以直接在视图类的方法中生成HTML并返回它,但通常您希望将HTML保持在模板中,以便更容易管理。View类提供了两种单独的方法来加载模板:

// Assuming that this->record is a My\App\Model\Cart instance, this will check in the following locations for the template:
// 1. [app.root]/View/Html/Templates/Cart/detail.phtml (this is where you should place your custom templates)
// 2. [app.root]/Alpha/View/Renderer/Html/Templates/Cart/detail.phtml
$html = $this->loadTemplate($this->record, 'detail', $fields);

// You can also load a fragment template (does not require a record to be passed).  It will check for the file in:
// 1. [app.root]/View/Html/Fragments/[fileName]
// 2. [app.root]/Alpha/View/Renderer/Html/Fragments/[fileName]
$html = $this->loadTemplateFragment('html', 'header.phtml', $fields);

在模板本身中,您可以将任何传递到$fields数组中的值作为常规变量引用。

<p>There are <?= $itemCount ?> items in your shopping cart.</p>

小部件

Alpha在Alpha\View\Widget包中提供了一些HTML小部件,这些小部件非常适合使用Twitter Bootstrap CSS和额外的jQuery代码快速渲染漂亮界面。

以下是小部件列表:

实用工具

Alpha在Alpha\Util包中包含了许多不同的实用工具。以下部分涵盖了其中的一些亮点。

缓存

提供了数据缓存,该缓存提供支持Memcache、Redis和APCu的工厂和可注入提供者。这些类在Alpha\Util\Cache包中提供,而提供者实现了Alpha\Util\Cache\CacheProviderInterface。以下是一个使用Redis提供者的示例:

use Alpha\Util\Service\ServiceFactory;

// ...

$cache = ServiceFactory::getInstance('Alpha\Util\Cache\CacheProviderRedis','Alpha\Util\Cache\CacheProviderInterface');
$record = $cache->get($cacheKey);

代码高亮

Alpha\Util\Code\Highlight\HighlightProviderFactory提供将普通源代码转换为代码高亮HTML源代码的对象。提供了Geshi和Luminous库的提供者。以下是一个示例:

use Alpha\Util\Service\ServiceFactory;

// ...

$highlighter = ServiceFactory::getInstance('Alpha\Util\Code\Highlight\HighlightProviderGeshi','Alpha\Util\Code\Highlight\HighlightProviderInterface');

$html = $highlighter->highlight($code, 'php');

电子邮件

Alpha提供了一个电子邮件包,其中包含注入不同电子邮件提供者的接口。以下是一个示例用法:

use Alpha\Util\Service\ServiceFactory;
use Alpha\Exception\MailNotSentException;

// ...

$mailer = ServiceFactory::getInstance('Alpha\Util\Email\EmailProviderPHP','Alpha\Util\Email\EmailProviderInterface');

try {
	$mailer->send('to@mail.com', 'from@mail.com', 'Subject', 'Some HTML...', true);
} catch (MailNotSentException $e) {
	// handle error...
}

聚合源

Alpha可以使用Alpha\Util\Feed包中的类根据活动记录列表生成Atom、RSS或RSS2聚合源。还提供了一个方便的FeedController,它已在FrontController中设置了以下路由:

$this->addRoute('/feed/{ActiveRecordType}/{type}', function ($request) {
    $controller = new FeedController();

    return $controller->process($request);
})->value('type', 'Atom');

如果您想在应用程序中直接使用馈送类,可以这样做

use Alpha\Util\Feed\Atom;

// ...

$feed = new Atom($ActiveRecordType, $title, $url, $description, $pubDate, $ID);
$feed->setFieldMappings('title', 'URL', 'description', 'created_ts', 'ID');
$feed->addAuthor('Feed author');
$feed->loadRecords(20, 'ID');

$xml = $feed->render();

验证

提供了一个验证类,用于处理典型的类型检查或测试常见的字符串模式(电子邮件地址、URL等)。相关的类是Alpha\Util\Helper\Validator,请检查Alpha\Test\Util\Helper\ValidatorTest单元测试,以获取大量使用此类的代码示例。

HTTP

过滤器

Alpha提供了一个框架,可以将可选的请求过滤器注入到前端控制器中,这些过滤器在通过路由之前运行。您可以编写自己的过滤器,实现Alpha\Util\Http\Filter\FilterInterface,此外,Alpha还提供了以下内置过滤器,您可以选择启用它们

在前端控制器中注册过滤器很容易,通常在应用程序引导时进行,典型地是在您的index.php文件中

use Alpha\Controller\Front\FrontController;
use Alpha\Util\Http\Filter\ClientBlacklistFilter;
use Alpha\Exception\ResourceNotAllowedException;

// ...

try {
	$front = new FrontController();
	$front->registerFilter(new ClientBlacklistFilter());
} catch (ResourceNotAllowedException $e) {
	// the filters will throw this exception when invoked so you will need to handle, e.g. send 403 response.
}

会话

Alpha提供了一层会话抽象,允许注入不同的会话提供者,这在例如您想将会话存储在数组中以进行单元测试时非常有用。还可以编写提供者以将会话存储在NoSQL或数据库后端。以下是一个使用默认PHP会话机制的示例

use Alpha\Util\Service\ServiceFactory;

// ...

$session = ServiceFactory::getInstance('Alpha\Util\Http\Session\SessionProviderPHP', 'Alpha\Util\Http\Session\SessionProviderInterface');
$session->set('somekey', 'somevalue'); // you can also pass complex types
echo $session->get('somekey');

请求和响应

而不是直接与PHP超级全局变量(如_GET和_POST)交互,Alpha在Alpha\Util\Http包中提供了Request和Response类来封装这些全局变量。

Request对象的属性(HTTP头、HTTP动词、请求体等)是在通过__construct($overrides)方法创建对象时设置的,基于可用的超级全局值。请注意,您可以通过$overrides散列数组参数覆盖任何超级全局值,这对于单元测试非常有用。

Response构造函数在构造时期望一个HTTP响应代码、正文以及可选的HTTP头数组。以下是一个示例

use Alpha\Util\Http\Response;

// ...

$response = new Response(200, '<p>hello world!</p>', array('Content-Type' => 'text/html'));
$response->send();

日志记录

提供了一个带有不同级别日志记录的标准方法集的日志记录类

use Alpha\Util\Logging\Logger;

// ...

$logger = new Logger('ClassName');
$logger->debug('Debug information');
$logger->info('Notable information');
$logger->warn('Something went wrong');
$logger->error('A serious error');
$logger->fatal('A fatal error');
$logger->sql('SELECT * FROM...');
$logger->action('Action carried out by the current user logged in the ActionLog table');

写入的日志由以下配置属性定义

$config->get('app.file.store.dir').'logs/'.$config->get('app.log.file');

Alpha还提供了一个KPI类,或关键性能指标,用于记录时间敏感条目,这可以表明您的应用程序在生产中的性能。以下是一个示例用法

use Alpha\Util\Logging\KPI;

// ...

$KPI = new KPI('KPIName');
// ...
$KPI->logStep('something happened');
// ...
$KPI->logStep('something else happened');
// ...
$KPI->log();

每个KPI都记录在单独的日志文件中,该文件位于以下位置,包括时间信息和会话ID

$config->get('app.file.store.dir').'logs/kpi-'.$KPIName.'.csv';

搜索

提供了一个简单的搜索引擎,您可以在应用程序中使用它,它由一个搜索控制器和一个后端抽象搜索API组成,该API基于附加到记录的标签在主活动记录数据库中搜索匹配记录

关键类包括

一个典型用例包含在Alpha附带的内容管理系统(CMS)中。当保存文章记录时,使用回调根据文章的内容(不包括stopwords-large.inistopwords-small.ini文件中包含的停用词)生成一系列标签记录,并通过ONE-TO-MANY关系类型将这些标签记录与文章相关联,并保存到主数据库中。然后对标签表进行搜索,一旦找到匹配的标签,就加载相关的文章记录。

有关示例代码,请查看Alpha\Controller\SearchController类以及Alpha\Test\Controller\SearchControllerTestAlpha\Test\Util\Search\SearchProviderTagsTest单元测试。

安全

Alpha提供了一个静态实用程序类,具有两个用于加密或解密数据的方法

use Alpha\Util\Security\SecurityUtils;

// ...

$encrypted = SecurityUtils::encrypt('Some data');
$decrypted = SecurityUtils::decrypt($encrypted);

该类使用PHP中的OpenSSL扩展来处理加密,使用AES 256算法。使用的密钥是您在配置文件中的security.encryption.key设置中设置的,对于您的应用程序是唯一的。

错误处理

在应用程序中构建自己的错误处理(使用try/catch块)是最佳实践,然而,在未捕获的异常或通用(非致命)PHP错误的情况下,Alpha提供了Alpha\Util\ErrorHandlers类,应在引导文件中如下使用

set_exception_handler('Alpha\Util\ErrorHandlers::catchException');
set_error_handler('Alpha\Util\ErrorHandlers::catchError', $config->get('php.error.log.level'));

《catchException()》方法捕获未捕获的异常对象并将其记录到应用程序日志(消息和堆栈跟踪)。《catchError()》方法捕获一般的PHP错误并将其转换为《Alpha\Exception\PHPException》对象,然后抛出以被《catchException()》捕获并记录。

联系

有关错误报告和功能请求,请发送电子邮件至:dev@alphaframework.org

在Twitter上: @alphaframework