首先给我们的框架起个名字叫“Poppy”,然后在自己工作目录新建一个Poppy文件夹,然后在该文件夹下新建src目录,这是本次存放我们自己代码的文件夹,然后在src下新建public文件夹并且在其内部新建index.php:
mkdir -p src/public
cd src/public
touch index.php
文件夹public属于nginx对外可以访问的,index.php作为我们的框架入口文件
==注意:本次演示的环境是基于mac+php7.0
==
:
mkdir Poppy && cd Poppy`
我们把Poppy
这个目录当成我们的工作目录,我先大概列出我们的目录,我大概建了几个主要文件,使用tree -L 3
查看如下:
.
├── composer.json
├── composer.lock
├── src
│ ├── Core //核心目录
│ │ ├── App.php
│ │ └── Handler.php
│ ├── Log //日志文件夹
│ │ └── poppy.log
│ ├── Model //模型文件夹
│ │ ├── Content.php
│ │ └── Model.php //model基类
│ ├── Public
│ │ └── index.php //项目单入口文件
│ ├── Services //控制器文件夹
│ │ └── articleController.php
│ ├── config.json //配置文件
│ └── routes.php //路由文件
接下来我们先使用Composer添加我们的HTTP组件,在命令行敲入
composer require symfony/http-foundation
回车等待执行完成,好了结果如下:
Using version ^3.2 for symfony/http-foundation
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
- Installing symfony/polyfill-mbstring (v1.3.0): Loading from cache
- Installing symfony/http-foundation (v3.2.8): Loading from cache
Writing lock file
Generating autoload files
composer所有的东西都存放在vendor里面,包括我们之前讲到的autoload.php,以及我们引入的symfony/http-foundation包,接下来我们引入该包作为我们的HTTP组件。来验证一下,写入以下代码:
// at Public/index.php
require __DIR__ . "/../../vendor/autoload.php";
define('ROOT_PATH', __DIR__);
define('APP_PATH', __DIR__ . "/../");
//创建一个request请求
$req = Symfony\Component\HttpFoundation\Request::createFromGlobals();
//获取该请求的方法类型
echo "Method: ".$req->getMethod()."
";
//获取URI的值
echo "URI: " . $req->getRequestUri() . "
";
//获取a参数的值,第二个参数为当a不存在时的默认值
echo "Params: ".$req->get('a','default');
使用php自带webserver在项目目录下启动项目:
php -S 127.0.0.1:8001 -t ./src/public
使用浏览器访问网址:http://127.0.0.1:8001/users/list?a=1
返回以下结果:
Method: GET
URI: /users/list?a=1
Params: 1
好了,体验了一把,我们终于可以直接使用别人的组件了,感觉加快了开发步骤,是不是感觉很爽~,有些人很奇怪,为什么就是这么写呢?为什么是这么用呢?其实用的方法有很多,当你拿到一个包你首先要阅读完全它的文档说明,只有知道它都有些什么,才能更好的利用它。
接下来继续添加我们的路由组件,这里选用nikic/fast-route
:
composer require nikic/fast-route
成功的界面不在赘述了,通过阅读它的usage我们写出如下代码,首先在src里面新建routes.php写入一些测试的路由:
//at routes.php
return [
//homepage
[
['GET'],
'/',
'App\Services\index@index'
],
//create articles
[
['POST'],
'/articles',
'App\Services\article@create'
],
//delete articles
[
['DELETE'],
'/articles/{Id}',
'App\Services\article@delArticleById'
],
//articles list
[
['GET'],
'/articles',
'App\Services\article@articlesList'
]
];
它返回的是一个大数组,数组内第一个值为请求方法,第二个为请求的URI,第三个为请求对应到的具体方法,接下来来完善我们的项目
// at Core/App.php
namespace App\Core;
class App
{
use Handler;
public function run()
{
$this->initRoute();
}
}
// at Core/Handle.php
namespace App\Core;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Odan\Middleware\Dispatcher\HttpFoundationSprinter;
trait Handler
{
/**
* Init route
* @return $this|mixed
*/
public function initRoute()
{
$routes = require APP_PATH . "/routes.php";
$dispatcher = \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) use ($routes) {
foreach ($routes as $route) {
$methods = array_map("strtoupper", (array)$route[0]);
$r->addRoute($methods, $route[1], $route[2]);
}
});
$request = Request::createFromGlobals();
$method = $request->getMethod();
$uri = rawurldecode($request->getRequestUri());
if (false !== $pos = strpos($uri, '?')) {
$uri = substr($uri, 0, $pos);
}
$response = Response::create();
//注入了Logger之后可以去除注释
//App::Logger()->addInfo(
// sprintf(
// 'Accepted request %s %s',
// $request->getMethod(),
// $request->getUri()
// ),
// [
// $request->getQueryString()
// ]
//);
$routeInfo = $dispatcher->dispatch($method, $uri);
switch ($routeInfo[0]) {
case \FastRoute\Dispatcher::NOT_FOUND:
return $response->setStatusCode('404')->setContent("404 Not Found")->send();
break;
case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
$allowedMethods = $routeInfo[1];
return $response->setStatusCode('405')->setContent("405 Method Not Allowed")->send();
break;
case \FastRoute\Dispatcher::FOUND:
try {
$sprinter = new HttpFoundationSprinter();
$response = $sprinter->run($request, $response, []);
$handler = $routeInfo[1];
$vars = $routeInfo[2];
if (is_string($handler) && (strpos($handler, '@'))) {
$ret = $this->callHandler($handler, $vars, $request, $response);
} else {
$ret = call_user_func($handler, $request, $response, $vars);
}
} catch (\Exception $e) {
return $response->setStatusCode("500")->setContent($e->getMessage())->send();
}
if ($ret instanceof Response) {
return $ret;
}
if (is_array($ret)) {
$response->headers->set("Content-Type", "application/json;charset=utf-8");
return $response->setContent(json_encode($ret))->send();
}
return $response->setContent($ret)->send();
break;
}
}
/**
* @param $handler
* @param $vars
* @param $request
* @param $response
* @return mixed
*/
public function callHandler($handler, $vars, $request, $response)
{
if (is_string($handler) && (strpos($handler, '@'))) {
list($class, $method) = explode('@', $handler);
$class = ucfirst($class) . "Controller";
return (new $class())->$method($request, $response, $vars);
}
return call_user_func($handler, $request, $response, $vars);
}
}
通过以上代码,我们能把routes里面的定义的路由转发到我们对应的控制器里面去,接下来我们来创建我们的controller,我们在src里面创建一个services文件夹,在里面创建一个articleController.php,随便写入一些内容如下:
//at Services/articleController.php
namespace App\services;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class articleController
{
public function articlesList(Request $request, Response $response)
{
return ['msg'=>'success'];
}
}
记得在index.php去调用App类里面的run方法:
//at Public/index.php
//实例化核心app类
$app = new \App\Core\App();
//启动框架
$app->run();
好了。我们访问http://127.0.0.1:8001/articles
页面,看到页面返回一个json内容:
{
'msg'=>'success'
}
到此为止,好像我们”弄”出了一个大概能用的框架了,能通过url定位到controller,是不是感觉略屌~,不过路还很长,继续往下看吧。
添加服务容器并且注入配置文件服务、日志服务
在项目目录执行
//引入服务容器
composer require pimple/pimple
//引入配置文件读取包
composer require hassankhan/config
//引入著名的 monolog 日志服务
composer require monolog/monolog
//引入ORM
composer require illuminate/database
在App.php
里面使用pimple来注入我们的服务,使用它很简单,注入一个服务就像添加一个服务后缀一样方便【服务是一个对象,当然你也可以使用它的工厂方法每次返回新的对象】,
//at App.php
namespace App\Core;
use Illuminate\Database\Capsule\Manager;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Noodlehaus\Config;
use Pimple\Container;
/**
* Class App
* @package App\Core
*/
class App
{
use Handler;
/**
* @var $container
*/
protected static $container;
/**
* @var $config
*/
protected static $config;
/**
* Init container
*/
public function initContainer()
{
$container = new Container();
//载入我们的config文件
$config = Config::load(APP_PATH . "config.json");
//注入到容器,下次可以直接使用
$container['config'] = $config;
//日志服务代码如下,我们使用config作为闭包的参数传进去
$container['logger'] = function () use ($config) {
$logger = new Logger($config->get('app_name'));
$logger->pushHandler(new StreamHandler($config->get('log_file')));
return $logger;
};
//载入 DB
$capsule = new Manager();
foreach ($config->get('connections') as $name => $item) {
$capsule->addConnection([
'driver' => $item['driver'],
'host' => $item['host'],
'database' => $item['name'],
'username' => $item['username'],
'password' => $item['password'],
'charset' => $item['charset'],
'collation' => $item['collation'],
'prefix' => $item['prefix'],
], $name);
}
$capsule->setAsGlobal();
$capsule->bootEloquent();
//赋值给静态类属性,方便下次使用
self::$container = $container;
}
/**
* @return mixed
*/
public static function getContainer()
{
return self::$container;
}
/**
* @return \Monolog\Logger
*/
public static function Logger()
{
return self::$container['logger'];
}
/**
* @return \Noodlehaus\Config
*/
public static function Config()
{
return self::$container['config'];
}
/**
* start app
*/
public function run()
{
$this->initContainer();
$this->initRoute();
}
}
config.json
文件内容如下:
{
"app_name": "Poppy",
"log_file": "../log/poppy.log",
"connections": {
"default": {
"driver": "mysql",
"host": "localhost",
"username": "root",
"password": "123456",
"port": "3306",
"name": "gear",
"charset": "utf8",
"collation": "utf8_general_ci",
"prefix": ""
}
}
}
载入了orm,就要去使用它,我们在Model/Content.php添加如下代码:
//at Model/Content.php
namespace App\Model;
class Content extends Model
{
//自定义表名
protected $table = 'content';
}
Model.php作为model基类来集成我们的Eloquent ORM类
//at Model/Model.php
namespace App\Model;
class Model extends \Illuminate\Database\Eloquent\Model
{
}
model准备好了,我们在Controller里面去使用它,大概就是返回某一条数据(方法命名好像不太合理不管了~)
//at articleController.php
namespace App\services;
use App\Model\Content;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class articleController
{
private $container;
/**
* 通过构造函数传入的服务容器实体
* 也可以直接通过App::getContainer()获取
* articleController constructor.
*/
public function __construct($container)
{
$this->container = $container;
}
public function articlesList(Request $request, Response $response, $vars)
{
//获取一条内容
$rel = Content::first();
$data = [];
//如果存在则格式化数组返回
if ($rel) {
$data = $rel->toArray();
}
return ['msg' => 'success', 'data' => $data];
}
}
访问http://127.0.0.1:8001/articles
可以看到页面显示如下:
{
msg: "success",
data: {
id: 1,
name: "Name1"
}
}
查看Log文件夹下自动生成了poppy.log,那代表我们的Log也能成功运行。
yeah,是不是感觉很有意思,到此为止,我们实现了路由、服务容器、日志、配置文件,这些应该满足了80%的业务需求,当然还不够,接下来我会继续更新,一一实现我们之前我们所讲的那些组件。