Slim 4初步研究

Slim是一个我很喜欢用的轻框架,我用它为我的任氏有无轩站点提供API服务。这几天趁着放假,想重新“折腾”一下我的站点,于是就开了一个虚拟机,装好了必要的软件,准备开发。

然后我发现,Slim这个框架已经升级到了4,有了重大的变化。

参照Slim官方说明创建项目后,目录结构如下(请忽略其中的nbproject目录):

  • app目录:它包含了对于整个应用来说最基本的一些文件。具体说明如下。

  • dependencies.php:它主要是创建应用全局的容器。应用安装时,会生成logger这个实例。数据库链接的实例也是在这里生成的:

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        ...
        PDO::class => function (ContainerInterface $c) {
            $settings=$c->get('settings')['db'];
            $host=$settings['host'];
            $user=$settings['user'];
            $pass=$settings['pass'];
            $db=$settings['db'];

            $dsn="mysql:host=$host;dbname=$db";
            $conn=new PDO($dsn, $user, $pass);
            $conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
            return $conn;
        },
    ]);
};

注意,我在这里设置了数据库PDO链接的一些属性。以后在应用的任何地方,只要用到PDO这个类的声明,Slim都会在容器中去寻找PDO的实例,从而保证所有的数据库链接都是统一的。

  • middleware.php:中间件我目前在API中还不会用到。按照Slim的说明,中间件可以在应用运行,修改相应的RequestResponse对象。
  • repositories.php:这里注册所有可以成为“仓库”的类。这是Slim 4新增的文件,用来统一管理MVC中的M;但Slim 4抛弃了M,而改用更轻量的仓库。这也是一个容器。如:
declare(strict_types=1);

use App\Domain\Book\BookRepository;
use App\Infrastructure\Persistence\Book\DBBookRepository;

use DI\ContainerBuilder;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        BookRepository::class => \DI\autowire(DBBookRepository::class),
    ]);
};

注:这里用到的一些类会在后续得到说明。不过可以提醒一下,Slim 4用一个接口(BookRepository)定义所有该仓库支持的操作,而用一个实例类(DBBookRepository)对这个接口进行实现。你可以把它们的这种关系类比成C++中的“hpp/cpp”的关系。

  • routes.php:这是常规的路由配置。请注意,Slim4放弃了一个controller中多个action的做法,而是一个action对应一个controller,如下例所示:
declare(strict_types=1);

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
use Slim\Interfaces\RouteCollectorProxyInterface as Group;

use App\Application\Actions\Index\IndexAction;
use App\Application\Actions\Book;

return function (App $app) {
    $app->get('/', IndexAction::class);
    $app->group('/summary', function(Group $group) {
        $group->get('', Book\SummaryAction::class);
    });
};

于是,/summary这个路由就会调用Book\SummaryAction::class中制定的action函数。

  • settings.php:全局配置文件。数据库的配置也在此处出现:
return function (ContainerBuilder $containerBuilder) {
    // Global Settings Object
    $containerBuilder->addDefinitions([
        'settings' => [
            'displayErrorDetails' => true, // Should be set to false in production
            'logger' => [
                'name' => 'slim-app',
                'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
                'level' => Logger::DEBUG,
            ],
            //Database connection
            'db' => [
                'host'  => 'localhost',
                'user'  => '****',
                'pass'  => '****',
                'db'    => '****',
            ],
        ],
    ]);
};

总结:app目录中的,都是对应用全局产生影响的文件。

  • public目录:这里是应用的入口文件。一般情况下,对于一个API应用来说,这里没有什么需要修改的。
  • src/Application目录:这里有四个目录。我们重点要关注的是src/Application/Actions目录。

根据我的理解,针对API要成立的各类实体,开发者可以进行逻辑抽象分类,形成各个action。在具体实现的时候,一般是针对每个实体实现一个抽象的类,然后对每个针对这个实体的操作定义一个action

比如我的数据库中有“书籍(book)”这个实体,于是我就暂时定义了两个文件:BookAction.phpSummaryAction.php

//BookAction.php

declare(strict_types=1);

namespace App\Application\Actions\Book;

use App\Application\Actions\Action;
use App\Domain\Book\BookRepository;

use Psr\Log\LoggerInterface;
use Psr\Http\Message\ResponseInterface as Response;

abstract class BookAction extends Action
{
    /**
     * {@inheritdoc}
     */
    protected $repo;

    public function __construct(BookRepository $repo, LoggerInterface $logger) {
        parent::__construct($logger);
        $this->repo=$repo;
    }
}

注意到,这是一个抽象类,主要目的是为本类(以及后续子类)引入各种dependency。这里我引入了BookRepositoryLoggerInterface

//SummaryAction.php
declare(strict_types=1);

namespace App\Application\Actions\Book;

use Psr\Http\Message\ResponseInterface as Response;

class SummaryAction extends BookAction
{
    /**
     * {@inheritdoc}
     */
    protected function action(): Response
    {
        $res=$this->repo->summary();
        return $this->respondWithData($res);
    }
}

SummaryAction继承了BookAction,完成了action方法。而在action方法中,通过调用repo中对应的方法而获取了数据并返回。Slim4很贴心地可以直接返回json数据,不用开发者再转换。调用的例子如下:

我们再来看src/Domain目录。

这个目录中包括用来定义各类仓库的文件。比如我们之前看到的BookRepository

// /src/Domain/Book/BookRepository.php
declare(strict_types=1);

namespace App\Domain\Book;

interface BookRepository
{
    public function summary(): array;
    public function detail($id):array;
}

如前所述,这是一个接口,只是定义了各类接口,也就是这个仓库能做什么的定义。

最后来看看src/Infrastructure/Persistence目录。这个目录对上面提到的接口进行实现。

// /src/Infrastructure/Persistence/Book/DBBookRepository.php
declare(strict_types=1);

namespace App\Infrastructure\Persistence\Book;

use App\Domain\Book\BookRepository;

class DBBookRepository implements BookRepository
{
    private $conn;

    public function __construct(\PDO $conn) {
        $this->conn=$conn;
    }

    public function summary(): array {
        $sql='select count(*) bc, sum(kword) wc, sum(page) pc from book_book';
        $res=$this->conn->query($sql)->fetch();

        return $res;
    }

    public function detail($id): array {

    }

}

请注意,这个类的构造函数中引入了\PDO,因此上文提到的dependency就起作用了。

总体感觉,Slim 4的结构还是很清晰的,耦合度也合适,确实是可以一用的轻量级框架。

本文推送到[go4pro.org]

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *