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

1
47855
,

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

Default
,

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

47855
,

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

Default
,

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

Default
,

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

Default
,

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.

Default
,

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.

Default
,

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

Default
,

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

Default
,

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

47855
,

Salut :)

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

Default
,

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

47855
,

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

Default
,

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

47855
,

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

Default
,

Effectivement, c'est bien plus simple ^^

Par contre, je ne vois pas la PR :p

47855
,

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

47855
,

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

Default
,

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

47855
,

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;
}
1