惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
Webroot Blog
Webroot Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
N
News | PayPal Newsroom
S
Security Affairs
T
Tor Project blog
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security @ Cisco Blogs
H
Heimdal Security Blog
Hacker News: Ask HN
Hacker News: Ask HN
Help Net Security
Help Net Security
U
Unit 42
云风的 BLOG
云风的 BLOG
The Hacker News
The Hacker News
Cisco Talos Blog
Cisco Talos Blog
量子位
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
有赞技术团队
有赞技术团队
T
Troy Hunt's Blog
P
Privacy & Cybersecurity Law Blog
Forbes - Security
Forbes - Security
人人都是产品经理
人人都是产品经理
L
Lohrmann on Cybersecurity
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
腾讯CDC
AI
AI
Last Week in AI
Last Week in AI
Latest news
Latest news
Google Online Security Blog
Google Online Security Blog
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
V2EX - 技术
V2EX - 技术
酷 壳 – CoolShell
酷 壳 – CoolShell

f2h2h1's blog

claw养殖技术 计算机网络基础知识 定时任务 ACME的使用经验 magento2加上varnish缓存 开发Magento2的模块 在magento2中使用persisted-query socket编程 一些开发笔记 一段CSDN文章主要内容的油猴脚本 电子邮件的不完整总结 git的笔记 在Windows下配置PHP服务器 终端,控制台和外壳 PHP各种运行方式的不完整总结 把网页导出成PDF 和颜色相关的笔记 HTTP认证方式的不完整总结 SEO的经验 密码学入门简明指南 文件的上传和下载 用纯CSS3实现的滑动按钮 在VSCode里调试PHP Linux的GUI 关于字符编码的一些坑 nc的使用和原理 在Windows下安装Magento2 对JS原型链的理解 使用docker-compose部署magento2 浏览器和服务器通讯方式的不完整总结 观察网站性能 一些关于Linux的笔记 telnet的不完整总结 在Windows下安装pear MySQL的时间类型和时间相关的函数 Windows下通过PEB读取进程的环境变量 关于 在VSCode里使用Xdebug远程调试PHP 在Windows下搭建git服务 关于环境变量的不完整总结 使用shell实现的kv数据库 如何完成以xx管理系统为选题的毕业设计 数字号码资源 各种标记语言 使用PowerShell实现的http服务器 kind相关经验 DNSSEC简介 nginx+ffmpeg+websocket实现的直播例子 使用Tesseract识别字符验证码 使用docker部署nuxt FirstData后台的设置 paypal,firtdata,支付宝的不完整接入指南 微信支付的不完整接入指南 用docker-compose部署lnmp环境 mongodb分片 练习
使用yii3实现一个微框架
2026-06-01 · via f2h2h1's blog

yii3 是一个现代的php框架,全面符合PSR标准,由一百多个独立包组成,实现按需加载

这篇文章是描述如何使用很少的 yii3 组件实现一个微框架

新建项目目录

前置的依赖

  • php 8.2
  • composer 2
  • git

安装命令

mkdir yii3-mrico;
cd yii3-mrico;
composer init;
composer require \
    httpsoft/http-server-request \
    yiisoft/yii-http \
    yiisoft/router-fastroute \
    yiisoft/di \
    yiisoft/injector \
    yiisoft/event-dispatcher \
    yiisoft/yii-event \
    yiisoft/psr-emitter ;

编写入口文件

index.php

<?php

/**
 * Yii3 Micro Framework - Single File Application
 */

// =============================================================================
// 0. Autoloading
// =============================================================================

declare(strict_types=1);

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

use HttpSoft\Message\Response;
use HttpSoft\Message\ResponseFactory;
use HttpSoft\Message\ServerRequest;
use HttpSoft\Message\ServerRequestFactory;
use HttpSoft\Message\StreamFactory;
use HttpSoft\Message\UriFactory;
use HttpSoft\Message\UploadedFileFactory;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\EventDispatcher\Dispatcher\Dispatcher;
use Yiisoft\EventDispatcher\Provider\Provider;
use Yiisoft\Http\Status;
use Yiisoft\Injector\Injector;
use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;
use Yiisoft\Router\CurrentRoute;
use Yiisoft\Router\Group;
use Yiisoft\Router\Middleware\Router;
use Yiisoft\Router\Route;
use Yiisoft\Router\RouteCollection;
use Yiisoft\Router\RouteCollectionInterface;
use Yiisoft\Router\RouteCollector;
use Yiisoft\Router\RouteCollectorInterface;
use Yiisoft\Router\UrlGeneratorInterface;
use Yiisoft\Router\UrlMatcherInterface;
use Yiisoft\Router\FastRoute\UrlGenerator;
use Yiisoft\Router\FastRoute\UrlMatcher;
use Yiisoft\Yii\Event\CallableFactory;
use Yiisoft\Yii\Event\ListenerCollectionFactory;
use Yiisoft\Yii\Http\Application;
use Yiisoft\Yii\Http\Event\AfterEmit;
use Yiisoft\Yii\Http\Event\AfterRequest;
use Yiisoft\Yii\Http\Event\ApplicationShutdown;
use Yiisoft\Yii\Http\Event\ApplicationStartup;
use Yiisoft\Yii\Http\Event\BeforeRequest;
use Yiisoft\Yii\Http\Handler\NotFoundHandler;


// =============================================================================
// 1. Event Listener Configuration
// =============================================================================

function createListenerProvider(ContainerInterface $container): ListenerProviderInterface
{
    $listenerConfig = [
        ApplicationStartup::class => [
            static function (ApplicationStartup $event) {
                error_log('[EVENT] Application starting up');
            },
        ],
        ApplicationShutdown::class => [
            static function (ApplicationShutdown $event) {
                error_log('[EVENT] Application shutting down');
            },
        ],
        BeforeRequest::class => [
            static function (BeforeRequest $event) {
                $request = $event->getRequest();
                error_log('[EVENT] Before request: ' . $request->getMethod() . ' ' . $request->getUri()->getPath());
            },
        ],
        AfterRequest::class => [
            static function (AfterRequest $event) {
                $response = $event->getResponse();
                $status = $response ? $response->getStatusCode() : 'no response';
                error_log('[EVENT] After request, status: ' . $status);
            },
        ],
        AfterEmit::class => [
            static function (AfterEmit $event) {
                error_log('[EVENT] Response emitted');
            },
        ],
    ];

    $injector = new Injector($container);
    $callableFactory = new CallableFactory($container, $injector);
    $factory = new ListenerCollectionFactory($injector, $callableFactory);
    $collection = $factory->create($listenerConfig);

    return new Provider($collection);
}

// =============================================================================
// 2. Route Configuration
// =============================================================================

function configureRoutes(RouteCollectorInterface $collector): void
{
    $routes = [
        // Home page
        'home' => Route::get('/')
            ->action(function (ResponseFactoryInterface $responseFactory) {
                $response = $responseFactory->createResponse(Status::OK);
                $response->getBody()->write(json_encode([
                    'framework' => 'Yii3 Micro',
                    'status'    => 'running',
                    'time'      => date('Y-m-d H:i:s'),
                    'php'       => PHP_VERSION,
                ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
                return $response
                    ->withHeader('Content-Type', 'application/json');
            }),
        // Health check
        'health' => Route::get('/health')
            ->action(function (ResponseFactoryInterface $responseFactory) {
                $response = $responseFactory->createResponse(Status::OK);
                $response->getBody()->write(json_encode([
                    'status' => 'healthy',
                    'php'    => PHP_VERSION,
                ]));
                return $response
                    ->withHeader('Content-Type', 'application/json');
            }),
        // Hello with name parameter
        'hello' => Route::get('/hello/{name}')
            ->action(function (
                ResponseFactoryInterface $responseFactory,
                CurrentRoute $currentRoute,
            ) {
                $name = $currentRoute->getArgument('name', 'World');
                $response = $responseFactory->createResponse(Status::OK);
                $response->getBody()->write(json_encode([
                    'message' => "Hello, {$name}!",
                    'route'   => $currentRoute->getUri()->getPath(),
                ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
                return $response
                    ->withHeader('Content-Type', 'application/json');
            }),
        // Echo POST request body
        'echo' => Route::post('/echo')
            ->action(function (
                ServerRequestInterface $request,
                ResponseFactoryInterface $responseFactory,
            ) {
                $body = $request->getParsedBody() ?? [];
                $response = $responseFactory->createResponse(Status::OK);
                $response->getBody()->write(json_encode([
                    'method'  => $request->getMethod(),
                    'uri'     => (string) $request->getUri(),
                    'body'    => $body,
                    'query'   => $request->getQueryParams(),
                ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
                return $response
                    ->withHeader('Content-Type', 'application/json');
            }),
        // Get URL by name (demonstrates URL generation)
        'urls' => Route::get('/urls')
            ->action(function (
                ResponseFactoryInterface $responseFactory,
                UrlGeneratorInterface $urlGenerator,
                \Yiisoft\Router\RouteCollectorInterface $collector,
            ) {
                $getRouteName = function($route) {
                    return $route->getData('name');
                };
                $getRouteGroupName = function($routeGroup) use (&$getRouteGroupName, $getRouteName) {
                    $list = [];
                    foreach ($routeGroup->getData('routes') as $item) {
                        if ($item instanceof Group) {
                            $list = array_merge($list, $getRouteGroupName($item));
                        } else {
                            $list[] = $getRouteName($item);
                        }
                    }
                    return $list;
                };
                $routeList = [];
                foreach ($collector->getItems() as $item) {
                    if ($item instanceof Group) {
                        $routeList = array_merge($routeList, $getRouteGroupName($item));
                    } else {
                        $routeList[] = $getRouteName($item);
                    }
                }
                $response = $responseFactory->createResponse(Status::OK);
                $response->getBody()->write(json_encode([
                    'routes' => [
                        'home'    => $urlGenerator->generate('home'),
                        'health'  => $urlGenerator->generate('health'),
                        'hello'   => $urlGenerator->generate('hello', ['name' => 'Yii3']),
                        'echo'    => $urlGenerator->generate('echo'),
                        'urls'    => $urlGenerator->generate('urls'),
                        'routeList' => $routeList,
                    ],
                ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
                return $response
                    ->withHeader('Content-Type', 'application/json');
            }),
        // Demo of dependency injection
        'di-test' => Route::get('/di-test')
            ->action(function (
                ResponseFactoryInterface $responseFactory,
                ContainerInterface $container,
                EventDispatcherInterface $eventDispatcher,
                UrlMatcherInterface $urlMatcher,
                UrlGeneratorInterface $urlGenerator,
                \Yiisoft\Router\RouteCollectorInterface $collector,
            ) {
                $response = $responseFactory->createResponse(Status::OK);
                $response->getBody()->write(json_encode([
                    'di_working'             => true,
                    'container_class'        => get_class($container),
                    'event_dispatcher_class' => get_class($eventDispatcher),
                    'url_matcher_class'      => get_class($urlMatcher),
                    'url_generator_class'    => get_class($urlGenerator),
                    'url_collector_class'    => get_class($collector),
                ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
                return $response
                    ->withHeader('Content-Type', 'application/json');
            }),
    ];

    $collector->addRoute(
        Group::create('/' . basename(__FILE__))->routes(
            ...array_map(function($key, $item){return $item->name(basename(__FILE__) .  '/' . $key);},  array_keys($routes), array_values($routes))
        ),
        ...array_map(function($key, $item){return $item->name($key);},  array_keys($routes), array_values($routes)),
    );

    $collector->addRoute(
        Route::methods(
            [\Yiisoft\Http\Method::GET, \Yiisoft\Http\Method::POST, \Yiisoft\Http\Method::PUT, \Yiisoft\Http\Method::DELETE, \Yiisoft\Http\Method::PATCH, \Yiisoft\Http\Method::OPTIONS],
            '/{path:.*}'
        )
        ->action(static function (ResponseFactoryInterface $responseFactory): ResponseInterface {
                $response = $responseFactory->createResponse(Status::NOT_FOUND);
                $response->getBody()->write('404 NOT FOUND');
                return $response;
        })
        ->name('fallback.404')
    );
}

// =============================================================================
// 3. DI Container Configuration
// =============================================================================

// Build routes first (needed for RouteCollection)
$routeCollector = new RouteCollector();
configureRoutes($routeCollector);
$routeCollection = new RouteCollection($routeCollector);

$containerConfig = ContainerConfig::create()
    ->withDefinitions([
        // --- PSR-17 HTTP Factories ---
        ResponseFactoryInterface::class         => ResponseFactory::class,
        ServerRequestFactoryInterface::class    => ServerRequestFactory::class,
        StreamFactoryInterface::class           => StreamFactory::class,
        UriFactoryInterface::class              => UriFactory::class,
        UploadedFileFactoryInterface::class     => UploadedFileFactory::class,

        // --- Router (use pre-built instances) ---
        RouteCollectionInterface::class         => static fn () => $routeCollection,
        RouteCollectorInterface::class          => static fn () => $routeCollector,
        CurrentRoute::class                     => CurrentRoute::class,
        UrlMatcherInterface::class              => UrlMatcher::class,
        UrlGeneratorInterface::class            => UrlGenerator::class,

        // --- Event Dispatcher ---
        ListenerProviderInterface::class        => static fn (ContainerInterface $c) => createListenerProvider($c),
        EventDispatcherInterface::class         => Dispatcher::class,

        // --- Middleware ---
        MiddlewareFactory::class                => static fn (ContainerInterface $c) => new MiddlewareFactory($c),
        Router::class                           => static function (ContainerInterface $c) {
            return new Router(
                $c->get(UrlMatcherInterface::class),
                $c->get(ResponseFactoryInterface::class),
                $c->get(MiddlewareFactory::class),
                $c->get(CurrentRoute::class),
                $c->get(EventDispatcherInterface::class),
            );
        },
    ]);

$container = new Container($containerConfig);

// =============================================================================
// 4. Application Bootstrap & Request Handling
// =============================================================================

try {
    // Create the server request from PHP globals
    $request = \HttpSoft\ServerRequest\ServerRequestCreator::createFromGlobals($_SERVER, $_FILES, $_COOKIE, $_GET, $_POST);

    // Build middleware pipeline
    /** @var MiddlewareFactory $middlewareFactory */
    $middlewareFactory = $container->get(MiddlewareFactory::class);
    /** @var EventDispatcherInterface $eventDispatcher */
    $eventDispatcher = $container->get(EventDispatcherInterface::class);

    $middlewareDispatcher = new MiddlewareDispatcher($middlewareFactory, $eventDispatcher);
    $middlewareDispatcher = $middlewareDispatcher->withMiddlewares([
        Router::class,  // Route requests to controllers
    ]);

    $fallbackHandler = new NotFoundHandler(
        $container->get(ResponseFactoryInterface::class)
    );

    // Create application
    $app = new Application(
        $middlewareDispatcher,
        $eventDispatcher,
        $fallbackHandler
    );

    // Start application
    $app->start();

    // Handle the request
    $response = $app->handle($request);

    // Emit the response
    $sapiEmitter = new \Yiisoft\PsrEmitter\SapiEmitter(65536);
    $sapiEmitter->emit($response);

    // Notify that response was emitted
    $app->afterEmit($response);

    // Shutdown
    $app->shutdown();

} catch (Throwable $e) {
    http_response_code(500);
    header('Content-Type: application/json');
    echo json_encode([
        'error'   => 'Internal Server Error',
        'message' => $e->getMessage(),
        'file'    => $e->getFile() . ':' . $e->getLine(),
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}

运行

php -S 127.0.0.1:8002

请求

curl -v http://127.0.0.1:8002/index.php

单独的action类

action类 本质上是一个普通的类,具体的处理方法只需要返回 ResponseInterface 即可

声明一个 action 类


class ActionController
{
    public function execute(ServerRequestInterface $request, ResponseFactoryInterface $responseFactory): ResponseInterface
    {
        $body = $request->getParsedBody() ?? [];
        $response = $responseFactory->createResponse(Status::OK);
        $response->getBody()->write(json_encode([
            'method'  => $request->getMethod(),
            'uri'     => (string) $request->getUri(),
            'body'    => $body,
            'query'   => $request->getQueryParams(),
        ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        return $response
            ->withHeader('Content-Type', 'application/json');
    }
}

把 action 类加入到 容器 中

$containerConfig = ContainerConfig::create()
    ->withDefinitions([
        ...
        ActionController::class => static fn() => new ActionController(),
    ]);

把 action类加入到 路由 中

// 在 $routes 数组中加上
$routes = [
    ...
    'action' => Route::get('/action')
            ->action([ActionController::class, 'execute']),
];

// 或者直接加到 $collector 对象中
$collector->addRoute(
    Route::get('/action')
            ->action([ActionController::class, 'execute']),
);

请求

curl -v http://127.0.0.1:8002/index.php/action

使用视图

安装对应的库

composer require yiisoft/yii-view-renderer

在根目录下新建要给视图文件 template.phtml ,并写入以下内容

<p>The message is: <?= $message ?></p>

在 index.php 里引入

use Yiisoft\Yii\View\Renderer\WebViewRenderer;

action类新建一个 view 方法

    public function view(ServerRequestInterface $request, WebViewRenderer $webViewRenderer): ResponseInterface
    {
        return $webViewRenderer->render(__DIR__ . '/template.phtml', [
            'message' => 'message',
        ]);
    }

加入路由

$routes = [
    ...
    'view' => Route::get('/view')
            ->action([ActionController::class, 'view']),
];

其实完全不用 库 也可以的

$routes = [
    ...
    'view-pure' => Route::get('/view-pure')
        ->action(function (
            ServerRequestInterface $request,
            ResponseFactoryInterface $responseFactory,
        ) {
            ob_start();
            try {
                $dictionary = ['message' => 'message2'];
                extract($dictionary, EXTR_SKIP);
                include __DIR__ . '/template.phtml';
            } catch (\Exception $exception) {
                ob_end_clean();
                throw $exception;
            }
            $output = ob_get_clean();
            $response = $responseFactory->createResponse(Status::OK);
            $response->getBody()->write($output);
            return $response;
        }),
];

加入授权

安装对应的库

composer require yiisoft/user