PHP 小白之路

Hello PHP

让我们在桌面创建一个 hello.php的文件

<?php
     echo "Hello World!";

执行php hello.php
Hooray!!! 我们得到了一个输出 Hello World! 的程序

我们如何通过Http请求输出"Hello World!"呢?
借助 Http 服务器。例如使用 nginx,监听 http 请求,当有对 php 文件的请求发生时,我们将它转发给 php-fpmphp-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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • Welcome 目前网络上充斥着大量的陈旧信息,让PHP新手误入歧途,传播着错误的实践和糟糕的代码,这必须得到纠正...
    layjoy阅读 21,655评论 7 118
  • PHP优化 默认安装的 PHP 就像是在百货商店里购买的普通套装,虽然合身,却不完美。调优的 PHP 就像是定做的...
    师娘哪里去了阅读 494评论 0 0
  • Nginx的工作原理 1.Nginx的模块与工作原理 Nginx由内核和模块组成,其中,内核的设计非常微小和简洁,...
    架构飞毛腿阅读 5,995评论 1 27
  • PHP基础知识 1. 引用变量 概念:在PHP中引用意味着用不同的名字访问同一个变量内容。 定义方式:使用&符号 ...
    LeeShun阅读 414评论 0 0
  • PHP不权威总结 欢迎阅读 本文目标用户是我自己,系统地持续集成PHP方方面面的知识,但不会事无巨细的一一列举,只...
    liufxlucky365阅读 329评论 0 0