Un framework MVC custom made

Continuarea discuției de aici

Nu am stat pe ganduri si am inceput sa lucrez la un proiect de la 0, un framework, folosind modelul arhitectural MVC ( asa cum il inteleg eu, momentan ).Nu ma judecati prea aspru, inca este in curs de dezvoltare, insa sunt deschis pentru feedback, mi-ar fi de mare ajutor.

https://github.com/Catalonian/php-mvc-framework

4 Likes

Fork la thread sa putem comenta, sau vrei cu issues pe github?

Nu prea stiu… asa cum este mai bine pentru toata lumea, in special pentru cei ce sunt aici sa invete, acesta este scopul principal, sa invatam.

Foloseste TDD.

Mai intai scrii o forma de integration tests. Cred ca pentru inceput e mai bine sa incepi cu subcutaneous tests.

Apoi scrii un unit test si apoi codul de productie necesar pentru a trece acel unit test.

Apoi inca un unit test, si tot asa.

Pana cand integration test trece de la sine.

Apoi commit, si treci la urmatorul integration test

Ai 1-2 carti de citit, cateva prezentari de urmarit, cateva articole de citit, si multa, multa practica.

Dar pe termen lung te va ajuta

  • sa scrii cod mai robust
  • sa faci refactoring mai usor.
  • sa mentii viteza de dezvoltare

PS: structura de fisiere si directoare nu indica ca proiectul ar fi un framework, ci o aplicatie. As folosi composer pentru generarea autoloaderului si as muta aplicatiile demonstrative in repozitorii separate. In aplicatii as folosi tot composer pentru a “instala” framework-ul.

Inteleg ca composer nu e scopul tau, dar aici e vorba de interoperabilitate cu alte proiecte. Daca chiar vrei sa faci totul de la zero, implementeaza un autoloader compatibil cu PSR-4 si lasa composer deoparte.

PPS: daca vrei sa iasa bine cu acele subcutaneous tests, trebuie sa faci un design pentru testabilitate, si sa poti scrie teste subcutane care acopera multe componente si totusi ruleaza in-memory (cu unele componente stubbed out la unele subcutaneous tests).

Ce ti-am scris aici acopera cateva luni de studiu si practica. Intreaba unde te blochezi.

2 Likes

M-am mai uitat pe codul tau. Vad ca folosesti o forma cruda de incapsulare a lui $_GET si $_POST, care are si cel putin un neajuns.

Mai bine implementeaza PSR-7. Permite middlewares, interoperabilitate mai usoara cu alte framework-uri.

1 Like

Am testat codul pe o masina virtuala cu ubuntu 14.04 , nginx 1.4.6 si php 5.5.9
Dupa require bootstrap.php crapa: $app->getInstance('router')->setBasePath('mvc/public/');

cat /etc/nginx/sites-available/mvc.local
server {
    listen       81;
    server_name  www.mvc.local;

    charset      utf-8;

    error_log /var/log/nginx/mvc.local.error.log;
    access_log  /var/log/nginx/mvc.local.access.log combined;

    index index.php;
    set $root_path "/var/www/mvc.local/public";
    root $root_path;

    client_max_body_size 10M;

    location / {
        try_files $uri $uri/ /index.php;
    }

    location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            # fastcgi_pass 127.0.0.1:9000;
            fastcgi_pass unix:/run/php5-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include fastcgi_params;
    }

    location ~* ^/(css|img|js|download)/(.+)$ {
        root $root_path;
    }

    location ~ /\.ht {
        deny all;
    }
}

Requestul GET / - mai jos ai continutul variabilei $_SERVER in public/index.php

array(32) {
  ["USER"]=>
  string(8) "www-data"
  ["HOME"]=>
  string(8) "/var/www"
  ["FCGI_ROLE"]=>
  string(9) "RESPONDER"
  ["SCRIPT_FILENAME"]=>
  string(35) "/var/www/mvc.local/public/index.php"
  ["QUERY_STRING"]=>
  string(0) ""
  ["REQUEST_METHOD"]=>
  string(3) "GET"
  ["CONTENT_TYPE"]=>
  string(0) ""
  ["CONTENT_LENGTH"]=>
  string(0) ""
  ["SCRIPT_NAME"]=>
  string(10) "/index.php"
  ["REQUEST_URI"]=>
  string(1) "/"
  ["DOCUMENT_URI"]=>
  string(10) "/index.php"
  ["DOCUMENT_ROOT"]=>
  string(25) "/var/www/mvc.local/public"
  ["SERVER_PROTOCOL"]=>
  string(8) "HTTP/1.1"
  ["GATEWAY_INTERFACE"]=>
  string(7) "CGI/1.1"
  ["SERVER_SOFTWARE"]=>
  string(11) "nginx/1.4.6"
  ["REMOTE_ADDR"]=>
  string(12) "192.168.56.1"
  ["REMOTE_PORT"]=>
  string(5) "43472"
  ["SERVER_ADDR"]=>
  string(14) "192.168.56.101"
  ["SERVER_PORT"]=>
  string(2) "81"
  ["SERVER_NAME"]=>
  string(13) "www.mvc.local"
  ["REDIRECT_STATUS"]=>
  string(3) "200"
  ["HTTP_HOST"]=>
  string(16) "www.mvc.local:81"
  ["HTTP_CONNECTION"]=>
  string(10) "keep-alive"
  ["HTTP_CACHE_CONTROL"]=>
  string(9) "max-age=0"
  ["HTTP_ACCEPT"]=>
  string(74) "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
  ["HTTP_UPGRADE_INSECURE_REQUESTS"]=>
  string(1) "1"
  ["HTTP_USER_AGENT"]=>
  string(105) "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36"
  ["HTTP_ACCEPT_ENCODING"]=>
  string(19) "gzip, deflate, sdch"
  ["HTTP_ACCEPT_LANGUAGE"]=>
  string(23) "en-US,en;q=0.8,ro;q=0.6"
  ["PHP_SELF"]=>
  string(10) "/index.php"
  ["REQUEST_TIME_FLOAT"]=>
  float(1452109403.0725)
  ["REQUEST_TIME"]=>
  int(1452109403)
}

Aici, am inceput sa-ti modific codul, revin.

In primul rand, falvius, multumesc pentru toate sfaturile tale, imi sunt de un real ajutor, multumesc atat de mult! Inca sunt la inceput, nu stiu atat de mult despre testarea codului, insa vreau sa invat.Acest proiect este doar pentru a studia mai bine conceptele din OOP si orice feedback este de un real ajutor.

Emanuel, daca accesezi adresa http://localhost/mvc/public/, totul trebuie sa functioneze corect.

Insa daca folosesti ceva de genul: http://mvc.local/public, trebuie sa modifci setBasePath(), cu alte cuvinte $app->getInstance('router')->setBasePath('public/');

Apropo, ma bucur atat de mult ca sunt persoane care doresc sa ajute, sunt cel mai recunscator.

Folosesti WAMP sau ceva asemanator?
Eu am printat ce am in $_SERVER, aici iti atrag atentia asupra:
["HTTP_HOST"]=> string(16) "www.mvc.local:81"
["REQUEST_URI"]=> string(1) "/""
din cele 2 ar trebui sa reiasa faptul ca accesez http://www.mvc.local:81/
Pe portul default http (80) am altceva, poti ignora portul.
Acum m-am uitat la eroare, de fapt este autoloaderul care nu o sa mearga decat pe sistemele Windows:

2016/01/06 23:07:53 [error] 3505#0: *146 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Uncaught exception 'Exception' with message 'Unable to autoload [/var/www/mvc.local/System\Application.php]' in /var/www/mvc.local/system/autoload.php:9
Stack trace:
#0 [internal function]: {closure}('System\Applicat...')
#1 /var/www/mvc.local/bootstrap/app.php(3): spl_autoload_call('System\Applicat...')
#2 /var/www/mvc.local/bootstrap/bootstrap.php(7): require_once('/var/www/mvc.lo...')
#3 /var/www/mvc.local/public/index.php(3): require('/var/www/mvc.lo...')
#4 {main}

Mai departe am modificat codul pentru a fi cat mai “universal”.

Da, folosesc ceva asemanator, xampp, sunt blocat pe windows, momentan.Deabia astept sa vad ce modificari ai facut tu.

index.php

<?php
header('Content-Type: text/html; charset=utf-8');
mb_internal_encoding("UTF-8");

define('ROOT_FOLDER', dirname(__DIR__));
require_once ROOT_FOLDER . '/bootstrap/bootstrap.php';

Nu este nevoie de DIRECTORY_SEPARATOR - atat Windows cat si Linux gasesc path-urile cu /
bootstrap/bootstrap.php

<?php
//Poti sa adaugi mai multe librarii, fiecare cu autoloadul propriu, prima cheie exemplu
$paths = [
    //Pt compatibilitate cu pachetele aduse de composer
    ROOT_FOLDER . '/vendor/autoload.php',
    ROOT_FOLDER . '/system/autoload.php',
    ROOT_FOLDER . '/bootstrap/app.php'
];

foreach($paths as $path){
    if(file_exists($path)){
        require_once $path;
    }
}

Aici cred ca te-ai inspirat din Codeigniter ca structura?
system/autoload.php

<?php
spl_autoload_register(function($className) {
    //De aici http://www.php-fig.org/psr/psr-4/examples/
    $prefix = 'System\\';
    $base_dir = ROOT_FOLDER . '/system/';
    $len = strlen($prefix);
    if (strncmp($prefix, $className, $len) !== 0) {
        return;
    }
    $relative_class = substr($className, $len);
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    if (file_exists($file)) {
        require $file;
    }
});

Acum avem o eroare, cred ca se intelege din mesaj ce s-a intamplat.
$app->getInstance('router')->setBasePath('mvc/public/');
Fatal error: Call to protected method System\Router::setBasePath() from context '' in /var/www/mvc.local/bootstrap/app.php on line 7

Ceea ce imi aminteste sa ma intorc mai la inceput in cod si sa afisez erorile daca sunt pe un mediu de development.
Variantele ar fi ori sa fac un folder config unde pun mai multe fisiere, inclusiv app/routes.php sau modific setarile serverului web, spre exemplu adaug fastcgi_param DEV 1; si dupa in index.php, undeva inainte de require_once voi avea:

// sau require config/ceva.php si verific intr-un array de setari
if(isset($_SERVER['DEV']) && $_SERVER['DEV'] == 1){
    ini_set('display_errors', 1);
    error_reporting(E_ALL);
}

Ar fi interesant daca ai veni cu lista.

Eu folosesc UwAmp, care este un fel de Wamp care te ajuta sa modifici chestii usor. Poti trece de la o versiune de alta, de php, in cateva click-uri, lafel si sa activezi sau dezactivezi pluginuri php, sa adaugi o versiune noua de php, etc.

Va rog sa ma scuzati de offtopic. N-am apucat sa ma uit pe framework, insa sunt interesat de progresul sau.

Conform ultimelor commits, nu prea pare să îl fi preocupat ce am avut de spus.

Poate mă înșel și îl preocupă cărțile, caz în care își poate compila o listă de cărți. M-am oferit să îl ajut acolo unde se blochează, deci dacă blocajul constă într-o listă prea mare de cărți, mă poate întreba punctual.

Mi se pare normal să muncești atunci când ai oportunitatea de a avea acces la “expert knowledge” și știu că sfaturile mele l-ar pune pe multă muncă, muncă la care poate nu e dispus să se înhame. E modul în care eu încerc să îmi optimizez resursele (timpul) pentru a obține ROI mare. Fiecare își poate stabili singur criteriile.

Nu e ca și cum aș face pe interesantul, e pragmatism și eficiență.

2 Likes

Desigur, daca esti dispus sa ma indrumi in ceea ce priveste cartile pe care trebuie sa le citesc si nu doar atat, ti-as fi cel mai recunscator, nu ca nu as avea propria mea lista de carti, insa eu incerc sa gasesc calea cea “corecta”, dar tu deja stii pasii necesari.Ceea ce mi-ai oferit tu in acel prim post al tau, acea diagrama destul de interesanta, imi este foarte necesara, deoarece prezinta pasii necesari pentru un TDD ca la carte.Momentan, astfel de concepte ca TDD, ma depasesc, nici nu folosesc teste in aplicatiile mele, dupa cum se poate observa si in proiectul curent, insa asta nu inseamna ca bat pasul pe loc.

Ce fac eu? Momentan studiez, studiez si studiez, apoi revin cu un update la proiect, insa daca se implica mai multe persoane, cu atat mai bine.

Ideea de la care am pornit, pentru acest proiect, a fost si este, ca acest proiect sa fie cat mai simplu, astfel incat sa se evidentieze conceptele din OOP, ca eu si multi altii ca mine, sa invatam.

De asemenea, am vrut ca proiectul sa aiba o structura cat mai simpla.
Revin cu updates in ceea ce priveste acest proiect.

Aceasta este o versiune mai imbunatatita a proiectului, insa am scris si o versiune mult mai simpla, versiune ce nu se mai folosesti de IOC ( Inversion of Control, este un principiu prin care dependentele unei clase sunt injectate in mod automat, in momentul executarii scriptului, in instanta ce se creeaza folosind librarira ReflectionClass oferita de PHP ) ci se foloseste de Controllers ca fiind services in containerul de dependente, insa pentru fiecare Controller, trebuie injectate manual dependentele, ceea ce implica mai mult cod, sa nu uitam ca mai trebuie si declarate ca services in container ( stiu ca silex foloseste un principiu asemanator, de aici am preluat ideea)

O sa revin cu un update in ceea ce priveste modul in care eu am gandit acest proiect, cred ca va fi destul de folositor.

Revino cand te simti pregatit :slight_smile:

Arunca un ochi si aici.

Nu trebuie sa ai ceva mare, robust si “corect”, ori “ca la carte”. Orice este un inceput bun.

Edit: TDD-ul te ajuta sa te concentrezi pe problema in cauza, in loc sa te concentrezi pe “oare modificarea asta strica ceva important?”, fiindca intrebarea nu mai este pusa de tine, de fiecare data cand gandesti o noua modificare, ci de testele scrise, lasandu-te sa te concentrezi pe functionalitatea aplicatiei, in loc de bugs. Si, deci, scapi de situatia in care nu modifici nimic fiindca iti este frica sa nu crape ceva.

Related:

1 Like

Pragmatism și eficiență era să scrii 3 titluri de cărți decât toată poliloghia asta…

1 Like

Depinde cum masori. Eu masor pe termen lung pentru @OP.

1 Like