Espiral: marco PHP / Go de alto rendimiento



Hola Habr Mi nombre es Anton Titov, CTO de Spiral Scout. Hoy me gustaría contarles sobre nuestro elefante PHP. O, más bien, sobre la segunda versión del marco PHP / Go de código abierto de pila completa: Spiral .

Spiral es un marco de componentes de pila completa desarrollado por nuestra empresa durante más de once años y que sirve a cientos de proyectos reales. El paquete de software se basa en muchas bibliotecas abiertas y propietarias, incluidas RoadRunner y Cycle ORM .

El marco es compatible con la mayoría de las recomendaciones de PSR, admite MVC y funciona 5-10 veces más rápido que Laravel / Symfony.

Si nunca ha oído hablar de Spiral y se pregunta qué es el framework PHP / Go y dónde fue la primera versión, bienvenido a cat.

Sobre el marco


El desarrollo en espiral comenzó en 2008/09 como un núcleo portátil para aplicaciones independientes. En 2010, finalmente formamos una empresa de outsourcing y desde entonces nos hemos dedicado a mejorar nuestra pila.

Como resultado, el marco se transformó en un conjunto de componentes independientes, unidos por una capa de integración común.

El único anuncio público de la primera versión ocurrió en Reddit en 2017 y nos dejó en claro que, técnicamente, Spiral realmente no venció a los competidores en ese momento. Tomamos en cuenta los comentarios que recibimos, la experiencia de proyectos más complejos y completamos la segunda versión en mayo de 2019.

Después de un año de documentación y ejecución de proyectos reales, estamos listos para presentar este desarrollo al público.

La principal diferencia entre Spiral 2.0 de la generación anterior del marco y, quizás, de todos los demás marcos PHP existentes es el servidor de aplicaciones integrado RoadRunner, así como la adaptación de la arquitectura para un modelo de ejecución de larga duración (modo demonio).

Rantheim híbrido


El concepto principal del marco es la simbiosis entre el servidor de aplicaciones escrito en Golang y el núcleo de PHP. El código de la aplicación PHP se carga solo una vez en la memoria, de esta manera obtiene importantes ahorros de recursos, pero pierde la capacidad de ejecutar WordPress.



Puede leer más sobre el modelo híbrido aquí y aquí .

El servidor es responsable de toda la parte de la infraestructura: HTTP / FastCGI, comunicación con los intermediarios de colas, GRPC, WebSockets, Pub / Sub, caché, métricas, etc.

Escribir código para Spiral prácticamente no es diferente del código para cualquier otro marco. Sin embargo, los principios SÓLIDOS deberán ser más responsables. Si el almacenamiento en caché anterior de los datos del usuario en singleton causó solo un dolor severo para la revisión del código, ahora dicho código simplemente no funcionará correctamente.

No se requiere conocimiento de Golang para trabajar con la plataforma. Sin embargo, después de haber aprendido un segundo idioma, puede crear integraciones casi perfectas con las bibliotecas de Golang. Por ejemplo, incrustar un motor de búsqueda de texto completo o escribir su propio controlador Kafka.

Disparar en la pierna fue un poco más difícil, se decidió abandonar el sistema de eventos y ganchos y priorizar el trabajo con la pila de llamadas de forma explícita.

El marco proporciona un conjunto de herramientas, como cierres de IoC, middleware, spoilers de capa de dominio y servicios inmutables.

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

Aunque este enfoque impone una sobrecarga adicional, la capacidad de no descargar el núcleo del programa, las conexiones a la base de datos y los sockets de la memoria cubren más que estas limitaciones.

Actuación


En la clase full-stack de frameworks PHP, Spiral compite principalmente con ensambles basados ​​en Swoole y varios micro-frameworks.



Los puntos de referencia completos están disponibles aquí y aquí .

Para nosotros, el rendimiento es un efecto secundario de la arquitectura elegida. Estamos seguros de que con una configuración adecuada y reemplazando el PSR-7 con una abstracción más ligera, la productividad se puede aumentar en un 50-80% (esto se demuestra con el ejemplo de ubicuidad-roadrunner ).

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

RoadRunner ( ), , Windows.

Spiral Swoole, !

PSR-*


La mayoría de los componentes espirales son opcionales para su construcción, la diferencia entre la construcción micro y completa está solo en el contenido composer.json. Si es necesario, puede usar las interfaces para reemplazar las bibliotecas estándar con implementaciones alternativas.

La capa HTTP del marco está escrita teniendo en cuenta los estándares PSR-7/15/17, puede cambiar de forma segura el enrutador , la implementación de mensajes, etc.

La mayoría de las bibliotecas de framework se pueden usar fuera del framework. Por ejemplo, RoadRunner funciona muy bien con Symfony y Laravel , y Cycle ORM estará disponible en Yii3.

Componentes del servidor


Además de los componentes PHP, el ensamblaje RoadRunner incluye varias bibliotecas escritas en Golang. La mayoría de los servicios de servidor se pueden administrar desde PHP.

En particular, hay un componente de cola que admite el trabajo con los corredores AMQP, Amazon SQS y Beanstalk. La biblioteca puede detener, reconectar y distribuir correctamente cualquier cantidad de colas entrantes a varios trabajadores.

Fuera de la caja hay monitoreo en Prometheus y puntos de verificación de salud, un reinicio en caliente y un límite de memoria. Para proyectos distribuidos, hay un servidor GRPC y un cliente. Hay zócalos web, pueden autorizarse desde una aplicación PHP y conectarse al bus pub-sub (en memoria o en Redis). Los controladores clave-valor se están probando actualmente.

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




Portabilidad


El marco no requiere PHP-FPM y NGINX. Y todos los componentes de Golang tienen controladores para trabajar sin dependencias externas. Por lo tanto, puede usar colas, sockets web, métricas sin instalar intermediarios o programas externos.

./spiral serve -v -d

Espiral no importa si escribe una aplicación distribuida enorme o un sitio pequeño que envía "escríbanos" en segundo plano. En cualquier caso, puede usar las mismas herramientas, unificando el comportamiento de los entornos locales y de producción.

Dado que la capa HTTP es opcional, puede escribir aplicaciones de consola que procesen datos en segundo plano utilizando un paquete de cola. Utilizamos tales programas para la migración de datos.

Ciclo ORM


Como un ORM fuera de la caja viene Cycle ORM . Este motor de Data Mapper es muy similar en función a Doctrine, pero muy diferente arquitectónicamente.

Al igual que Doctrine, Cycle puede trabajar con modelos de dominio puro, generando migraciones de forma independiente y organizando claves foráneas. El esquema de mapeo puede describirse por código o recopilarse a partir de anotaciones. Pero en lugar de DQL, se utilizan los clásicos Constructores de consultas.

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

El ciclo se ejecuta más rápido que Doctrine en las selecciones, pero más lento en persistir. El motor admite consultas complejas con varias estrategias de carga, clases de proxy, incrustaciones y proporciona transacciones portátiles en lugar del EntityManager global.

Se pueden escuchar más detalles en PHP Rusia 2020 .

La "característica" principal de ORM es la capacidad de cambiar la asignación de datos y enlaces en tiempo de ejecución. En palabras simples, puede permitir que los usuarios definan independientemente el esquema de datos (DBAL admite la introspección y la declaración de esquemas de bases de datos).

Puede leer más sobre la comparación de Ciclo, Elocuente y Doctrina 2 aquí .

Creación rápida de prototipos


Spiral incluye varias herramientas para acelerar y simplificar el desarrollo. Los principales son la inyección de autodependencia, la configuración automática y la búsqueda automática de modelos mediante análisis estático. Los comandos de consola le permiten generar la mayoría de las clases requeridas y son fácilmente personalizables.

Para la integración en el IDE hay un sistema de creación rápida de prototipos. Usando el prototipo mágico de Retrato, puede obtener acceso rápido a consejos en el IDE.



El rasgo encuentra automáticamente repositorios ORM, componentes estándar y puede indexar sus servicios mediante anotaciones. Debajo del capó, se utiliza el método mágico __get , que le garantiza una revisión rápida del código.

Simplemente ejecute el comando `php app.php prototype: inject -r`, y el sistema de creación de prototipos eliminará automáticamente toda la magia:

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

Bajo el capó, se utiliza PHP-Parser .

La seguridad


Dado que la mayoría de nuestras aplicaciones están desarrolladas para el segmento B2B, los asuntos de seguridad deben tomarse en serio.

Los componentes para validar solicitudes complejas (filtros de solicitud), CSRF y cifrado (basados ​​en cifrado desactivado / php ) estarán disponibles para usted . El trabajo con cookies y sesión admite anti manipulación y firma de datos en el lado del servidor.

El marco proporciona un componente de autenticación basado en token que caduca. Como controlador, puede usar sesiones, una base de datos o incluso JWT puro.

O todos los tipos de tokens al mismo tiempo, si el requisito "¡conecte SAML / SSO / 2FA con urgencia!" Vuela repentinamente hacia usted :(

La autorización de acceso se realiza a través del componente RBAC con algunas mejoras que permiten la operación en modo DAC y ABAC. Hay soporte para muchos roles, anotaciones para proteger los métodos del controlador y un sistema de reglas.

El trabajo con la capa de dominio se produce a través de una capa interceptora intermedia . Por lo tanto, puede crear restricciones especiales en un grupo de controladores, validar previamente datos y errores de ajuste.

Motor de plantillas


Si solo le gusta Twig, puede desplazarse por esta sección, simplemente instale la extensión y use herramientas familiares. :)

Fuera de la caja viene el motor de plantillas Stemper, o más bien, una biblioteca para crear su propio marcado DSL. En particular, hay un lexer completo, varias gramáticas, un analizador y acceso a AST (similar al PHP-Parser de Nikita).

Es posible analizar varias gramáticas anidadas. Entonces, por ejemplo, puede usar las directivas de Laravel Blade y su propio marcado DSL (en forma de etiquetas HTML) dentro de la misma plantilla. Resulta algo así como componentes web en el lado del servidor.

Utilizamos este componente para describir interfaces complejas usando primitivas y reglas 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>

Es compatible con el escape automático con soporte de contexto (por ejemplo, la salida de PHP dentro del bloque JS convierte automáticamente los datos a JSON), mapas de origen para trabajar con errores. Las plantillas se compilan en código PHP optimizado y luego se entregan directamente desde la memoria de la aplicación.
Stemper puede funcionar completamente con el DOM del documento (aunque más lento si utiliza herramientas especializadas).

Desarrollo de marcos


Por supuesto, en más de una década de desarrollo de esta herramienta, hemos cometido muchos errores. Algunos de ellos fueron corregidos para el lanzamiento de la segunda versión, mientras que la otra parte requiere una revisión del concepto de algunos componentes.

Tampoco negaremos que algunas cosas requieran un archivo. Y algunas preguntas pueden sonar para nosotros por primera vez. Aunque la documentación intenta cubrir el máximo de componentes, aún puede ceder en algunos lugares.

Estamos abiertos a críticas y sugerencias, ya que nosotros mismos estamos utilizando activamente esta herramienta. Nuestro equipo y la comunidad están resolviendo lentamente la enorme acumulación de mejoras.

Se necesitará mucho trabajo para mejorar y acelerar el ciclo, así como reescribir los filtros de solicitud de acuerdo con los últimos RFC. En desarrollo se encuentra un componente para trabajar y emular bases de datos Key-Value.

Simplemente no logramos traducir muchas cosas de la primera versión. Los planes son restaurar paquetes ODM, panel de administración, escribir un buen perfilador, etc.

Comunidad y enlaces


Puede iniciar sesión en nuestra pequeña comunidad Discord . Canal de telegramas .


Todo el código se distribuye bajo la licencia MIT y no tiene restricciones para uso comercial.

Gracias por la atención. ¡Espero que nuestras herramientas sean útiles en sus proyectos!

All Articles