Spiral: framework PHP / Go hautes performances



Bonjour, Habr. Je m'appelle Anton Titov, directeur technique de Spiral Scout. Aujourd'hui, je voudrais vous parler de notre éléphant PHP. Ou plutôt, sur la deuxième version du framework PHP / Go open-source full-stack - Spiral .

Spiral est un framework full-stack de composants développé par notre entreprise depuis plus de onze ans et servant une centaine de projets réels. Le progiciel est basé sur de nombreuses bibliothèques ouvertes et propriétaires, y compris RoadRunner et Cycle ORM .

Le cadre est compatible avec la plupart des recommandations PSR, prend en charge MVC et fonctionne 5 à 10 fois plus rapidement que Laravel / Symfony.

Si vous n'avez jamais entendu parler de Spiral et que vous vous demandez ce qu'est le framework PHP / Go et où est passée la première version, alors bienvenue sur cat.

À propos du cadre


Le développement en spirale a commencé en 2008/09 en tant que noyau portable pour les applications indépendantes. En 2010, nous avons finalement formé une société d'externalisation et nous nous sommes depuis engagés dans l'amélioration de notre stack.

En conséquence, le cadre a été transformé en un ensemble de composants indépendants, unis par une couche d'intégration commune.

La seule annonce publique de la première version a eu lieu sur Reddit en 2017 et nous a clairement montré que, techniquement, Spiral ne battait pas vraiment les concurrents à l'époque. Nous avons pris en compte les retours que nous avons reçus, l'expérience de projets plus complexes et terminé la deuxième version en mai 2019.

Après un an de documentation et de fonctionnement sur de vrais projets, nous sommes prêts à soumettre ce développement au public.

La principale différence entre Spiral 2.0 de la génération précédente du framework et, peut-être, de tous les autres frameworks PHP existants est le serveur d'applications intégré RoadRunner, ainsi que l'adaptation de l'architecture pour un modèle d'exécution à longue durée de vie (mode démon).

Hybride Rantheim


Le concept principal du framework est la symbiose entre le serveur d'applications écrit en Golang et le noyau PHP. Le code d'application PHP n'est chargé qu'une seule fois dans la mémoire - de cette façon, vous économisez beaucoup de ressources, mais perdez la possibilité d'exécuter WordPress.



Vous pouvez en savoir plus sur le modèle hybride ici et ici .

Le serveur est responsable de toute la partie infrastructure: HTTP / FastCGI, communication avec les courtiers de file d'attente, GRPC, WebSockets, Pub / Sub, cache, métriques, etc.

L'écriture de code pour Spiral n'est pratiquement pas différente du code pour tout autre framework. Cependant, les principes SOLIDES devront être plus responsables. Si auparavant la mise en cache des données utilisateur dans singleton n'a causé que de graves difficultés pour la révision du code, maintenant un tel code ne fonctionnera tout simplement pas correctement.

La connaissance de Golang n'est pas requise pour travailler avec la plateforme. Cependant, après avoir appris une deuxième langue, vous pouvez créer des intégrations presque transparentes avec les bibliothèques Golang. Par exemple, intégrez un moteur de recherche en texte intégral ou écrivez votre propre pilote Kafka.

Tirer dans la jambe était un peu plus difficile, il a été décidé d'abandonner le système d'événements et de hooks et de prioriser le travail avec la pile d'appels sous une forme explicite.

Le cadre fournit un ensemble d'outils, tels que les fermetures IoC, les middlewares, les spoilers de couche de domaine et les services immuables.

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

Bien qu'une telle approche impose une surcharge supplémentaire, la possibilité de ne pas décharger le cœur du programme, les connexions à la base de données et les sockets de la mémoire couvre plus que ces limitations.

Performance


Dans la classe full-stack des frameworks PHP, Spiral est principalement en concurrence avec les assemblages basés sur Swoole et plusieurs micro-frameworks.



Des benchmarks complets sont disponibles ici et ici .

Pour nous, la performance est un effet secondaire de l'architecture choisie. Nous sommes sûrs qu'avec une configuration appropriée et en remplaçant le PSR-7 par une abstraction plus légère, la productivité peut être augmentée de 50 à 80% (cela est prouvé par l'exemple de l' ubiquité-roadrunner ).

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

RoadRunner ( ), , Windows.

Spiral Swoole, !

PSR-*


La plupart des composants Spiral sont facultatifs pour votre version, la différence entre la version micro et la version complète ne concerne que le contenu composer.json. Si nécessaire, vous pouvez utiliser les interfaces pour remplacer les bibliothèques standard par des implémentations alternatives.

La couche HTTP du framework est écrite en tenant compte des standards PSR-7/15/17, vous pouvez changer en toute sécurité le routeur , l'implémentation des messages, etc.

La plupart des bibliothèques de frameworks peuvent être utilisées en dehors du framework. Par exemple, RoadRunner fonctionne très bien avec Symfony et Laravel , et Cycle ORM sera disponible dans Yii3.

Composants serveur


En plus des composants PHP, l'assemblage RoadRunner comprend plusieurs bibliothèques écrites en Golang. La plupart des services serveur peuvent être gérés depuis PHP.

En particulier, il existe un composant de file d'attente qui prend en charge le travail avec les courtiers AMQP, Amazon SQS et Beanstalk. La bibliothèque peut correctement arrêter, reconnecter et distribuer n'importe quel nombre de files d'attente entrantes à plusieurs travailleurs.

Dès la sortie de la boîte, il y a une surveillance sur Prometheus et des points de contrôle de santé, un redémarrage à chaud et une limite de mémoire. Pour les projets distribués, il existe un serveur et un client GRPC. Il existe des sockets web, elles peuvent être autorisées depuis une application PHP et connectées au bus pub-sub (en mémoire ou sur Redis). Les pilotes de valeur-clé sont actuellement testés.

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




Portabilité


Le framework ne nécessite pas PHP-FPM et NGINX. Et tous les composants Golang ont des pilotes pour fonctionner sans dépendances externes. Ainsi, vous pouvez utiliser des files d'attente, des sockets Web, des métriques sans installer de courtiers ou de programmes externes.

./spiral serve -v -d

La spirale n'a pas d'importance si vous écrivez une énorme application distribuée ou un petit site envoyant "écrivez-nous" en arrière-plan. Dans tous les cas, vous pouvez utiliser les mêmes outils, unifiant le comportement des environnements locaux et de production.

La couche HTTP étant facultative, vous pouvez écrire des applications de console qui traitent les données en arrière-plan à l'aide d'un package de files d'attente. Nous utilisons de tels programmes pour la migration des données.

Cycle ORM


En tant qu'ORM prêt à l'emploi, vient le cycle ORM . Ce moteur Data Mapper est très similaire en fonction de Doctrine, mais très différent sur le plan architectural.

Comme Doctrine, Cycle peut fonctionner avec des modèles de domaine purs, générant indépendamment des migrations et organisant des clés étrangères. Le schéma de mappage peut être décrit par code ou collecté à partir d'annotations. Mais au lieu de DQL, des constructeurs de requêtes classiques sont utilisés.

//    
//        
$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();

Le cycle s'exécute plus rapidement que Doctrine sur les sélections, mais plus lentement persiste. Le moteur prend en charge les requêtes complexes avec plusieurs stratégies de chargement, classes proxy, incorporations et fournit des transactions portables au lieu du EntityManager global.

Plus de détails peuvent être entendus à PHP Russie 2020 .

La principale «fonctionnalité» d'ORM est la possibilité de modifier le mappage des données et des liens lors de l'exécution. En termes simples, vous pouvez permettre aux utilisateurs de définir indépendamment le schéma de données (DBAL prend en charge l'introspection et la déclaration des schémas de base de données).

Vous pouvez en savoir plus sur la comparaison de Cycle, Eloquent et Doctrine 2 ici .

Prototypage rapide


Spiral comprend plusieurs outils pour accélérer et simplifier le développement. Les principaux sont l'injection d'auto-dépendance, la configuration automatique et la recherche automatique de modèle utilisant l'analyse statique. Les commandes de la console vous permettent de générer la plupart des classes requises et sont facilement personnalisables.

Pour l'intégration dans l'IDE, il existe un système de prototypage rapide. En utilisant le magique PrototypeTrait, vous pouvez accéder rapidement à des astuces dans l'EDI.



Le trait trouve automatiquement les référentiels ORM, les composants standard et peut indexer vos services par des annotations. Sous le capot, la méthode magique __get est utilisée , ce qui vous garantit une révision rapide du code.

Exécutez simplement la commande `php app.php prototype: inject -r`, et le système de prototypage supprimera automatiquement toute la magie:

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')
        ]);
    }
}

Sous le capot, PHP-Parser est utilisé .

sécurité


La plupart de nos applications étant développées pour le segment B2B, les questions de sécurité doivent être prises au sérieux.

Des composants pour valider les demandes complexes (Filtres de demande), CSRF et chiffrement (basé sur le chiffrement par désamorçage / php ) seront à votre disposition . Le travail avec les cookies et la session prennent en charge la protection contre la falsification et la signature des données côté serveur.

Le cadre fournit un composant d'authentification basé sur un jeton expirant. En tant que pilote, vous pouvez utiliser des sessions, une base de données ou même du JWT pur.

Ou tous les types de jetons en même temps, si l'exigence "connectez de toute urgence SAML / SSO / 2FA!" Vole soudainement vers vous :(

L'autorisation d'accès est effectuée via le composant RBAC avec quelques améliorations qui permettent un fonctionnement en mode DAC et ABAC. De nombreux rôles sont pris en charge, des annotations pour protéger les méthodes du contrôleur et un système de règles.

Le travail avec la couche domaine se fait via une couche intercepteur intermédiaire . Vous pouvez donc créer des restrictions spéciales sur un groupe de contrôleurs, pré-valider les données et envelopper les erreurs.

Moteur de modèle


Si vous n'aimez que Twig - vous pouvez faire défiler cette section, installez simplement l'extension et utilisez des outils familiers. :)

Le moteur de modèle Stemper, ou plutôt, une bibliothèque pour créer votre propre balisage DSL est sorti de la boîte. En particulier, il existe un lexer à part entière, plusieurs grammaires, un analyseur et un accès à AST (similaire au PHP-Parser de Nikita).

Il est possible d'analyser plusieurs grammaires imbriquées. Ainsi, par exemple, vous pouvez utiliser les directives Laravel Blade et votre propre balisage DSL (sous la forme de balises HTML) dans le même modèle. Il s'avère quelque chose comme des composants Web côté serveur.

Nous utilisons ce composant pour décrire des interfaces complexes à l'aide de primitives et de règles simples.

<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>

Il prend en charge l'échappement automatique avec prise en charge du contexte (par exemple, la sortie de PHP à l'intérieur du bloc JS convertit automatiquement les données en JSON), les cartes source pour travailler avec les erreurs. Les modèles sont compilés en code PHP optimisé puis donnés directement à partir de la mémoire de l'application.
Stemper peut fonctionner pleinement avec le DOM du document (bien que plus lent si vous utilisez des outils spécialisés).

Développement du cadre


Bien sûr, en plus d'une décennie de développement de cet outil, nous avons commis de nombreuses erreurs. Certains d'entre eux ont été corrigés pour la sortie de la deuxième version, tandis que l'autre partie nécessite une révision du concept de certains composants.

Nous ne nierons pas non plus que certaines choses nécessitent un fichier. Et certaines questions peuvent nous sembler pour la première fois. Bien que la documentation essaie de couvrir le maximum de composants, elle peut encore s'affaisser à certains endroits.

Nous sommes ouverts aux critiques et aux suggestions, car nous-mêmes utilisons activement cet outil. L'énorme arriéré d'améliorations est lentement trié par notre équipe et la communauté.

Beaucoup de travail sera nécessaire pour améliorer et accélérer le cycle, ainsi que pour réécrire les filtres de demande selon les derniers RFC. En cours de développement, un composant permet de travailler et d'émuler des bases de données de valeurs-clés.

Nous n'avons tout simplement pas réussi à traduire beaucoup de choses depuis la première version. Les plans sont de restaurer les packages ODM, le panneau d'administration, d'écrire un bon profileur, etc.

Communauté et liens


Vous pouvez vous connecter à notre petite communauté Discord . Canal télégramme .


Tout le code est distribué sous la licence MIT et n'a aucune restriction pour une utilisation commerciale.

Merci pour l'attention. J'espère que nos outils vous seront utiles dans vos projets!

All Articles