Tag: go4pro

  • 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]

  • 终于有了自己的VPS

    终于下定决心、排除万难,和前令狐一起搞定了一个VPS。这个VPS是在Linode购买的,我们购买的是最便宜的Linode 512套餐,20刀一个月。

    搞这个VPS主要有这么几个动机:

    1. 创建自己的VPN。这样我的手机上也可以直接挂Twitter,Facebook,Youtube了;
    2. 为Go4Pro.org找一个家……我原本很傻很天真的认为用了BlueHost的虚拟主机后应该也可以,但是由于BH对Python的支持是Django,而我们对Go4Pro.org的开发是基于TG2,于是不得不找一个可以让我们随心所欲搞东西的地方。
    3. 为将来的rsywx.net等站点找一个更自由、更有控制力的地方。

    慢慢来吧……

    最后说一句,原来这些站点–抛开VPN的诱惑–都是可以几乎以0成本的代价运行在我家里的2F主机上的。但是随着80端口的被封,我的2F主机已经无法承担这个任务。都是被B的……