利用 Composer 一步一步构建自己的 PHP 框架(二)——构建路由

2014-10-13   /   阅读数:93092   /   分类: PHP

本教程示例代码见 https://github.com/johnlui/My-First-Framework-based-on-Composer


上一篇中我们已经建立了一个空的 Composer 项目,本篇将讲述如何构建路由。


久负盛名的 CodeIgniter 框架是很多人的 PHP 开发入门框架,同样也是我开始学习如何从头构建一个网站的框架。在 CI 中我学到了很多,其中对 MVC 的深入理解和对框架本质的理解对我的影响最大。从使用框架是为了提高开发效率的角度来看,框架的本质就是路由。


下面我们就开始自己来构建路由,先去 GitHub 搜一下:点此查看搜索结果


推荐 https://github.com/NoahBuscher/Macaw,对应的 Composer 包为 noahbuscher/macaw 。

下面开始安装它,更改 composer.json:

{
  "require": {
    "noahbuscher/macaw": "dev-master"
  }
}

运行 composer update,成功之后将得到以下目录:


QQ20141013-2.jpg

至此,Macaw 包安装成功!


下面,就是见证奇迹的时刻!我们将赋予 MFFC 生命力,让它真正地跑起来!


新建 MFFC/public 文件夹,这个文件夹将是用户唯一可见的部分。在文件夹下新建 index.php 文件:

<?php

// Autoload 自动载入
require '../vendor/autoload.php';

// 路由配置
require '../config/routes.php';


上面一行表示引入 Composer 的自动载入功能,下面一行表示载入路由配置文件。新建 MFFC/config 文件夹,在里面新建 routs.php 文件,内容如下:

<?php

use NoahBuscher\Macaw\Macaw;

Macaw::get('fuck', function() {
  echo "成功!";
});

Macaw::get('(:all)', function($fu) {
  echo '未匹配到路由<br>'.$fu;
});

Macaw::dispatch();


Macaw 的文档位于 https://github.com/NoahBuscher/Macaw,请按照你的 HTTP 服务软件类型自行设置伪静态,其实跟绝大多数框架一样:“将所有非静态文件全部指向 index.php”。


然后,将某一个端口用 Apache 或 Nginx 分配给 MFFC/public 目录,这一步十分建议用 Apache 或者 Nginx 做。

如果使用 PHP 内置 HTTP 服务器:

cd public && php -S 127.0.0.1:3000

将导致路由的 Macaw::get('fuck' 必须写成 Macaw::get('/fuck' 才能响应。


目前的代码使用 Apache + mod_php 和 Nginx + php-fpm 方式均没有问题。


我在本地绑定了 81 端口,访问 http://127.0.0.1:81/fuck 可以看到:

Image

如果页面乱码,请调整编码为 UTF-8。如果你成功看到以上页面,那么恭喜你,路由配置成功!


Macaw 只有一个文件,去除空行总共也就一百行多一点,通过代码我们能直接看明白它是怎么工作的。下面我简略分析一下:

1. Composer 的自动加载在每次 URL 驱动 MFFC/public/index.php 之后会在内存中维护一个全量命名空间类名到文件名的数组,这样当我们在代码中使用某个类的时候,将自动载入该类所在的文件。

2. 我们在路由文件中载入了 Macaw 类:“use NoahBuscher\Macaw\Macaw;”,接着调用了两次静态方法 ::get(),这个方法是不存在的,将由 MFFC/vendor/codingbean/macaw/Macaw.php 中的 __callstatic() 接管。

3. 这个函数接受两个参数,$method 和 $params,前者是具体的 function 名称,在这里就是 get,后者是这次调用传递的参数,即 Macaw::get('fuck',function(){...}) 中的两个参数。第一个参数是我们想要监听的 URL 值,第二个参数是一个 PHP 闭包,作为回调,代表 URL 匹配成功后我们想要做的事情。

4. __callstatic() 做的事情也很简单,分别将目标URL(即 /fuck)、HTTP方法(即 GET)和回调代码压入 $routes、$methods 和 $callbacks 三个 Macaw 类的静态成员变量(数组)中。

5. 路由文件最后一行的 Macaw::dispatch(); 方法才是真正处理当前 URL 的地方。能直接匹配到的会直接调用回调,不能直接匹配到的将利用正则进行匹配。


下一步:利用 Composer 一步一步构建自己的 PHP 框架(三)——设计 MVC

WRITTEN BY

avatar

评论:

寞踪
2015-09-16 17:48
Macaw 路由得根据代理环境自己改改,不然没法用!
tsh
2015-08-21 14:43
Warning: preg_match(): Compilation failed: unmatched parentheses at offset 5 in D:\MFFC\vendor\NoahBuscher\macaw\Macaw.php on line 117
404  
大神出现了这个问题该怎样解决呢
JohnLui
2015-08-22 13:58
@tsh: 你改了这个文件?目测是语法错误。。。
tsh
2015-08-24 09:10
@JohnLui:没有改过文件
darren
2015-09-16 13:51
@tsh:我在win7+phpstudy+php5.5的集成环境下也遇到了这个问题,我是这样逐步解决的:
1. 先把routes.php的
Macaw::get('fuck',function(){
    echo 'fuck';
});

Macaw::get('(:all)', function($fu) {
    echo 'not match route</br>'.$fu;
});

替换为:
Macaw::get('/fuck',function(){
    echo 'fuck';
});

Macaw::get('/(:all)', function($fu) {
    echo 'not match route</br>'.$fu;
});
这时报错没有了,但是访问 /fuck路径时 ‘not match route’ 字符串也会输出;

2. 根据站长发布的TinyLara框架的TinyRouter.php 文件,把 vendor/noahbuscher/macaw/Macaw.php 文件做了调整:

      __callstatic($method, $params) 方法中,  $uri = $params[0];;
    dispatch() 方法中: $uri = self::detect_uri();
   添加了
    private static function detect_uri()
    {
        $uri = $_SERVER['REQUEST_URI'];
        if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) {
            $uri = substr($uri, strlen($_SERVER['SCRIPT_NAME']));
        } elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) {
            $uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
        }
        if ($uri == '/' || empty($uri)) {
            return '/';
        }
        $uri = parse_url($uri, PHP_URL_PATH);
        return str_replace(array('//', '../'), '/', trim($uri, '/'));
    }
这时routes.php文件中的路由:

Macaw::get('fuck',function(){
    echo 'success';
});

Macaw::get('(:all)', function($fu) {
    echo 'not match route</br>'.$fu;
});
访问正常了,不用添加  ‘/ ’  了
Kenny
2015-11-24 12:54
@darren:Macaw处理URL的时候好像是有问题
鬼国二少
2015-12-15 22:49
@darren:6666,根据这个指示成功了!
peiwen
2015-08-20 18:06
大神,打印__callstatic 中的  $uri = dirname($_SERVER['PHP_SELF']).$params[0];   是带 index.php的 类似这样:比如我配置路由是Macaw::get('/IMG',function(){
    echo 'Img server';
});
那 $uri =   /index.php/foo
我是开启apache重写的, .htaccess文件内容如下
<IfModule mod_rewrite.c>
  Options +FollowSymlinks
  RewriteEngine On

  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>

如果我访问的路径是  myhost.com/foo ,   那么Macaw 中dispatch  里 $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);   这里的$uri = /foo , 这就导致 没法匹配到路由 (因为上面实际是 index.php/foo)
是因为我的重写有问题吗?  (Apache/2.4.4 (Win32) PHP/5.4.16)
JohnLui
2015-08-22 13:55
@peiwen:https://github.com/TinyLara/TinyRouter/blob/master/TinyRouter.php 参考这个文件,这里解决了子目录和第一个 / 的问题。
myth0216
2015-08-14 09:05
__callstatic函数里面,dirname($_SERVER['PHP_SELF']) 反馈的是\ ,会导致路由失败,加一句$uri = str_replace("\\","/",$uri) 解决
JohnLui
2015-08-14 11:01
@myth0216:目测这是你的运行环境的特殊情况
myth0216
2015-08-14 11:07
@JohnLui:我在Apache和Nginx上都试了,对dirname($_SERVER['PHP_SELF']) 也进行输出,结果是'\' ,不知道是不是PHP版本的问题,版本是 php5.6.12
xfice
2015-06-19 16:04
@JohnLui您好,我使用composer安装intervention/image这个图像处理插件的时候出现下面的错误,请问怎么解决啊
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - intervention/image 2.2.2 requires ext-fileinfo * -> the requested PHP exte
nsion fileinfo is missing from your system.
    - intervention/image 2.2.1 requires ext-fileinfo * -> the requested PHP exte
nsion fileinfo is missing from your system.
    - intervention/image 2.2.0 requires ext-fileinfo * -> the requested PHP exte
nsion fileinfo is missing from your system.
    - Installation request for intervention/image ^2.2 -> satisfiable by interve
ntion/image[2.2.0, 2.2.1, 2.2.2].
JohnLui
2015-06-19 16:26
@xfice:缺少 PHP 插件 ext-fileinfo
xfice
2015-06-19 17:17
@JohnLui:@JohnLui  ,您好,是php_fileinfo.dll这个吗。
xfice
2015-06-19 17:30
@JohnLui:如果是php_fileinfo.dll的话我环境中是有的,对了。我是在windows+apache的开发环境
Jay_xy
2015-06-18 14:30
root:---->/var/www/html/public  
[root@localhost public]# pwd
/var/www/html/public
[root@localhost public]# ls -a
.  ..  .htaccess  index.php
[root@localhost public]# cat .htaccess
<IfModule mod_rewrite.c>
        Options +FollowSymlinks
        RewriteEngine On

        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>

[root@localhost public]# cd ../config/
[root@localhost config]# ls
routes.php
[root@localhost config]# cat routes.php
<?php

use NoahBuscher\Macaw\Macaw;

Macaw::get('/fuck',function(){
                echo "Hello world!";
                });

Macaw::get('(:all)',function($fu){
                echo "未匹配到路由<br>".$fu;
                });

Macaw::dispatch();


按大神说的不管我加不加/在fuck前面访问都是404,不知道是那里写错了?
JohnLui
2015-06-18 15:28
@Jay_xy:试一下 /index.php/fuck ?
Jay_xy
2015-06-18 15:59
@JohnLui:找到原因了,是因为我没有开户apache里面的AllowOverride All导致没有读取到.htaccess文件,

现在的情况是我访问
http://192.168.154.132/fuck或者http://192.168.154.132/index.php/fuck的时候都会走all那个路由


use NoahBuscher\Macaw\Macaw;

Macaw::get('fuck',function($fu){
        echo "Hello world!---".$fu;
        });

Macaw::get('(:all)',function($fu){
        echo "未匹配到路由<br>".$fu;
        });

Macaw::dispatch();


未匹配到路由
/index.php/fuck

未匹配到路由
/fuck

不知道哪里还要进行相关的配置那?
wgfy
2015-07-03 13:02
@Jay_xy:我这也是有这个问题,求解
myth0216
2015-08-09 23:11
@Jay_xy:我开了AllowOverride All后,并且设置了.htaccess文件,伪静态还是失败!
Limighten
2016-01-03 13:18
@Jay_xy:感谢楼主分享!来这里的,都是有探索精神的人!
利用节日期间,调试了一下,发现Macaw处理URL的时候可以匹配到`/index.php/fuck'`却不能匹配到 `/fuck`,原因是`_SERVER['PHP_SELF']` 获取到的数据是 `/index.php/fuck` 所以才会这样,经查看_SERVER变量,发现其实是_SERVER['PHP_SELF']  是 `_SERVER['SCRIPT_NAME']` 和 `_SERVER['REQUEST_URI']` 的拼装,所以应该是 _SERVER['REQUEST_URI'] 就ok了。现在动手改掉
    $uri = dirname($_SERVER['PHP_SELF']).$params[0];

   $uri = dirname($_SERVER['REQUEST_URI']).$params[0];
搞定!一切运行正常。另,怎样才不算垃圾评论?
erick
2017-02-06 11:06
@Limighten:$uri = dirname($_SERVER['REQUEST_URI']).$params[0]; 这个拼接方式也有问题。$_SERVER['REQUEST_URI']这个打印出来是一个‘/’。导致正则的时候,有两个‘//’。我去掉了
// $uri = dirname($_SERVER['PHP_SELF']).'/'.$params[0];
    $uri = $params[0];
上面原来的,直接保留一个。这样就可以正常匹配。
string 'fuck' (length=4)
string '/' (length=1)
string '(:all)' (length=6)
Hello world!
杀不死
2017-07-12 16:38
@Limighten:你这个 有点没看懂呢!! 到底留什么删什么
PeaBlog
2015-06-07 00:34
“新建 MFFC/config 文件夹,在里面新建 routs.php 文件” 文件名应该是routes.php
JohnLui
2015-06-07 00:58
@PeaBlog:这么多年过去了才被发现的错误……
su_yin12
2017-10-13 10:23
@JohnLui:只是大家不说
lingxian
2015-06-05 16:40
http://127.0.0.1:81/fuck
Object not found!

The requested URL was not found on this server. If you entered the URL manually please check your spelling and try again.

If you think this is a server error, please contact the webmaster.

Error 404

127.0.0.1
Apache/2.4.12 (Win32) OpenSSL/1.0.1l PHP/5.6.8

<?php

use NoahBuscher\Macaw\Macaw;

Macaw::get('fuck', function() {
  echo "成功!";
});


Macaw::get('/', function() {  
  echo 'Hello world!';
});

Macaw::get('/test', function() {
  echo "成功!";
});

Macaw::dispatch();
但是http://127.0.0.1:81/    这个路由可以显示Hello world!
JohnLui
2015-06-05 18:37
@lingxian:没有做伪静态。使用 http://127.0.0.1:81/index.php/fuck 就可以了
trey
2015-05-04 17:08
Composer project detected, Do you want to create index?
每次用vim打开都会提示这个。。 这个是怎么回事?
JohnLui
2015-05-04 17:40
@trey:关闭那个插件吧
trey
2015-05-15 13:58
@JohnLui:ok了,补全插件没有配置好,谢谢
xiaochuan
2015-04-16 16:50
现在Macaw包的名字不是codingbean/macaw了,这个已经deprecated了,已经改为noahbuscher/macaw了
wdpl
2015-04-09 17:57
你好  使用 PHP 内置 HTTP 服务器,php -S 127.0.0.1:3000  这种方法可以实现;但是怎么发布呢  就是别人无法访问我的系统,怎么做才能让别的电脑可以访问我的系统
JohnLui
2015-04-09 18:00
@wdpl:可以尝试使用 php -S 192.168.x.x:3000,监听局域网 ip。
wdpl
2015-04-09 19:47
@JohnLui:OK  可以了,果然牛逼!!!又有新的疑问,官方文档上说,内置的Web服务器只是提供开发测试使用,不推荐在生产环境中使用;那在此路由的基础上,有什么其他方法访问系统呢
JohnLui
2015-04-09 20:54
@wdpl:用 Apache Nginx 都可以呀
wdpl
2015-04-09 21:26
@JohnLui:不懂  用 Apache怎么做,能不能说一下具体步骤  谢啦 大侠
JohnLui
2015-04-09 21:30
@wdpl:
"然后,将某一个端口用 Apache 或 Nginx 分配给 MFFC/public 目录,这一步十分建议用 Apache 或者 Nginx 做。"
wdpl
2015-04-10 15:12
@JohnLui:按照你的方法,我给MFFC/public目录分配了8080端口
Macaw::get('/', function() {
  echo "fuck!";
});
Macaw::get('/fuck', function() {
  echo "fuck!";
});
但是第一条路由成功,第二条路由失败  提示“Internal Server Error The server encountered an internal error or misconfiguration and was unable to complete your request.”这样信息,然后我把'/'去掉,还是不行,望大侠指点
JohnLui
2015-04-10 15:45
@wdpl: 第二条要去掉第一个 / ,我在上面说过啦
wdpl
2015-04-10 16:01
@JohnLui:改成这样吗?
Macaw::get('/', function() {
  echo "fuck!";
});
Macaw::get('fuck', function() {
  echo "fuck!";
});
测试了  还是不行
wdpl
2015-04-10 16:06
@JohnLui:大侠是不还得设置一下伪静态?如何设置呢?
JohnLui
2015-04-10 17:44
@wdpl:跟 Laravel 一样就行。
JohnLui
2015-04-10 17:44
@wdpl:那就是伪静态的问题了,设置一下 .htaccess 就行。
wdpl
2015-04-10 20:46
@JohnLui:设置了.htaccess,终于实现啦 非常感谢大侠!    大侠啥时出视频教程   非常期待哦!
蒲 蒲
2015-03-27 11:27
你好,我在 routes.php 文件中配置  了三个路由,第一个路由可以访问,但是test方法就报一下错误,伪静态我也配置了
Object not found!
The requested URL was not found on this server. If you entered the URL manually please check your spelling and try again.

If you think this is a server error, please contact the webmaster.

Error 404


<?php
use NoahBuscher\Macaw\Macaw;

Macaw::get('/', function() {  
  echo 'Hello world!';
});

Macaw::get('/test', function() {
  echo "成功!";
});

Macaw::get('(:all)', function($fu) {
  echo '未匹配到路由<br>'.$fu;
});

Macaw::dispatch();
JohnLui
2015-03-27 12:29
@蒲 蒲:去掉第一个 /
LAU
2015-01-23 18:10
您好   通过你的php -S 127.0.0.1:81 这种方法构建路由是成功的。   但是我直接用localhost/mffc/public访问  提示路由不成功。这是什么原因     我想用localhost/mffc/public访问   该如何做?
JohnLui
2015-01-23 18:17
@LAU:不能 除非直接指向 public 文件夹
LAU
2015-01-23 21:13
@JohnLui:怎样直接指向public文件夹
JohnLui
2015-01-24 23:25
@LAU:具体的就是 Apache 和 nginx 的配置了,请自行搜索。。。
堕落到妖灬
2015-01-14 10:11
可不可以出个视频教程》?
LAU
2015-01-12 09:55
根据您的步骤访问127.0.0.1:3000  提示未匹配到路由   什么原因?
JohnLui
2015-01-12 21:20
@LAU:尝试一下 Macaw::get('/') ?
yangbai
2015-01-14 14:00
@JohnLui:嗯,写路由的时候要使用绝对路径,
Macaw::get('/yangbai', function() {
  echo "成功!";
});
不能少了'/',否则匹配不到的,楼主记得把文章更新一下,你那边还一直是
Macaw::get('fuck', function() {
  echo "成功!";
});
JohnLui
2015-01-14 14:36
@yangbai:我的代码没问题,我测过好多种情况,你再自己看看文章。。。
一切还好
2015-01-09 23:54
Fatal error: require(): Failed opening required '../config/routes.php' (include_path='.;c:\php\includes') in D:\wnp\www\MFFC\public\index.php on line 7  这是啥错误? 请教一下
JohnLui
2015-01-10 00:01
@一切还好:这是相对路径载入不兼容导致的,建议参考 https://github.com/TinyLara/TinyLara/blob/master/bootstrap.php 使用绝对路径拼装解决。
大娱乐家
2014-11-14 16:14
访问http://127.0.0.1:81/fuck  结果是404 是什么情况~
JohnLui
2014-11-14 16:26
@大娱乐家:你用了PHP内置服务器?
大娱乐家
2014-11-14 16:55
@JohnLui:用的nginx
JohnLui
2014-11-14 17:00
@大娱乐家:那就应该是伪静态的问题了
大娱乐家
2014-11-14 17:06
@JohnLui:ok~~谢谢~怎么配置方便说一下吗
JohnLui
2014-11-14 17:09
@大娱乐家:跟Laravel一样就行~
大娱乐家
2014-11-14 17:11
@大娱乐家:多谢 打扰了~
JohnLui
2014-11-14 17:28
@大娱乐家:不打扰,欢迎骚扰,本人全天候客服~~
lucien
2014-11-10 13:06
如何实现,子域名路由的功能?
JohnLui
2014-11-10 22:56
@lucien:子域名不建议使用代码进行路由。
yuanchao
2014-10-30 15:08
大神,比如我现在有一个controller,里面有 home ,test,cache,index 等等方法,可不可以不要每次新增一个方法就新增一条路由呀,我觉得应该有,但是我找了下没找着
JohnLui
2014-10-30 15:18
@yuanchao:http://laravel-china.org/docs/controllers#restful-controllers 请仔细看文档。这类问题建议去群里问,我只回复教程相关的问题。
yuanchao
2014-10-30 15:21
@JohnLui:好勒,我去加个组织,谢谢您了,这段时间打扰了
yuanchao
2014-10-30 01:01
那如果这个url 上带参数呢?怎么获取?官方文档好不详细
JohnLui
2014-10-30 01:05
@yuanchao:::get('article/(:num)', function($num) {
  echo $num;
});
yuanchao
2014-10-30 01:09
@JohnLui:恩恩,这样确实是可以,只不过,如何把我们的参数,带到控制器里面去呢?
JohnLui
2014-10-30 01:17
@yuanchao:explode('/', $_SERVER['REQUEST_URI'])[2]

思路不要被限定了,并不是一定要先把参数传过去的。
yuanchao
2014-10-30 01:28
@JohnLui:谢谢指教,早点休息,注意身体
JohnLui
2014-10-30 01:29
@yuanchao:
yuanchao
2014-10-30 01:35
@JohnLui: 还得多和你学习

发表评论:

© 2011-2019 岁寒  |  Powered by Emlog