螺旋:高性能PHP / Go框架



哈Ha 我叫Anton Titov,螺旋侦察兵CTO。今天,我想向您介绍我们的PHP大象。更确切地说,关于开源全栈PHP / Go框架的第二版-Spiral

Spiral是由我公司开发超过11年的组件完整堆栈框架,可为100个实际项目提供服务。该软件包基于许多开放和专有的库,包括RoadRunnerCycle ORM

该框架与大多数PSR建议兼容,支持MVC,运行速度比Laravel / Symfony快5-10倍。

如果您从未听说过Spiral,并且想知道PHP / Go框架是什么以及第一个版本的发布地,那么欢迎加入。

关于框架


螺旋开发始于2008/09年,是针对自由职业者应用程序的便携式内核。 2010年,我们终于成立了一家外包公司,此后一直致力于改善我们的堆栈。

结果,该框架被转换为一组独立的组件,并由一个公共集成层联合在一起。

第一版的唯一公告是于2017年在Reddit上发布的,并向我们表明,从技术上讲,Spiral当时并没有真正击败竞争对手。我们考虑了收到的反馈,更复杂项目的经验,并于2019年5月完成了第二版。

经过一年的文档编制并在实际项目上运行,我们准备将这一开发成果提交给公众。

与上一代框架的Spiral 2.0以及可能与所有其他现有PHP框架之间的主要区别在于集成的应用程序服务器RoadRunner,以及针对长期执行模型(守护程序模式)进行的体系结构调整。

杂种兰特海姆


该框架的主要概念是用Golang编写的应用服务器和PHP核心之间的共生。PHP应用程序代码仅一次加载到内存中-这样,您可以节省大量资源,但会失去运行WordPress的能力。



您可以在此处此处阅读有关混合模型的更多信息

服务器负责整个基础架构部分:HTTP / FastCGI,与队列代理,GRPC,WebSockets,发布/订阅,缓存,指标等的通信。

实际上,为Spiral编写代码与任何其他框架的代码没有什么不同。但是,SOLID原则将必须承担更多责任。如果以前以单例形式缓存用户数据仅导致代码检查时非常痛苦,那么现在这样的代码将无法正常工作。

使用该平台不需要Golang知识。但是,学习了第二语言后,您就可以与Golang库建立几乎无缝的集成。例如,嵌入全文搜索引擎或编写自己的Kafka驱动程序。

用腿射击要困难一些,因此决定放弃事件和钩子系统,并以显式的形式优先处理调用堆栈。

该框架提供了一组工具,例如IoC闭包,中间件,域层破坏者和不可变服务。

$container->runScope(
    [UserContext::class => $user],
    function () use ($container) {
        dump($container->get(UserContext::class);
    }
);

尽管这种方法带来了额外的开销,但是不卸载程序核心,从数据库到数据库的连接以及套接字的能力远远超过了这些限制。

性能


在PHP框架的全栈类中,Spiral主要与基于Swoole的程序集和一些微框架竞争。



完整的基准测试可在此处此处获得

对我们来说,性能是所选架构的副作用。我们确信,通过适当的配置并用更浅的抽象替换PSR-7,可以将生产率提高50-80%(这已在ubiquity-roadrunner的示例中得到了证明)。

Swoole. Swoole RoadRunner, PHP ++. , Go. , .

RoadRunner ( ), , Windows.

Spiral Swoole, !

PSR-*


大多数Spiral组件对于您的构建都是可选的,微型构建和完整构建之间的区别仅在于内容composer.json如有必要,您可以使用接口将标准库替换为替代实现。

编写框架的HTTP层时要考虑到PSR-7 / 15/17标准,您可以安全地更改路由器,消息的实现等。

大多数框架库都可以在框架之外使用。例如,RoadRunner可以与SymfonyLaravel一起很好地工作,而Cycle ORM将在Yii3中可用。

服务器组件


除了PHP组件之外,RoadRunner程序集还包含几个用Golang编写的库。大多数服务器服务都可以在PHP中进行管理。

特别是,存在一个队列组件,该组件支持与代理AMQP,Amazon SQS和Beanstalk一起使用。该库可以正确停止,重新连接并将任意数量的传入队列分配给多个工作程序。

开箱即用的是对Prometheus和运行状况检查点的监视,热重启和内存限制。对于分布式项目,有一个GRPC服务器和客户端。 有Web套接字,可以从PHP应用程序授权它们,并连接到pub-sub总线(在内存中或Redis上)。键值驱动程序当前正在测试中。

INFO[0154] 10.42.5.55:51990 Ok {2.28ms} /images.Service/GetFiles
INFO[0155] 10.42.3.95:50926 Ok {11.3ms} /images.Service/GetFiles
INFO[0156] 10.42.5.55:52068 Ok {3.60ms} /images.Service/GetFiles
INFO[0158] 10.42.5.55:52612 Ok {2.30ms} /images.Service/GetFiles
INFO[0166] 10.42.5.55:52892 Ok {2.23ms} /images.Service/GetFiles
INFO[0167] 10.42.3.95:49938 Ok {2.37ms} /images.Service/GetFiles
INFO[0169] 10.42.5.55:52988 Ok {2.22ms} /images.Service/GetFiles




可移植性


该框架不需要PHP-FPM和NGINX。并且所有Golang组件都具有无需外部依赖即可工作的驱动程序。因此,您可以使用队列,WebSocket,指标,而无需安装外部代理或程序。

./spiral serve -v -d

如果您在后台编写大型分布式应用程序或发送“写信给我们”的小型网站,螺旋式技术都没关系。无论如何,您都可以使用相同的工具来统一本地和生产环境的行为。

由于HTTP层是可选的,因此您可以编写使用队列程序包在后台处理数据的控制台应用程序。我们使用此类程序进行数据迁移。

循环ORM


作为开箱即用的ORM,请输入Cycle ORM该Data Mapper引擎的功能与Doctrine非常相似,但在体系结构上却非常不同。

与Doctrine一样,Cycle可以与纯领域模型一起使用,独立生成迁移并安排外键。映射方案可以用代码描述或从注释中收集。但是使用经典的查询生成器代替DQL。

//    
//        
$users = $orm->getRepository(User::class)
    ->select()
    ->where('active', true)
    ->load('orders', [
        'method' => Select::SINGLE_QUERY, // force LEFT JOIN
        'load'   => function($query) {
            $query->where('paid', true)->orderBy('timeCreated', 'DESC');
        }
    ])
    ->fetchAll();

$transaction = new Transaction($orm);

foreach($users as $user) {
    $transaction->persist($user);
}

$transaction->run();

在选择中, 循环的运行速度比 Doctrine 要快,但在持久性方面,运行速度要慢。该引擎通过几种加载策略,代理类,嵌入支持复杂的查询,并提供可移植的事务而不是全局EntityManager。

有关更多详细信息,请参见PHP Russia 2020

ORM的主要“功能”是在运行时更改数据和链接的映射的能力。简而言之,您可以允许用户独立定义数据模式(DBAL支持数据库模式的自省和声明)。

您可以在此处阅读有关比较“周期”,“口才”和“教义2”的更多信息

快速成型


Spiral包括多种工具,可以加快和简化开发。主要包括自动依赖注入,自动配置和使用静态分析的自动模型搜索。控制台命令使您可以生成大多数必需的类,并且可以轻松自定义。

为了集成到IDE中,有一个快速的原型制作系统。使用神奇的PrototypeTrait,您可以快速访问IDE中的提示。



该特征会自动找到ORM存储库,标准组件,并可以通过注释为您的服务建立索引。后台,使用了神奇的__get方法,可以确保您快速进行代码检查。

只需运行命令php app.php prototype:inject -r,原型系统将自动删除所有魔术:

namespace App\Controller;

use App\Database\Repository\UserRepository;
use Spiral\Views\ViewsInterface;

class HomeController
{
    /** @var ViewsInterface */
    private $views;

    /** @var UserRepository */
    private $users;

    /**
     * @param ViewsInterface $views
     * @param UserRepository $users
     */
    public function __construct(ViewsInterface $views, UserRepository $users)
    {
        $this->users = $users;
        $this->views = $views;
    }

    public function index()
    {
        return $this->views->render('profile', [
            'user' => $this->users->findByName('Antony')
        ]);
    }
}

在后台,使用PHP-Parser

安全


由于我们的大多数应用程序都是针对B2B领域开发的,因此必须认真考虑安全问题。

验证复杂请求(请求过滤器),CSRF和加密(基于defuse / php-encryption)的组件将向您提供使用cookie和会话支持服务器端数据的防篡改和签名。

该框架提供了一个基于令牌的过期身份验证组件作为驱动程序,您可以使用会话,数据库甚至纯JWT。

或同时所有类型的令牌(如果要求“紧急连接SAML / SSO / 2FA!”)突然发给您 :(

访问授权通过RBAC组件执行,并进行了一些改进,以允许在DAC和ABAC模式下运行。支持许多角色,用于保护控制器方法的注释以及规则系统。

域层的工作是通过中间拦截器层进行的因此,您可以在一组控制器上创建特殊限制,预先验证数据并包装错误。

模板引擎


如果只喜欢Twig,则可以滚动浏览此部分,只需安装扩展程序并使用熟悉的工具即可。:)

开箱即用的是Stemper模板引擎,或者说是一个用于创建自己的DSL标记的库。特别是,有一个完整的词法分析器,几种语法,一个解析器和对AST的访问(类似于Nikita的PHP解析器)。

可以解析几个嵌套的语法。因此,例如,您可以在同一模板中使用Laravel Blade指令和您自己的DSL(以HTML标签的形式)标记。事实证明,服务器端类似Web组件。

我们使用此组件使用简单的原语和规则来描述复杂的接口。

<extends:admin.layout.tabs title="User Information"/>
<use:bundle path="admin/bundle"/>

<ui:tab id="info" title="Information">
  User, {{ $user->name }}
</ui:tab>

<ui:tab id="data" title="User Settings">
  <grid:table for={{ $user->settings }}>
    <grid:cell title="Key">{{ $key }}</grid:cell>
    <grid:cell title="Value">{{ $value }}</grid:cell>
  </grid:table>
</ui:tab>

它支持带有上下文支持的自动转义(例如,JS块内的PHP输出自动将数据转换为JSON),用于处理错误的源映射。模板被编译成优化的PHP代码,然后直接从应用程序内存中给出。
Stemper可以完全使用文档的DOM(如果使用专用工具,则速度会较慢)。

框架开发


当然,在开发此工具的十多年中,我们犯了许多错误。其中一些已针对第二个版本的发布进行了更正,而其他部分则需要对某些组件的概念进行修订。

我们也不会否认某些事情需要文件。有些问题可能是我们第一次听到。尽管文档试图涵盖最多的组件,但它仍可能在某些地方下垂。

我们愿意接受批评和建议,因为我们自己一直在积极使用此工具。我们的团队和社区正在逐步整理出大量积压的改进。

根据最新的RFC,需要大量工作来改善和加快周期,以及重写请求过滤器。正在开发中的是用于工作和模拟键值数据库的组件。

我们只是没有设法翻译第一版中的许多内容。计划是还原ODM软件包,管理面板,编写良好的分析器等。

社区与链接


您可以登录到我们的小型Discord社区电报频道


所有代码均根据MIT许可分发,并且对商业用途没有任何限制。

谢谢您的关注。希望我们的工具在您的项目中派上用场!

All Articles