teodoroleckie/framework

This package is abandoned and no longer maintained. No replacement package was suggested.

DEPRECATED - php framework

1.5.0 2020-05-15 07:24 UTC

This package is auto-updated.

Last update: 2021-05-26 12:54:37 UTC


README

Estructura de su proyecto:

El directorio config es obligatorio y se debe suministrar al objeto Kernel para que inicialicen las configuraciones.

En el fichero dependencies.php deberá inicializar su contenedor de dependencias, el nombre de este fichero puede ser de su elección.

En el fichero routes.php se definirán qué controladoras o closures deben atender cada tipo de petición, el nombre de este fichero puede ser de su elección.

En el fichero config.php se inicializa el objeto Config, que por ejemplo, debe leer variables que dependen del entorno. El nombre de este fichero se debe respetar y no puede ser otro.

MyProject/
        |-config/
                |-dependencies.php
                |-routes.php
                |-config.php
        |-public/
        |-vendor/
        |-src/
            |-Controller/
            |-Infrastructure/
            |-Domain/Model/
            |-Application/
            |-tpl/

Todos los ficheros que se encuentran dentro del directorio config, está disponible una variable $di que es nuestro gestor de dependencias. Un ejemplo de un fichero config.php podría ser el siguiente:

<?php

use Framework\Container\DiInterface;
use Framework\Config;


/** @var $di DiInterface */
$di->getShared('config')->merge(
  new Config(
      [
        'hostname' =>  getenv('HOSTNAME')
      ]
  )
);

En este punto, nuestro contenedor de dependencias ya ha creado una instancia de un objeto Config y lo deja disponible para que creemos otro objeto para que lo podamos mergear con las configuraciones que creemos en tiempo de ejecución. Notar que el objeto config solamente estará disponible para el shared, dado que queremos siempre la misma instancia.

Gestor de dependencias:

namespace Framework\Container\Di

Para registrar dependencias se pueden hacer de dos modos, setShared y set. Para obtener dependencias lo haremos por medio de getShared y get. El método setShared registra instancias que se comportarán como singleton y set registra instancias que no se comportan como singleton, es decir con cada invocación se retornará una instancia nueva.

Podemos hacer registros por nombre de clase:
El primer argumento será el nombre que se utilizará para registrar la dependencia y el segundo es el className:

use Framework\Container\Di;
use Framework\Http\ServerRequestFactory;

$di = new Di();

$di->setShared(
    'request.factory'
    , ServerRequestFactory::class
);
// o
$di->set(
    'request.factory'
    , ServerRequestFactory::class
);

Para obtener la instancia lo haremos invocando con el nombre de registro.

$di->getShared('request.factory');
// o
$di->get('request.factory');

Si una clave se registra por setShared solamente podrá ser obtenido por getShared, igualmente si se registra por set solamente se podrá obtener la instancia por get.

Registro por closure:

$di->setShared(
    'request'
    , static function () use ($di) {
        return $di->getShared('request.factory')::fromGlobals();
    }
);

Registro con argumentos por construcción:

$di->setShared(
    'home.controller'
    , [
        'className' => HomeController::class
        , 'arguments' => [
            [
                'type' => 'service'
                , 'value' => 'request'
            ]
            , [
                'type' => 'service'
                , 'value' => 'response.factory'
            ]

        ]
    ]
);

Registro con invocación de métodos:

$di->setShared(
    'my.class'
    , [
        'className' => MyClass::class
        ,'calls' => 
            [
                [
                    'arguments' => [
                        [
                            'type' => 'parameter',
                            'value' => 5
                        ]
                    ]
                    , 'method' => 'firstMethod'
                ]
                ,[
                     'arguments' => [
                         [
                             'type' => 'service',
                             'value' => 'response.factory'
                         ]
                     ]
                     , 'method' => 'secondMethod'
                 ]
            ]
    ]
);

Puede notar que la primera invocación se hace con un parámetro definido como 'type' => 'parameter', pero el segundo se hace por un servicio 'type' => 'service' que también deberá estar definido. En nuestro caso 'response.factory'.

Gestor de rutas:

El gestor de rutas se deberá definir en un fichero php. En él podrá indicar el path por medio de expresiones regulares y los métodos de peticiones que acepta.
A continuación hay un ejemplo de la definición de un enrutamiento:

<?php

use Framework\Container\DiInterface;
use Framework\Routes\Item;
use Framework\Routes\Router;


$routes = new Router();

/** @var $di DiInterface */
$di->setShared('routes', $routes);


/** home routes , atiende petiociones get, post y options para el path dado */
/* Será atendida por la controladora 'home.controller' definida en nuestro gestor de dependencias */
$routes->add(new Item(
        'routesName'
        , '/home/(?<id>([0-9]{1,2}))/?'
        , '/home/%(id)s/'
        ,['get','post','options']
    )
    , ['home.controller','homeAction']
);

Al routes se le añaden instancias de Item como primer argumento y como segundo argumento un array con la controladora y action.

El objeto Item requiere de tres argumentos obligatorios:
1º El nombre de la ruta.
2º La expresión regular para hacer el match con el path de nuestra url.
3º Una expresión que se utiliza cuando queremos ensamblar una url basándonos en el nombre de ruta y parámetros.
4º Opcional, un array de métodos de peticiones que soporta dicha ruta, si no se indica ninguno atenderá peticiones para todos los tipos, get, post, options etc.

Puede notar que la definición de la controladora es por medio de un nombre definido en nuestro gestor de dependencias.

Recuerde que al action, en nuestro caso un método llamado homeAction de nuestra controladora recibe los siguientes argumentos:

ServerRequestInterface $request // objeto reques de la petición
ResponseFactoryInterface $responseFactory // factory para componer el response
Params $params // parámetros definidos en el routes

El objeto Params serán los parámetros definidos en nuestra ruta con las expresiones regulares.

Por ejemplo para una url /home/18/, en el action podremos obtener el valor de 18 (que es nuestro parámetro definido como id) de la siguiente forma:

<?php

namespace MyProject\Controller;


use Framework\Controller;
use Psr\Http\Message\ServerRequestInterface;
use Framework\Routes\Params;
use Psr\Http\Message\ResponseFactoryInterface;

/**
 * Class HomeController
 * @package MyProject\Controller
 * @author  Teodoro Leckie Westberg <teodoroleckie@gmail.com>
 */
class HomeController extends Controller
{
    /**
     * @param ServerRequestInterface   $request
     * @param ResponseFactoryInterface $responseFactory
     * @param Params                   $params
     * @return mixed
     */
    public function index(
        ServerRequestInterface $request
        , ResponseFactoryInterface $responseFactory
        , Params $params
    )
    {
        var_dump($params->id);
    }
}

También puede definir un Closure que atiendan sus peticiones, muy útiles para API Rest y se haría de la siguiente forma:

/** @var RouterInterface $routes */
use Framework\Routes\RouterInterface;$routes->add(new Item(
        'routesName'
        , '/home/(?<id>([0-9]{1,2}))/?'
        , '/home/%(id)s/'
        ,['get','post','options']
    )
    , static function(
        ServerRequestInterface $request
        , ResponseFactoryInterface $responseFactory
        , Params $params        
    ){
        var_dump($params->id);
    }
);

También puede definir un Closure que atiendan sus peticiones pero que además ha sido definido por el contenedor de dependencias:

Primeramente en nuestro gestor de dependencias dependencies.php definimos el closure:

$di->setShared('my.controller',  static function(){
    return static function(
        ServerRequestInterface $request
        , ResponseFactoryInterface $responseFactory
        , Params $params        
    ){
        var_dump($params);
    };
});

Luego en nuestro routes.php definimos nuestro handler:

$routes->add(new Item(
        'routesName'
        , '/home/(?<id>([0-9]{1,2}))/?'
        , '/home/%(id)s/'
    )
    ,['my.controller']
);

Ensamblar url o reverse de url:

Pude hacer reverse de url por ejemplo para mostrar una url o asignarla a su gestor de plantilla preferido de la siguiente forma.

Por ejemplo partimos de una ruta definida:

$routes->add(new Item(
        'routesName'
        , '/home/(?<param1>([a-z]+))/(?<param2>([0-9]{1,}))/?'
        , '/home/%(param1)s/%(param2)s/'
    )
    , ['home.controller','home']
);

Puede hacer lo siguiente, por ejemplo dentro de una controladora:

/** @var Router $routes */
$routes = $this->getContainer()->getShared('routes');
var_dump($routes->reverse('routesName',['param1'=> 'otherpath', 'param2'=> 555]));
//  tendrá la siguiente salida: /home/otherpath/555/

Controladores:

Podemos definir tantos controladores como sea necesario con sus respectivos actions. Es suficiente que extendamos de la clase Framework\Controller. Dichas controladoras implementan la interfaz Framework\Container\Injectable y por dicha razón tendremos disponible en nuestras controladoras el contenedor de dependencias.

<?php

namespace MyProject\Controller;

use Framework\Controller;
use Psr\Http\Message\ServerRequestInterface;
use Framework\Routes\Params;
use Psr\Http\Message\ResponseFactoryInterface;

/**
 * Class HomeController
 * @package MyProject\Controller
 * @author  Teodoro Leckie Westberg <teodoroleckie@gmail.com>
 */
class HomeController extends Controller
{
    public function homeAction(
        ServerRequestInterface $request
        , ResponseFactoryInterface $responseFactory
        , Params $params        
    )
    {
        // $responseFactory = $this->getContainer()->getShared('response.factory');
        // o $responseFactory = $this->di->getShared('response.factory');

        return $responseFactory->createHtmlResponse('hola', 200)
            ->withHeader(
                'Access-Control-Allow-Headers'
                , 'X-Requested-With
                , Content-Type
                , Accept, Origin, Authorization'
            );
    }
}

Simplemente tenemos que registrar la controladora en el contenedor de dependencias /config/dependencies.php de la siguiente forma:

$di->setShared(
    'home.controller'
    , [
        'className' => HomeController::class
       
    ]
);

Y en el /config/routes.php de la siguiente forma:

$routes->add(new Item(
        'routesName'
        , '/home/(?<id>([0-9]{1,2}))/?'
        , '/home/%(id)s/'
        ,['get','post','options']
    )
    , ['home.controller','homeAction']
);

Config:

El config (/config/config.php) lo utilizaremos para almacenar cualquier tipo de objeto o valor, también por ejemplo para almacenar valores que dependen del environment de nuestro proyecto.

<?php

use Framework\Container\DiInterface;
use Framework\Config;


/** @var $di DiInterface */
$di->getShared('config')->merge(
  new Config(
      [
        'hostname' =>  getenv('HOSTNAME')
         ,'environment' =>  getenv('ENV')
         ,'cacheTtl' =>  getenv('CACHE_TTL')
         ,'db' => [
            'dbuser' =>  getenv('DB_USER')
            ,'dbhost' =>  getenv('DB_HOST')
            ,'dbpass' =>  getenv('DB_PASS')
         ]
      ]
  )
);

Luego en nuestro proyecto podremos acceder simplemente utilizando la clave config en nuestro gestor de dependencias.

/** @var $config Framework\Config */
$config = $this->di->getShared('config');

var_dmp( $config->db->dbuser );
var_dmp( $config->db->toArray() );

index.php

En nuestro punto de entrada, tendremos que inicializar el .env, crear una instancia del Kernel e invocar al handler pasando como el argumento la url actual.

<?php

include_once '../vendor/autoload.php';

use Framework\Kernel;

(new Dotenv\Dotenv(__DIR__ . '/../'))->load();

(new Kernel($_SERVER['REQUEST_METHOD']))
    ->changePath(dirname(__DIR__) . DIRECTORY_SEPARATOR)
    ->handle($_SERVER['REDIRECT_URL'] ?? '/');

Logger

Puede utilizar la librería que prefiera mientras siempre y cuando implemente psr\log. En este caso se sugiere Monolog\Logger. Se deberá inicializar en el contenedor de dependencias de la siguiente forma:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;


/** logger */
$di->setShared('log'
    , static function () use ($di) {
        $log = new Logger('app');
        return $log->pushHandler(new StreamHandler('../log/myfile.log'));
    }
);

Para obtener el objeto logger simplemente se lo solicitamos al contenedor:

$this->di->getShared('log')->error(new Exception('My exception));

Tenemos disponibles los siguientes métodos:

/** @var $log Psr\Log\LoggerInterface */
$log->error('my message');
$log->alert('my message');
$log->critical('my message');
$log->warning('my message');
$log->notice('my message');
$log->info('my message');
$log->debug('my message');

Console

La finalidad de la clase Console es la de inicializar las dependencias. Se puede utilizar la librería Symfony\Component\Console\* para gestionar comandos. Puede ver un ejemplo a continuación:

En el fichero dependencies.php definimos nuestro comando y el app:

$di->set(
    'command:multiply'
    , [
        'className' => \Mynamespace\Command\MyCommand::class
    ]
);

/** command loader */
$di->set('command.loader'
    , static function () use ($di) {
        return new Symfony\Component\Console\CommandLoader\FactoryCommandLoader(
            [
                'command:multiply' => static function () use ($di) {
                    return $di->get('command:multiply');
                }
            ]
        );
    });

/** command app */
$di->set('command.app'
    , static function () use ($di) {
        $application = new Symfony\Component\Console\Application();
        $application->setCommandLoader($di->get('command.loader'));

        return $application;
    }
);

Luego tendremos que crear en nuestro proyecto un fichero bin/console con lo siguiente (recuerde dar permisos de ejecución a dicho fichero):

#!/usr/bin/env php
<?php

require __DIR__ . '/../vendor/autoload.php';

(new Dotenv\Dotenv(__DIR__ . '/../'))->load();

// load dependencies
$command = new Framework\Console();
$command->changePath(dirname(__DIR__) . DIRECTORY_SEPARATOR);

$di = $command->getContainer();
$log = $di->getShared('log');

try{
    // invocar al app para que se inicialicen las dependencias
    $di->get('command.app')->run();

}catch(Exception $exception){
    // log error
    $log->error($exception);
}

El comando será algo similar a lo siguiente:

<?php

namespace Mynamespace\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class MyCommand
 *
 * @package Mynamespace\Command
 * @author  Teodoro Leckie Westberg <teodoroleckie@gmail.com>
 */
class MyCommand extends Command
{

    protected function configure()
    {
        $this
            ->setName('command:multiply')
            ->setDescription('Primer comando')
            ->addArgument('number', InputArgument::REQUIRED, 'Es requerido un número')
            ->addArgument('otherNumber', InputArgument::REQUIRED, 'Es requerido un número');
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     * @return int|void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $number = $input->getArgument('number');
        $otherNumber = $input->getArgument('otherNumber');

        $output->writeln(
            sprintf(
                'Hola, el resultado de multiplicar %s x %s = %s!'
                , $number
                , $otherNumber
                , ($number * $otherNumber)
            )
        );
    }
}

Para ejecutar el comando simplemente tendremos que hacer referencia a su nombre de la siguiente forma:

$ ./bin/console command:multiply 5 8

Generar una estructura de proyecto nuevo

Desde el framework (una vez clonado y ejecutado el composer install) puede generar la estructura de su proyecto. Simplemente tiene que ejecutar el comando app:skeleton con el path en el que quiere crear su proyecto:

$ bin/console app:skeleton "/var/www/mysite"

Si todo ha salido correctamente verá la siguiente salida:

Creado el directorio: /bin/
Creado el directorio: /src/
Creado el directorio: /src/Controller/
Creado el directorio: /src/tpl/
Creado el directorio: /src/Infrastructure/
Creado el directorio: /src/Application/
Creado el directorio: /src/Domain/Model//
Creado el directorio: /tests/
Creado el directorio: /config/
Creado el directorio: /public/
Creado el composer.json
Console creado.
Index creado.
Controlador creado
Routes creado.
Dependencias creadas.
Config creado.
Env creado.