teodoroleckie/framework

该包已被 废弃 并不再维护。未建议替代包。

已弃用 - php 框架

1.5.0 2020-05-15 07:24 UTC

This package is auto-updated.

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


README

项目结构

目录 config 是必需的,并且必须提供给 Kernel 对象以初始化配置。

dependencies.php 文件中,您应该初始化您的依赖项容器,该文件的名称可以是您选择的。

routes.php 文件中,您将定义哪些控制器或闭包应该处理每种类型的请求,该文件的名称可以是您选择的。

config.php 文件中,初始化 Config 对象,例如,它必须读取依赖于环境的变量。 该文件名必须保留,不能是其他名称

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

位于 config 目录内的所有文件,都有一个 $di 变量可用,它是我们的依赖项管理器。一个 config.php 文件的示例可能如下所示

<?php

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


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

在此阶段,我们的依赖项容器已创建一个 Config 对象的实例,并将其保留以供我们创建另一个对象,以便我们可以将其与在执行时创建的配置合并。请注意,config 对象仅对 shared 可用,因为我们总是想要相同的实例。

依赖项管理器

namespace Framework\Container\Di

注册依赖项有两种方式,setShared 和 set。获取依赖项我们将通过 getShared 和 get。setShared 方法注册作为 singleton 行为的实例,而 set 注册不作为 singleton 行为的实例,即每次调用都会返回一个新的实例。

可以通过类名注册
第一个参数将是用于注册依赖项的名称,第二个参数是 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
);

要获取实例,我们将通过调用注册名称来获取。

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

如果使用 setShared 注册一个键,则只能通过 getShared 获取,同样,如果使用 set 注册,则只能通过 get 获取实例。

通过闭包注册

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

通过构造函数参数注册

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

        ]
    ]
);

通过方法调用注册

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

请注意,第一个调用使用定义为 'type' => 'parameter' 的参数,但第二个调用使用定义为 'type' => 'service' 的服务,该服务也必须定义。在我们的例子中 'response.factory'。

路由管理器

路由管理器必须在 PHP 文件中定义。在其中,您可以通过正则表达式指定路径以及它接受的请求方法。
以下是一个路由定义的示例

<?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']
);

Item 的实例添加到 routes,第一个参数是控制器和 action 的数组。

Item 对象需要三个必填参数
1. 路由名称。
2. 用于与我们的 URL 路径匹配的正则表达式。
3. 当我们根据路由名称和参数构建 URL 时使用的表达式。
4º 可选,一个支持该路由的请求方法数组,如果没有指定任何,将处理所有类型的请求,如get、post、options等。

请注意,控制器的定义是通过我们依赖管理器中的一个命名来实现的。

请记住,在我们的例子中,action(一个名为homeAction的方法)接收以下参数

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

Params对象将是我们路由中定义的参数,使用正则表达式。

例如,对于url /home/18/,我们可以在action中通过以下方式获取18(这是我们定义为id的参数)的值

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

您还可以定义一个处理请求的Closure,这在API Rest中非常有用,其定义方式如下

/** @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);
    }
);

您还可以定义一个由依赖注入容器定义的Closure来处理请求

首先,在我们 的依赖管理器dependencies.php中定义该Closure

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

然后,在routes.php中定义我们的处理器

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

组装url或反向url

例如,可以通过以下方式反转url以显示url或将它分配给您首选的模板管理器

例如,从以下定义的路由开始

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

例如,您可以在控制器内部这样做

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

控制器

我们可以定义必要的控制器及其相应的actions。我们只需要扩展Framework\Controller类即可。这些控制器实现了Framework\Container\Injectable接口,因此我们可以在控制器中使用依赖注入容器。

<?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'
            );
    }
}

我们只需要将控制器注册到依赖注入容器中的/config/dependencies.php文件中,如下所示

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

并在/config/routes.php中按如下方式定义

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

配置

我们将使用config(/config/config.php)来存储任何类型的对象或值,例如,存储依赖于项目环境的值。

<?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')
         ]
      ]
  )
);

然后,在我们的项目中,我们可以通过在依赖管理器中使用键config来访问它。

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

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

index.php

在我们的入口点,我们需要初始化.env文件,创建Kernel实例并调用处理器,传入当前url作为参数。

<?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'] ?? '/');

日志记录器

您可以使用您偏好的库,只要它实现了psr\log即可。在这种情况下,建议使用Monolog\Logger。它必须以以下方式初始化在依赖注入容器中

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

要获取logger对象,只需向容器请求它即可

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

我们有以下方法可用

/** @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类的目的是初始化依赖项。您可以使用Symfony\Component\Console\*库来管理命令。以下是一个示例

dependencies.php文件中定义我们的命令和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;
    }
);

然后,我们需要在项目中创建一个名为bin/console的文件,内容如下(请记得为该文件设置执行权限)

#!/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);
}

命令可能如下所示

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

要执行命令,只需按照以下方式引用其名称

$ ./bin/console command:multiply 5 8

生成新的项目结构

从框架(一旦克隆并执行了composer install)中,您可以生成项目结构。只需执行命令app:skeleton并指定您想创建项目的地方的路径

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

如果一切顺利,您将看到以下输出

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.