Librairie Frash - Framework PHP 7

Default
,

Bonjour,

Je suis un étudiant en développement web, et passionné par PHP principalement, j'ai commencé à faire mon framework il y a un an.

Mon objectif, depuis le début du développement, est de pouvoir tout développer par moi-même. Et ça m'a servit, notamment à appréhender les regex, entre autres (Reflection Class, ...). Au début, ma seule "antorse" a été l'inclusion de TWIG, puis il y a peu, j'ai commencé à inclure mon propre moteur de templates au framework.

Mon framework vise à être un "all-in-one", comme je le qualifie. Tous les composants indépendants que l'on retrouverait dans un projet symfony, allant du framework au gestionnaire de tests unitaires, seraient développés et intégrés à mon framework.

Au début, je n'utilisais pas Packagist, aujourd'hui, j'ai trois repos Github pour mon framework, frash-install, avec lequel on va pouvoir installer le framework -- le deuxième repo --, frash, et le troisième, frash-docs, contenant pour l'instant un début de documentation en markdown, et en français.

Concernant les liens...
frash-install : https://github.com/AlixSperoza/frash-install
frash : https://github.com/AlixSperoza/frash
frash-docs : https://github.com/AlixSperoza/frash-docs

Si vous avez un intérêt pour mon framework, je place ce lien : https://github.com/AlixSperoza/InterfaceGestionArmy
C'est un projet personnel m'aidant à roder mon framework et à développer les fonctionnalités. Il peut également vous servir à l'utiliser, en attendant une documentation complète.

Concernant la description de certaines fonctionnalités, par exemple le Dependency Injection Container, il est transféré en paramètre à toutes les fonctionnalités, à chaque fois. Il est instancié dans le front-controller, est passé au router, qui le passe au controller et à l'action correspondant à la route, dans l'action, le DIC servira à instancier le moteur de template, la création de formulaire, ou une autre fonctionnalité.

J'ai une "centaine" de pistes d'amélioration pour mon framework, pour la console, le gestionnaire de tests unitaires, l'ORM, etc.
Toutefois, pour certaines fonctionnalités, telles que l'équivalent du BrowserKit de Symfony ou la création d'un mock, deux fonctionnalités pouvant servir pour les tests unitaires, je ne pense pas avoir encore les compétences, ou tout du moins, je ne sais pas trop comment faire.

Pour ces deux fonctionnalités, ou si vous voulez m'aider, j'ai activé les pull requests. Et je me les auto-valide pour l'instant.

Si vous souhaitez que je vous détaille un élément du framework, dîtes-le :)

J'espère vous avoir bien présenté mon framework. J'espère qu'il saura vous intéresser.

Merci.

30 Réponse

2
47855
,

@Alix_Speroza Les grand esprits se rencontrent : je viens de faire un PR qui implémente cette méthode __call ;)

Default
,

Comment est appelé call() ?
Je suis pas non plus familier de call_user_func_array :p

47855
,

Je viens de me rendre compte que je me suis gourré, att je modif le code ;)

Edit : voilà c'est fait ;)

Pour ce qui est de la function call_user_func_array, elle prend en premier argument le nom de la fonction (ou, si c'est dans une classe, un tableau contenant la référence à la classe en premier paramètre et le nom de la méthode à appeler en second paramètre) que tu souhaite appeler et en second argument les paramètres que tu souhaite lui passer sous forme de tableau.

Default
,

Je viens de merge ta PR :)

Aussi, j'ai modifié la class OrmFactory, notamment en indiquant les namespace du Counter et du Finder en haut du fichier et non en string. J'ai également supprimé les références à "CreateFormSql", un composant qui verra le jour bien plus tard.

Default
,

Bonjour,

Deux nouvelles features assez importantes sur le framework.

Jusqu'à maintenant, dans le moteur de templates, pour désigner une route, il fallait mettre :

{{ route url }}

Maintenant, on peut également faire de cette manière :

{> url }}

C'est plus court, j'ai laissé la possibilité d'utilisation de la première solution, la syntaxe de la deuxième solution ne plaira peut-être pas à tout le monde ^^

Ensuite, la deuxième feature concerne l'ORM. Jusqu'à maintenant, dans un Repository, il fallait ajouter les class Select, Insert, Update, etc en Namespace pour les utiliser dans nos fonctions, car on pouvait importer une class Select venant du dossier MySQL comme une class Select venant du dossier PGSQL. Maintenant, pour faire initialiser un Select, ce n'est plus :

$sel = new Select(...);

Mais :

$sel = $this->newSelect(...);

La class Repository doit par contre extend le QueryBuilder.
Pour arriver à ce résultat, j'ai unifié les class de requête des dossiers MySQL et PGSQL. Je devais en passer par les fonctions newSelect, newUpdate, ..., pour passer en paramètre de ces class le paramètre system, qui vaut MySQL ou PGSQL. Car la construction de la requête est différente en fonction du système. La diférence de taille est la présence de double guillemets autour du nom de la table et des colonnes dans une requête PGSQL, ce qu'il n'y a pas dans une requête MySQL.

Je vous remercie.

Default
,

Bonjour,

J'ai démarré à côté deux projets basés sur le framework, expliquant que je n'ai pas donné d'indications sur l'avancée ce dernier mois ^^

Je viens donc de valider la PR comprenant la version 1.4.20.
Une fonctionnalité que j'ai rajouté est en lien avec les deux projets parallèles.

Des bundles dans le dossier src de frash/. Je n'ai pas encore transféré la homepage et la bottom bar dans des bundles. Une fois fait, ça me permettra d'enlever des fonctions dédiées à l'affichage de vues spéciales... situées dans le vendor.

J'ai fait une révision du code pour la compatibilité PSR-2.

J'ai également commencé la création d'une API automatique inspirée sur Loopback. Y'a juste le select all de fonctionnel. L'implémentation d'un moyen de sécurisation permettra le développement, et l'utilisation, d'autres types de requêtes (UPDATE, ...).

J'ai enfin mit à jour le générateur de documentation, j'ai corrigé les erreurs. Une prochaine mise à jour du générateur permettra de générer des fichiers plus esthétiques :D

Je vous remercie.

Default
,

Salut à vous :)

Alors, oui, ça fait 7 mois que je n'ai pas posté. Mais je reviens avec quelques ajouts au framework. :)

La prise en charge (partielle) de MongoDB.

use Frash\ORM\MongoDB\QueryBuilder;

class AppRepository extends QueryBuilder
{
    public function updateExample(string $id)
    {
        $upd = $this->newUpdate('database.collection');
        $upd->filter([ '_id' => new \MongoDB\BSON\ObjectID($id) ])->update([ '$push' => [ 'subdocument' => 'value' ]]);

        $this->update($upd);
    }

    public function insertExample(string $name)
    {
        $ins = $this->newInsert('database.collection');
        $ins->setId()->fields([ 'name' => $name ]);

        $this->insert($ins);
    }

    public function findExample(string $id)
    {
        $sel = $this->newSelect('database.collection', [ 'options' => [ 'limit' => 1 ]]);
        $sel->filter([ 'other_id' => $id, 'default' => true ]);

        return $this->find($sel, true);
    }
}

Ca, ce ne sont que des exemples. Dans le findExample(), l'exemple donné est développé pour retourner un seul résultat, enlevons l'array dans le newSelect() et le true dans le $this->find() et ça retournera autant de résultats que Mongo en trouve.

De même, le Delete n'est pas encore prit en charge. Tout comme d'éventuels Finder et Counter que l'on trouve dans l'ORM côté SQL (compatibles MySQL et PGSQL).

La traduction

Désormais, dans une class de Traduction, vous pourrez faire comme ceci :

protected $key_test = 'Bonjour {{ $1 }} !';

Ainsi, dans votre vue, en mettant :

{{ trad key_test Alix_Speroza }}

Vous obtiendrez "Bonjour Alix_Speroza !".

Faker

J'ai commencé le développement d'un faker, il est pour l'instant très basique mais peut générer des données et les enregistrer dans une base de données SQL.

Documentation

C'était un gros défaut de mon framework je pense, l'absence de documentation est paliée pour les fonctionnalités autres que celles listées ici.

Routing

Une petite fonctionnalité, au lieu de faire :

$this->get('route', 'Controller:action');
$this->post('meme_route', 'MemeController:memeAction);

Vous pouvez regrouper ces deux lignes :

$this->many([ 'get', 'post' ], 'route', 'Controller:action');

Filtres du moteur de template

A l'image de TWIG, vous pouvez attribuer un filtre (Pour l'instant) à une variable affichée. Le seul filtre "officiel" disponible pour l'instant permet de passer la variable en paramètre de la fonction rawurlencode(). Bientôt, il y aura ucfirst(), lcfirst(), etc.

Vous pouvez faire vos propres filtres. Dans Configuration\Service, vous devrez renseigner plusieurs informations sur votre filtre :

'templating' => [
    'filter' => [
            'number_format' => [ 'call' => 'nb', 'class' => 'Bundles\AppBundle\Service\NumberFormatFilter' ]
        ],
    'extension' => []
]

La class du filtre donnera :

class NumberFormatFilter
{
    public function define(InitialTemplate $template, $tag)
    {
        return 'number_format('.$tag.', 0, ",", " ")';
    }
}

La méthode define() est obligatoire. Le moteur de template appellera directement cette méthode aussitôt la class du filtre instanciée.

Kernel

Dans Configuration\Config, il y a désormais la clé kernel dans l'array de configuration. Il contient les namespace vers toutes les class Bundle dont a besoin le framework : Les class des bundles à l'intérieur du framework, tout comme les class de vos bundles.

Controller et Service

Dans les paramètres d'une Action, vous pouvez désormais appeler directement un Service, en faisant précéder le nom de la variable par le nom de la class du service.

public function exampleAction(ExampleService $ex, ...){}

Condition dans une vue

Dernier ajout plus ou moins important, si vous effectuez une condition dans votre vue, vous pouvez retranscrire un in_array().

{{ if $example_array has "value_test" }}

Je vous remercie d'avoir prit le temps pour cette longue lecture. :)

Default
,

Salut à vous, une petite news aujourd'hui :)

Au début du framework, je ne l’avais pas doté de bundles. Je les ai intégré plus tard. Récemment, j’ai travaillé sur Laravel et Symfony Flex.

Et ça m’a poussé à revenir également en BundleLess. Il est vrai que ça simplifie à la fois le code de l’application (Avis personnel :D ) mais aussi le code interne du framework.

Le prochain chantier du framework devrait tourner autour des entités et de l’ORM.

Default
,

Bonjour ! :)

J'ai donc continué à développer le framework en bundleless. Concernant la configuration, le paramètre "env" pourra maintenant prendre en valeur "prod" ou "dev". "local" remplacé par "dev", ça me semble plus logique :D .

Comme je l'avais dit, je me suis concentré surtout sur l'ORM, avec pour inspiration Laravel. J'ai développé les changements cités ci-dessous en développant en même temps un jeu en ligne.

Migration SQL

Et j'ai donc commencé par faire un système de migration, exécutable par la commande console :

php console.php ORM:migration --all

Dans le dossier database/migration (Hiérarchie de dossiers très inspirée :D ), vous pourrez créer vos class de migration. Dans mon cas, pour ma table user, voici le code de la class :

<?php
namespace Database\migration;
use Frash\ORM\Database\Migration;

class CreateUserTable extends Migration
{
    public function upload()
    {
        $this->addTable('user', function(){
            $this->increment('id');
            $this->varchar('pseudo')->length(30);
            $this->char('password')->length(40);
            $this->varchar('mail')->length(100);
            $this->bigint('points')->default(20);
            $this->bigint('main_territory')->default(0);
            $this->bigint('current_territory')->default(0);
            $this->smallint('rang')->default(2);

            $this->timeRecord();
            $this->deleteRecord();
        });
    }
}

$this->timeRecord() va créer deux colonne "created_at" et "updated_at" dans la table.
$this->deleteRecord() créera une colonne "deleted_at".

La class peut s'appeler de la façon dont on veut. Et le système de migration n'est pour l'instant compatible qu'avec MySQL :D

Entity

J'ai revu le fonctionnement des entités et des repositorys. L'on peut instancier une entity ou un repository depuis l'action d'un controller. Et dans un repository, on peut instancier, par exemple, l'entité et le querybuilder dans le __construct.

<?php
namespace App\Repository;
use App\Entity\User;

class UserRepository
{
    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

Concernant l'entité, pour l'instant, il y a seulement plusieurs propriétés à renseigner :

<?php
namespace App\Entity;
use Frash\ORM\Query\Entity\Entity;

class User extends Entity
{
    public $table = 'user';
    public $primary_key = 'id';

    public $time_record = true;
    public $delete_record = true;

    public $cols = [
        'pseudo', 'password', 'mail', 'points', 'rang'
    ];

    public $defaults = [
        'points' => 20, 'rang' => 2
    ];
}

Dans le repository, ou le controller, vous pouvez créer une requête directement grâce à l'entité. (Avant, il fallait obligatoirement le faire dans le repository, avec le QueryBuilder).

Insert

<?php
public function insertUser(string $pseudo, string $password, string $mail): int
{
    $this->user->pseudo = $pseudo;
    $this->user->password = $password;
    $this->user->mail = $mail;

    return $this->user->insert();
}

Fonctionnalité à venir, si vous mettez true dans ->insert() , ça vous retournera l'entité intégrale avec toutes les colonnes et leur valeur.

Update

<?php
public function updateRangUser(int $user_id, int $rang)
{
    $this->user->where('id', $user_id)->update([ 'rang' => $rang ]);
}

Select - Premier résultat

<?php
public function getRangUser(int $user_id): int
{
    return $this->user->where('id', $user_id)->just('rang')->first();
}

Pour ce cas-ci, j'ai utilisé la fonction just() intégrée à l'entity. Ca va tout simplement remplacer le * dans le SELECT * FROM, par les colonnes indiquées.

Count

<?php
public function countByPseudo(string $pseudo): int
{
    return $this->user->where('pseudo', $pseudo)->count();
}

Where

Concernant le Where, vous pouvez toujours renseigner de cette manière :

<?php
where('id', $user_id);
where('id', '>', $user_id);

S'il n'y pas de troisième paramètre, comme dans la première ligne, le signe de comparaison sera automatiquement "=".

Si vous voulez renseignez plusieurs where d'affilés, et que ce n'est pas un OR (Pas encore intégré), comme Laravel, vous pouvez faire :

<?php
where([
    [ 'col' => 'dest_id', 'value' => $user_id ],
    [ 'col' => 'type', 'sign' => 'IN', 'value' => [ 'type1', 'type2' ]]
]);

Dans le cas d'un IN, la clé "value" doit obligatoirement être reliée à un array.

On a fait le tour des quelques premières intégrations de l'entity.

Middleware

Dans le routing, vous pouvez faire un $this->group([ 'middleware' => '...' ], function(){});
J'ai intégré la possibilité d'ajouter un paramètre au middleware individuellement pour chaque route.

<?php
$this->group([ 'middleware' => '...' ], function(){
    $this->get('route', 'Controller:action', [ 'middleware' => [ 'params' => [ 'test_param' => 'value_test' ]]]);
});

Ainsi, dans votre middleware, vous pourrez récupérer ce paramètre avec $this->params['test_param'].

Webprofiler

Un autre composant du framework que je souhaite développer énormément, c'est un WebProfiler comme celui de Symfony (J'ai de l'ambition :D ) avec une DebugBar.

La DebugBar est actuellement assez moche et ne donne que le code HTTP, le temps total d'exécution, le controller et l'action.

En bref, tout ça m'aura permit de réorganiser une petite partie du code du framework. Et c'est finit pour la présentation du jour :D

Je vous remercie pour le temps passé à lire ce gros pavé :D .

Default
,

Hello,

Cette fois, j'ai surtout revu l'organisation interne du framework. Pour bon nombre de composants du framework, je les ai "singletonisé" :-° afin d'éviter de leur passer d'office dans le constructeur l'instance du Dic, que je passe normalement à toutes les class.

Mais il y a quand même quelques nouveautés.

:...

Qu'est-ce donc ?
Avec une route, c'est mieux :

$this->get('route/route/:get1/:...', ...);

":get1" est une... Get. :... va servir à obtenir une string de tout ce qui se suit, jusqu'à la fin de l'URL.

"route/route/je_suis_une_get/blabla/route/5/id/7"

":get1" correspondra à "je_suis_une_get" et "...", accessible avec :

$this->request->get()->route_end;

"route_end" renverra donc la string "blabla/route/5/id/7", vous pouvez ainsi faire le traitement que vous voulez sur cette string.

Where Entity

Jusqu'à maintenant, si l'on mettait plusieurs paramètres dans une fonction where(), il fallait faire comme ceci :

$this->entity->where([
    [ 'col' => 'column', 'sign' => '=', 'value' => $value ],
    [ ... ]
]);

Désormais, plus besoin de mettre les clés "col", "sign" et "value", juste des valeurs. Egalement, si le signe de comparaison est "=", c'est optionnel de l'indiquer. En interne, la fonction mettra "=" d'office si le "sous-array" n'a que deux clés.

Maintenance et Profiler

Deux fonctionnalités à développer, un mode maintenance qui permettra d'afficher automatiquement un template par défaut indiquant aux utilisateurs que le site est en maintenance, hormis aux utilisateurs ayant l'IP indiquée dans le fichier Config.php. C'est une feature à développer.

Quant au Profiler, lui aussi est à améliorer. Le but, à l'avenir, va être de déclarer des microtime dans chaque class, que le Profiler enregistrera et retournera sous la forme d'une timeline (Inspiré je l'avoue sur le WebProfiler de Symfony :-° ).

Auth

La dernière fois, mon post pouvait montrer une grande inspiration de Laravel. J'ai continué. Avec une class Auth, fonctionnant basiquement comme celle de Laravel.

Auth::id();
Auth::user()->column;

Auth::check([ 'mail' => $mail, 'password' => $no_hashed_password ]);

Dans Config.php, une nouvelle clé, "auth" permet de configurer cette class.

'auth' => [
    'entity' => 'App\Entity\User',
    'column_password' => 'password',
    'crypt_password' => 'sha1'
],

Auth::check() vérifiera automatiquement si le user avec les paramètres que vous lui passerez en array existe. Si oui, il créera la session frash_auth contenant les valeurs de chaque colonne de l'entité indiquée dans la configuration.

Voilà :)

2