美文网首页
PHP 小白之路

PHP 小白之路

作者: 張小明 | 来源:发表于2024-03-20 15:06 被阅读0次

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 框架很有帮助

composer 的自动加载

相关文章

网友评论

      本文标题:PHP 小白之路

      本文链接:https://www.haomeiwen.com/subject/ftsxtjtx.html