Hello PHP
让我们在桌面创建一个 hello.php的文件
<?php
echo "Hello World!";
执行php hello.php
Hooray!!! 我们得到了一个输出 Hello World! 的程序
我们如何通过Http请求输出"Hello World!"呢?
借助 Http 服务器。例如使用 nginx,监听 http 请求,当有对 php 文件的请求发生时,我们将它转发给 php-fpm,php-fpm 执行我们想要执行的 php文件并拿到结果,原路返回
PHP 类
通常情况下,我们需要使用类来执行特定的功能,那么上面的程序我们可以改写为下面这样,我们使用类 Hello 来输出了 Hello World!
<?php
class Hello {
public function index() {
echo "Hello World!";
}
}
(new Hello())->index();
可是如果我们把所有类都写在一个文件里,这个文件将变得臃肿和难以维护。
<?php
class Hello {
public function index() {
echo "Hello World!";
}
}
(new Hello())->index();
class h2{
function __construct(){
echo "H2!";
}
}
(new h2());
- 所以通常情况下,一个文件中我们只放一个类,一个类只负责一件事情,我们的类的调用也通常是在另外的文件中。
修改 hello.php
<?php
class Hello {
public function index() {
echo "Hello World!";
}
}
在桌面新建一个 use.php的文件, 我们将使用这个新的 php 文件作为入口调用 hello.php
<?php
$hello = new Hello();
$hello->index();
执行php use.php
,我们得到了一个报错
PHP Fatal error: Uncaught Error: Class "Hello" not found in /Users/zhangmingsheng/Desktop/use.php:2
Stack trace:
#0 {main}
thrown in /Users/zhangmingsheng/Desktop/use.php on line 2
Fatal error: Uncaught Error: Class "Hello" not found in /Users/zhangmingsheng/Desktop/use.php:2
Stack trace:
#0 {main}
thrown in /Users/zhangmingsheng/Desktop/use.php on line 2
找不到 Class "Hello",这是因为我们在 use.php 中并没有将 Hello 类加载进来
PHP 类的加载
我们可以使用 include 或者 require 方法来加载 php 文件
include在包含文件不存在时会发出警告、在多次包含同一个文件时会重复解析和执行;而require在包含文件不存在时会引发致命错误、在多次包含同一个文件时只包含一次。使用include_once和require_once可以避免重复包含的问题。在实际开发中,我们可以根据具体需求选择适合的函数来使用。
在 use.php中添加 include 语句
<?php
include '/Users/zhangmingsheng/Desktop/hello.php';
$hello = new Hello();
$hello->index();
执行php use.php
,成功输出Hello World!
命名空间
为防止重名,php 引入了命名空间,默认情况下,所有常量、类和函数名都放在全局空间下,就和PHP支持命名空间之前一样。
命名空间通过关键字namespace 来声明。
- 如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间。
<?php
// 定义代码在 'MyProject' 命名空间中
namespace MyProject;
使用命名空间:别名/导入 use
<?php
use My\Full\Classname as Another;
// 下面的例子与 use My\Full\NSname as NSname 相同
use My\Full\NSname;
- use导入可以使用 as 添加别名,如果没有使用 as,则最后一段路径名将作为别名
使用命名空间改写我们的代码,
修改hello.php
<?php
namespace HelloSpace;
class Hello {
public function index() {
echo "Hello World!";
}
}
修改use.php
<?php
use HelloSpace\Hello;
$hello = new Hello();
$hello->index();
执行php use.php
,我们得到了一个报错
PHP Fatal error: Uncaught Error: Class "HelloSpace\Hello" not found in /Users/zhangmingsheng/Desktop/use.php:3
Stack trace:
#0 {main}
thrown in /Users/zhangmingsheng/Desktop/use.php on line 3
Fatal error: Uncaught Error: Class "HelloSpace\Hello" not found in /Users/zhangmingsheng/Desktop/use.php:3
Stack trace:
#0 {main}
thrown in /Users/zhangmingsheng/Desktop/use.php on line 3
找不到 Class "HelloSpace\Hello"
,原来,use 并不能直接用来加载 php 文件,使用 use 的前提是,use 的目标类文件已经被加载,否则就会报错,我们仍然需要使用 include 语句来加载 hello.php
添加 include 语句,一切恢复正常
<?php
include '/Users/zhangmingsheng/Desktop/hello.php';
use HelloSpace\Hello;
$hello = new Hello();
$hello->index();
use 的特殊性
在上面的例子中我们看到,use 并不能直接用来加载 php 文件,使用 use 的前提是,类文件已经被加载,否则就会报找不到类的错误。
但是 use 又有它的特殊性,就是 use 可以作为前向引用,什么意思呢,我们对use.php进行下微调
<?php
use HelloSpace\Hello;
include '/Users/zhangmingsheng/Desktop/hello.php';
$hello = new Hello();
$hello->index();
再次执行php use.php
, 成功输出了Hello World!
啊咧咧?不是说需要先 include 么,为什么这次又可以了呢?
- 这是因为我们虽然声明了
use HelloSpace\Hello;
,但是还没有开始调用 Hello 类,
use 可以提前声明我要使用 Hello,只要保证在真正使用 Hello 前,Hello 类可以完成加载。
所以如果我们再次如下调整,猜猜结果会怎样呢?
<?php
use HelloSpace\Hello;
$hello = new Hello();
$hello->index();
include '/Users/zhangmingsheng/Desktop/hello.php';
没错,我们又获得了找不到 Class "HelloSpace\Hello"
的报错
关于use 的前向引用,在 laravel 框架的入口文件也有体现
<?php
//注意这两个 use 声明
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
$kernel->terminate($request, $response);
我们发现 autoload.php
自动加载文件是在 use 声明后才require 进来的,这里就展示了 use 的特殊性。
那既然提到了自动加载,那什么是自动加载呢?
PHP自动加载
在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件。 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表(每个类都有个文件)。
spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。
像 class 一样的结构都可以以相同方式自动加载。包括类、接口、trait 和枚举。
在桌面创建自动加载类Autoloader
<?php
class Autoloader{
function __construct(){
spl_autoload_register(array(__CLASS__, 'autoload'));
}
public static function autoload($class){
include '/Users/zhangmingsheng/Desktop/hello.php';
}
}
此类中,我们注册了自动加载函数autoload
,当遇到未加载的类的时候,就去 include hello.php
正常情况下,这里会根据$class
参数判断具体要 include 哪个文件,我们这里做了简化,只 include hello.php
接着,我们修改 use.php
<?php
include 'autoloader.php';
new Autoloader();
use HelloSpace\Hello;
$hello = new Hello();
$hello->index();
运行 php use.php
,成功输出 Hello World!
我们发现,我们这次并没有在 use.php
中直接 include hello.php
,而是通过autoloader
自动加载了hello.php
。
将引入 autoloader
的两条语句注释,再次运行,报错,找不到 Class "HelloSpace\Hello"
。
这就是php的自动加载机制一个简单的演示
自动类加载器
-
想想我们之前的自动加载模型,还有哪些地方可以改进?
对,我们不是根据实际需要的类进行相应类的加载。现在我们来完善它,写一个可用的自动加载器吧
前面我们已经简单讲过 namespace,namespace像 java 的 package 一样,可以防止类名重复引发问题。但 java 的 package 还有一个附带的作用,就是 package可以推倒出类文件的目录,这样当我们知道一个类的 package 的时候,我们也能推断出如何加载这个类。那我们 php 的 namespace 是否也可以具有这样的功能呢?答案是可以的
- 与目录和文件的关系很像,PHP 命名空间也允许指定层次化的命名空间的名称。
因此,命名空间的名字可以使用分层次的方式定义:
<?php
namespace MyProject\Sub\Level; //声明分层次的单个命名空间
const CONNECT_OK = 1;
class Connection { /* ... */ }
function Connect() { /* ... */ }
我们现在让我们的程序的命名空间与目录名对应起来
在桌面创建 HelloSpace
目录,将桌面的hello.php
移入其中
<?php
namespace HelloSpace;
class Hello {
public function index() {
echo "Hello World!";
}
}
我们按照命名空间对应目录名的规则,修改autoloader.php
<?php
class Autoloader{
function __construct(){
spl_autoload_register(array(__CLASS__, 'autoload'));
}
public static function autoload($class){
$filePath = str_replace('\\', '/', $class);
require_once $filePath . '.php';
}
}
运行 php use.php
,成功输出 HelloWorld!
- 可是在我们开发的时候有时候命名空间并不一定和目录名完全对应,为了兼容这种情况,我们就需要使用 Map,设置他们的对应关系
修改autoloader.php
使我们可以添加Map映射
<?php
class Autoloader{
protected $base_dir = "/Users/zhangmingsheng/desktop";
protected $maps = [];
function __construct(){
spl_autoload_register(array(__CLASS__, 'autoload'));
}
public function autoload($class){
//完整的类名由命名空间名和类名组成
//得到命名空间名
$pos = strrpos($class, '\\');
$namespace = substr($class, 0, $pos);
//得到类名
$realClass = strtolower(substr($class, $pos + 1));
//根据命名空间名和类名得到文件路径
$this->mapLoad($namespace, $realClass);
}
protected function mapLoad($namespace, $realClass){
if(isset($this->maps[$namespace])){
$namespace = $this->maps[$namespace];
}
//处理路径, 将命名空间中的\替换成/. 由于 namespace 有些带有\有些不带有\, 所以需要rtrim处理
$namespace = rtrim(str_replace('\\', '/', $namespace),'/').'/';
$filePath = $this->base_dir . '/' . $namespace . $realClass . '.php';
//将该文件包含进来即可
if(file_exists($filePath)){
require_once $filePath;
}else{
echo sprintf("%s文件不存在", $filePath);
}
}
function addMaps($namespace, $path){
if(isset($this->maps[$namespace])){
echo sprintf("%s已经存在", $namespace);
return;
}
$this->maps[$namespace] = $path;
}
}
接下来,我们创建一个新的php文件来验证我们的 autoloader,
在桌面创建目录 relationship,在 relationship 目录下,创建spare目录, 在 spare 中创建xiaoming.php
<?php
namespace Spare;
class Xiaoming {
public function sing() {
echo "xiaoming sing!";
}
public function dance() {
echo "xiaoming dance!";
}
}
此时xiaoming.php的目录名是 relationship/spare/xiaoming.php,而 Xiaoming 类的命名空间是 Spare。
修改use.php
,添加 map 映射
<?php
include 'autoloader.php';
$auto = new Autoloader();
$auto->addMaps('Spare', 'relationship/spare');
use Spare\Xiaoming;
$xm = new Xiaoming();
$xm->dance();
执行 php use.php
成功输出 xiaoming dance!
至此,我们的自动加载器就完成了
laravel Request的生命周期
PHP 主要的工作是处理 Http 请求,所以 PHP 的开发框架的核心也都围绕 Http 请求服务,所以了解请求的生命周期对理解 laravel 框架很有帮助
要说生命,那肯定要从生命的诞生开始,我们找到 laravel 的入口文件 index.php, 看看它都做了什么
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
$kernel->terminate($request, $response);
use 语句前面已经讲过了,我们往后看
首先定义LARAVEL_START
常量,然后判断是否处于maintenance
模式。之后才进入正题:
- 加载
autoload.php
- 加载
app.php
- 初始化
Http\Kernel
- 处理
request
- 处理
response
前面已经简单讲述过autoload,现在我们看下 laravel 的app
到底是什么, app
是一个 service container
,那什么是service container
呢?
这里引用下文档原文
The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.
上面的定义中提到了依赖注入,那什么是依赖注入呢?可以看下我之前写的一篇短文,依赖注入(DI),控制反转(IOC),依赖反转(依赖倒置/DIP)
短文中显示的举例了什么是依赖注入,同时也提到 laravel的app
使用自动加载来隐式的完成了依赖注入的
可能看到这里,虽然知道了 app
能做什么,但是依然想不明白app
到底做了啥。
- app其实为你提供了一个workspace, 它为你管理依赖关系,从而可以让你把精力集中在业务逻辑中。
它就像是炒菜锅,使你能专注在如何炒好菜,调好味道,而不用担心一不留神,火就把菜烧成了灰烬,而我们炒菜的第一步,就是要有一个炒菜锅
有了app
后,我们进入Http\Kernel
的初始化
我们来看下Illuminate\Foundation\Http\Kernel.php