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


Salut,

J'ai vite fait parcouru les différents dossiers de ton FW, et regardé la documentation et une chose me saute aux yeux : tu "obliges" à charger le DIC comme dépendance de chaque contrôleur (ou méthode du contrôleur si pas de constructeur à ce que j'ai compris). La question que je me pose est : pour quelle raison ?
Pourquoi ne pas imaginer par exemple un helper global (ex. dic(Database::class) pour charger la DB) ou même prévoir un contrôleur parent dans lequel tu définis les différentes dépendances les plus utilisées (ex. ORM, form, redirect etc) dont tous les autres contrôleurs hériteraient ?

/* BASE CONTROLLER */
namespace Bundles\AppBundle\Controllers;

use Frash\Framework\DIC\Dic;

class BaseController {
        protected $dic;
        protected $session;
        protected $form;
        protected $orm;
        protected $redirect;
        /* etc ...*/

        public function __construct(Dic $dic){
            $this->dic = $dic;
            $this->form = $this->dic->load('form');
            $this->redirect = $this->dic->load('redirect');
            $this->session = $this->dic->load('session')
            $this->orm = $this->dic->load('orm');
            /* etc ...*/
        }
}

/* USERS CONTROLLER */

namespace Bundles\AppBundle\Controllers;

class UsersController extends BaseController {}

Je ne sais pas si tu vois où je veux en venir ? :)

Sinon, ton FW a l'air d'être carré, il faurdrait que je regarde ça de plus près. Manque aussi un routeur, une partie validation de data entres autres et on sera pas mal :)

Salut, je te remercie de ta réponse.

La(les) raison(s) qui me pousse(nt) à "obliger" à charger le DIC à chaque fois est que je ne sais pas comment faire pour permettre au développeur d'avoir une action de ce genre :

public function __construct(UserRepository $user_repo, FormFactory $forms)

J'ai réfléchis, je pense pouvoir utiliser la ReflectionClass pour parcourir les arguments d'une méthode et capturer les namespace de leur type, mais ensuite, pour les passer dynamiquement dans l'instanciation du Controller, je ne sais pas comment faire.

Sur le principe du controller parent, effectivement, ce serait une bonne idée, si je restais sur le DIC en unique paramètre possible. Le DIC ne gère pas uniquement la liste des dépendences, je charge également toutes les class de configuration (Config, Database, etc.) et je lui passe différents paramètres (Le bundle, le "prefix_lang", ...).

"Manque aussi un routeur", comment ça ? Framework/Routing/Router.php, non ?

"une partie validation de data entres", une validation de formulaire ? C'est une de mes pistes d'amélioration, la possibilité de définir dans une class les paramètres de validation de tous les champs d'un formulaire.

Si tu as d'autres commentaires, indications...

Oups, autant pour moi je n'avais pas fait attention pour la partie routage ^^
Une validation de données postées de manière générale (un peu à la manière de Laravel)
Sinon pour ton injection de dépendances, regarde également ce qui se fait du côté de Laravel, fouille le code source pour essayer de comprendre un peu comment c'est fait (ils utilisent un peu le même principe que celui que tu as mis en exemple) ;)

Bonjour,

Du coup, je donne quelques nouvelles ^^
Le framework est passé en 1.4.10.
Entre autres changements, j'ai mis en place le BaseController, qui est pratique, effectivement.
J'ai aussi changé la syntaxe du moteur de template, pour la faire ressembler à TWIG, je me limite toutefois uniquement à {{ et }}.
J'ai également finalisé l'intégration des middlewares au routing.

Désormais, depuis Routing.php, vous pouvez affecter un middleware, que vous assignerez également dans Service.php :

private static $config = [
        'middleware' => [
            'Admin' => 'AppBundle@Admin',
            'Connected' => 'AppBundle@Connected',
            'NoConnected' => 'AppBundle@NoConnected'
        ]
];

Ce qui permettra de tester le middleware, par exemple, Connected :

<?php
namespace Bundles\AppBundle\Service;
use Frash\Framework\DIC\Dic;

class Connected{
    private $dic;

    public function __construct(Dic $dic){
        $this->dic = $dic;
    }

    public function define(){
        if($this->dic->load('session')->has('id') === false){
            $this->dic->load('redirect')->route('home/')->go();
            return false;
        }

        return true;
    }
}

J'ai mis la documentation en français, les quelques README déjà faits pour être synchro avec les changements.

Merci :)

Bonjour,

Le framework est passé en 1.4.12 et a passé le cap symbolique des 1000 commits ^^
J'ai mis à jour la documentation en ajoutant un .md pour informer sur la contribution, si vous avez des idées d'amélioration et le temps pour les développer, je suis bien sûr preneur. :)

La 1.4.12 règle principalement un problème du Dic, où pour setter ou getter un paramètre, il fallait faire :

$this->dic->get('bundle');
$this->dic->set('bundle', $value);

Maintenant, il n'y qu'à faire :

$this->dic->bundle;
$this->dic->bundle = $value;

Merci :)

Bonjour,

Une version 1.4.15 vient d'être publiée.

Beaucoup de modifications, d'améliorations et de fix bug. ^^
J'ai notamment créé près d'une centaine d'issue correspondant à toutes les pistes d'amélioration que j'ai. (Si ça intéresse quelqu'un de participer :) )

On peut notamment compter :

  • Un QueryBuilder commun à MySQL et PGSQL, au lieu d'un pour chacun. (Je vise à unifier les Finder et Counter)

  • Quand l'on faisait une requête AJAX, il fallait passer le PREFIX et/ou le PREFIX_LANG (Deux paramètres définis dans le Router) dans la vue, dans un div en display: none par exemple, afin de pouvoir faire :

    
    var prefix = $('#prefix').text();

$.ajax({
url : prefix+'ajax/route/',
...

Maintenant, les variables prefix et prefix_lang sont définies en rajoutant {{ ajax }} dans votre template parent par exemple.

- J'ai ajouté une mise en forme à la stracktrace en cas d'erreur. Les couleurs sont probablement à revoir :D
- J'ai ajouté une homepage qui s'affichage si la route est "site.com/" et que "racine" dans Config.php n'est pas définit. Là aussi, il faut que je revois son contenu, qui est pour l'instant, très basique.
- J'ai débugué la partie MySQL de l'ORM, j'en ai profité pour revoir la partie PGSQL.
- Enfin, j'ai revu le fonctionnement du parser du moteur de templates. Jusqu'à présent, les conditions et loops étaient mises dans des méthodes appelées le moment venu. Pour un foreach, ça ressemblait à :

public function foreachname(){
$implode = [];

foreach($this->params['param'] as $key => $value){
    $implode[] = ...
}

return implode("\n", $implode);

}


Maintenant, il n'y a plus d'implode, le foreach est intégré au content de la view. Il y a toutefois quelques cas d'usage à voir, quand il n'y a pas de content avant la boucle, voir comment indenter le contenu à l'intérieur de la boucle (Actuellement, s'il y a un foreach, dans une condition, dans un foreach, dans un foreach, les quatre débuteront tous au même nombre de tabulation.

Je vous remercie.

Bonjour,

Ce matin, j’ai merge la version 1.4.16, elle intègre notamment une première version d'un "BrowserKit" qui, pour l’instant, permet de récupérer ce que renvoie la route ou l’URL indiquée, le response code et le header ($http_response_header).

Je me suis rendu compte après coup qu’il y a deux variables que je n’avais pas changé, c’est un fix bug qui va être mergé ce midi. ^^ Les deux variables (La même : $this->system), dans OrmFactory, renvoyaient une erreur si j’utilisais finder() ou counter(), mauvais namespace ( :D ).

La 1.4.16, sinon, met à jour l’affichage d’Exception, intègre {{ call Bundle:Controller:action }} dans le moteur de templates, unifie les QueryBuilder, Counter et Finder de MySQL et PGSQL (Ce qui a amené au bug de namespace). Lors de l’exécution de la commande Framework:init, vous renseignez les langues disponibles pour la traduction ( "fr/en/de/it/…" ), pour chaque langue indiquée, la commande va générer le fichier de traduction. (TradFr, TradEn, etc…)

Pour finir, j’ai complété légèrement les tests unitaires en ajoutant checkRegex() et checkNotRegex() ainsi qu’une class de tests unitaires pour la class Framework\Utility\Generator.

$this->checkRegex('/^([a-zA-Z]*)$/', Generator::get(10, false, true, true, false));

Je réfléchis à décliner le framework pour avoir une version compatible PHP 5.6.

Merci.

Bonjour,

La version 1.1 de frash-install est en release. Elle permet l'utilisation de la 1.4.17 du framework.

La version 1.4.17 complète la fonctionnalité {{ call }} du moteur de templates, ainsi, vous pourrez mettre {{ call POST route/ }}, par exemple.

Cette version met également en place un "Dispatcher", instancié par le frontcontroller, qui va se charger du DIC, du routing et de l'instanciation du controller et de l'action.

Enfin, il y a une première version introduisant les paramètres personnalisés pour l'action, ainsi, vous pourrez faire :

public function indexAction(IndexRepository $index, Finder $finder, Counter $counter, Session $session, ...){

Comme vous pouvez également ne mettre aucun paramètre pour l'action et vous servir uniquement du Dic passé par BaseController.

Merci :)

Salut !

Après un petit moment d'absence, j'ai pu améliorer la 1.4.17.

J'ai ajouté une fonction get() à la méthode Collection. En lui passant un paramètre "key1.key2.key3", get() retourna la valeur de key3 contenue dans key2 qui est contenue dans key1 ^^

$arr = [
    'key1' => [
        'key2' => (object) [
            'key3' => true
        ]
     ]
];

Cet exemple retournera donc true.

Un petit ajout est la class StringFormat, qui pour l'instant ne contient que la fonction replace(), elle permettra à terme d'avoir :

$sf = new StringFormat(' Hello ');
$sf->replace('ll', 'w')->trim()->lcfirst();

Par cette class, j'ai voulu reproduire le "comportement" de Javascript (var.replace().trim()...).

J'ai placé deux fonctions (dump() et predump()) dans un fichier helpers. Ca évite l'instanciation d'une class pour l'appel d'une fonction de débugage. predump() affiche un print_r() entouré par pre.

J'ai enfin revu la syntaxe concernant l'affichage de variables dans une vue, à l'extérieur d'une loop. Au lieu de mettre @param, c'est maintenant $param.

Je vous remercie. :)

Bonjour à tous,

Ca fait un petit moment que j'ai pas donné de nouvelles concernant l'avancée du framework.
Il a bien avancé ^^

J'ai notamment intégré les jointures pour PostGreSQL.

$sel = new Join('table1', [ 'order' => 'table1.nom ASC' ]);
$sel->colJoin('table1.id');
$sel->colJoin('table1.nom');
$sel->colJoin('table1.responsable', 'responsable');
$sel->colJoin('immobilier.nom', 'local');
$sel->colJoin('table3.nom', 'table3_nom');
$sel->colJoin('table3.prenom', 'table3_prenom');
$sel->join('INNER JOIN', 'immobilier', 'table1.immobilier', 'immobilier.id');
$sel->join('LEFT JOIN', 'table3', 'table1.responsable', 'table3.id');
$sel->where('table1.superieur', ':sup')->execute([ $sup ]);

return $this->queryJoin($sel);

Assez classique, et ça retourne un array, d'arrays.

J'ai effectué quelques modifications, notamment dans la syntaxe de parsing du moteur de templates. Désormais, les variables "référencées" dans un foreach du moteur de template peuvent être des arrays multidimensionnels.

La syntaxe donne :

{{ foreach $array.other.other2 :: key, data }}

Concernant le Routing, lors de la définition d'une get optionnel ( :id? ), l'on peut ajouter la clé "default"... Pour renseigner une valeur par défaut.

$this->get('route/:id?', 'IndexController:organisationAction', [ 'get' => [ 'id' => [ 'type' => 'integer', 'default' => 0 ]]]);

Je vous remercie :)

Salut :)

Je vois que ton framework avance bien ! C'est cool :)
Tu as prévu une doc (référence & API) ? :)

Oui, il faut tout d'abord que je revois le générateur de documentation ainsi que les commentaires dans toutes les class.
Ca, ce sera la documentation du code.

J'ai aussi la documentation d'utilisation du framework à continuer, je l'ai d'ailleurs un peu avancé (Repo frash-docs).

Ok ça marche :)

La doc c'est un gros morceau : c'est ultra important. Une mauvaise documentation et personne ne voudra utiliser ton FW, aussi bon soit-il.
N'hésite pas à mettre des exemples de code partout où tu peux.

Je ne peux que te conseiller de t'inspirer de la documentation de Laravel qui est, selon moi, une référence en la matière :)

Dans ma documentation, je mets un exemple de code pour "chaque" utilisation :)
Effectivement, la documentation de Laravel est très bien.

Pour ton routing, tu ne devrais pas te limiter aux verbs GET et POST, mais au minimum à GET, POST, PUT,DELETE et PATCH.
Dans ta classe qui gère le routing, au lieu de créer autant de tableaux que tu as de verbs, mais un tableau global $routes avec en clé le verb et en valeur les routes.

De la même manière, tes méthodes get() et post() sont quasiment idenitques : crées une méthode générique call() qui prend en premier paramètre le verb, ça sera bien plus simple :

private $routes = [];

private function call(string $verb, string $route, $path, array $params = [])
{
        $middlewares = [];
        if (!empty($this->waiting['middleware']) || !empty($this->waiting['bundle']))
        {
            if (!empty($this->waiting['middleware']))
            {
                foreach ($this->waiting['middleware'] as $m)
                {
                    $middlewares[] = $m;
                }
            }
            $new_path = is_string($path) && !empty($this->waiting['bundle']) ? $this->waiting['bundle'].':'.$path : $path;
        }

        if (!isset($this->routes[ $verb ]))
        {
            $this->routes[ $verb ] = [];
        }        
        $this->routes[ $verb ][ $route ] = [ 'path' => $new_path, 'params' => $params, 'middleware' => $middlewares ];
        return $this;
    }

    protected function get(string $route, $path, array $params = [])
    {
        return $this->call('get', string $route, $path, array $params = []);
    }

    protected function post(string $route, $path, array $params = [])
    {
        return $this->call('post', string $route, $path, array $params = []);
    }

    protected function put(string $route, $path, array $params = [])
    {
        return $this->call('put', string $route, $path, array $params = []);
    }

    protected function delete(string $route, $path, array $params = [])
    {
        return $this->call('delete', string $route, $path, array $params = []);
    }

    protected function patch(string $route, $path, array $params = [])
    {
        return $this->call('patch', string $route, $path, array $params = []);
    }

C'est quand même plus simple non ? :)
Et je pense que l'on peut encore simplifier les appels à la méthode call() en générant dynamiquement les méthodes get(), post(), put(), delete() et patch() ;)

j'ai fait un PR sur ton repo ;)
Après je n'ai rien testé je te préviens ^^

Effectivement, c'est bien plus simple ^^

Par contre, je ne vois pas la PR :p

Arf, j'ai fork ton projet mais je ne suis pas sûr que mon PR soit passé. A vrai dire je n'ai pas trouvé comment en faire un depuis ton repo ^^

Edit : c'est bon j'ai soumis le PR ;)

C'est bon j'ai trouvé en fait ^^

Je la testerai ce soir :)
Je te remercie. :)

j'ai refais un autre PR, afin de simplifier encore les choses via la méthode magique PHP __call() :

protected function __call($method, $arguments)
{
    $response = call_user_func_array([ $this, $method ], $arguments);
    if ($response === false)
    {
        throw new \Exception("Router_exception :: Method {$method} not found");
    }
    return $response;
}

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

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

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.

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.

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.

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.

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. :)

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.

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 .

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à :)