13、魔术常量

PHP 向它运行的任何脚本提供了大量的预定义常量。不过很多常量都是由不同的扩展库定义的,只有在加载了这些扩展库时才会出现,或者动态加载后,或者在编译时已经包括进去了。PHP有八个魔术常量它们的值随着它们在代码中的位置改变而改变。

常见的八个魔术常量(前后都是双下划线)

  • __LINE__:显示该行代码在文件中的行号,不区分大小写
  • __FILE__:显示文件的完整路径和文件名。如果被包含在文件中,则返回被包含的文件名
    • PS:PHP 4.0.2 起,__FILE__ 总是包含一个绝对路径(如果是符号连接,则是解析后的绝对路径),而在此之前的版本有时会包含一个相对路径。
  • __DIR__:显示文件所在的目录。如果被包括在文件中,则返回被包括的文件所在的目录
    • PS1:等价于dirname(__FILE__)
    • PS2:除非是根目录,否则目录中名不包括末尾的斜杠/(PHP 5.3.0中新增)
  • __FUNCTION__:显示函数名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该函数被定义时的名字(区分大小写)。
    • PS:在 PHP 4 中该值总是小写字母的。
  • __CLASS__:类的名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该类被定义时的名字(区分大小写)。
    • PS:在 PHP 4 中该值总是小写字母的。类名包括其被声明的作用区域(例如 Foo\Bar)。注意自 PHP 5.4 起 __CLASS__ 对 trait 也起作用。当用在 trait 方法中时,__CLASS__是调用 trait 方法的类的名字。
  • __TRAIT__:Trait 的名字(PHP 5.4.0 新加)。自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 traits。
    • PS1:Trait 名包括其被声明的作用区域(例如 Foo\Bar)。
    • PS2:从基类继承的成员被插入的 SayWorld Trait 中的 MyHelloWorld 方法所覆盖。其行为 MyHelloWorld 类中定义的方法一致。优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法。
  • __METHOD__:类的方法名(PHP 5.0.0 新加)。返回该方法被定义时的名字(区分大小写)。
  • __NAMESPACE__:当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。

实例:

<html>
<body>
    <h1>__LINE__</h1>
    <?php
        echo '这是第 “ ' . __LINE__ . ' ” 行';  // 这是第 “ 5 ” 行
    ?>

    <h1>__FILE__</h1>
    <?php
        echo '该文件位于 “ ' . __FILE__ . ' ” '; // 该文件位于 " E:\wamp\www\test\index.php "
    ?>

    <h1>__DIR__</h1>
    <?php
        echo '该文件位于 “ '  . __DIR__ . ' ” '; // 该文件位于 “ E:\wamp\www\test ”
    ?>

    <h1>__FUNCTION__</h1>
    <?php
        function test() {
            echo  '函数名为:' . __FUNCTION__ ;
        }
        test(); // 函数名为:test
    ?>

    <h1>__CLASS__</h1>
    <?php
        class test {
            function _print() {
                echo '类名为:'  . __CLASS__ . " ";
                echo  '函数名为:' . __FUNCTION__ ;
            }
        }
        $t = new test();
        $t->_print();    // 类名为:test 函数名为:_print
    ?>

    <h1>__TRAIT__</h1>
    <?php
        class Base {
            public function sayHello() {
                echo 'Hello ';
            }
        }
        trait SayWorld {
            public function sayHello() {
                parent::sayHello();
                echo 'World!';
            }
        }
        class MyHelloWorld extends Base {
            use SayWorld;
        }
        $o = new MyHelloWorld();
        $o->sayHello();  // Hello World!
    ?>

    <h1>__METHOD__</h1>
    <?php
        function test() {
            echo  '函数名为:' . __METHOD__ ;
        }
        test(); // 函数名为:test
    ?>

    <h1>__DIR__</h1>
    <?php
        namespace MyProject;
        echo '命名空间为:"', __NAMESPACE__, '"'; // 输出 "MyProject"
        // 命名空间为:"MyProject"
    ?>
</body>
</html>

14、命名空间★★

没太理解,所以附上原文:

具体笔记地址:PHP命名空间是什么意思 - php完全自学手册 - php中文网手册

  • 命名空间(namespace):是一种封装事物的方法。默认情况下,所有常量、类和函数名都放在全局空间下。
  • 解释:
    • 在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮演了命名空间的角色。
    • 具体举个例子,文件 foo.txt 可以同时在目录/home/greg/home/other 中存在,但在同一个目录中不能存在两个 foo.txt 文件。
    • 另外,在目录 /home/greg 外访问 foo.txt 文件时,我们必须将目录名以及目录分隔符放在文件名之前得到 /home/greg/foo.txt。这个原理应用到程序设计领域就是命名空间的概念。
  • 作用:解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产生一个致命的错误。这种情况下只要避免命名重复就可以解决,最常见的一种做法是约定一个前缀。
    • 解决问题1:用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突
    • 解决问题2:为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性
  • 语法:namespace 命名空间名;,后续的代码中的常量、类名、函数名都存放于该命名空间下,直到再次定义命名空间
  • 注意:在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare 语句。所有非 PHP 代码包括空白符都不能出现在命名空间的声明之前。
  • 举例:
<?php
    declare(encoding='UTF-8'); //定义多个命名空间和不包含在命名空间中的代码
    namespace MyProject {
        const CONNECT_OK = 1;
        class Connection { /* ... */ }
        function connect() { /* ... */  }
    }
    namespace { // 全局代码
        session_start();
        $a = MyProject\connect();
        echo MyProject\Connection::start();
    }
?>
  • 错误示例:
<html>
<?php
namespace MyProject; // 命名空间前出现了“<html>” 会致命错误 - 命名空间必须是程序脚本的第一条语句
?>
  • 子命名空间:与目录和文件的关系很象,PHP 命名空间也允许指定层次化的命名空间的名称。因此,命名空间的名字可以使用分层次的方式定义:
<?php
    namespace MyProject\Sub\Level;  //声明分层次的单个命名空间
    const CONNECT_OK = 1;
    class Connection { /* ... */ }
    function Connect() { /* ... */  }
?>
  • 命名空间的使用:定义
    1. 非限定名称,或不包含前缀的名称,
      • 例如:$a = new foo();或者foo::staticmethod();
      • 如果当前命名空间为currentnamespacefoo会被解析为urrentnamespace\foo
      • 如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo
      • ==警告==:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。
    2. 限定名称,或包含前缀的名称
      • 例如:$a = new subnamespace\foo();或者subnamespace\foo::staticmethod();
      • 如果当前命名空间为currentnamespacefoo会被解析为currentnamespace\subnamespace\foo
      • 如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为subnamespace\foo
    3. 完全限定名称,或包含了全局前缀操作符的名称
      • 例如:$a = new \currentnamespace\foo();或者\currentnamespace\foo::staticmethod();
      • 在这种情况下,foo 总是被解析为代码中的文字名(literal name) currentnamespace\foo
      • 注意访问任意全局类、函数或常量,都可以使用完全限定名称,例如 \strlen()\Exception\INI_ALL
  • 实例
<html>
<body>
    <h1>file1.php</h1>
    <?php
        namespace Foo\Bar\subnamespace; 
        const FOO = 1;
        function foo() {}
        class foo {
            static function staticmethod() {}
        }
    ?>
</body>
</html>
<html>
<body>
    <h1>file2.php</h1>
    <?php
        namespace Foo\Bar;
        include 'file1.php';
        const FOO = 2;
        function foo() {}
        class foo {
            static function staticmethod() {}
        }
        /* 非限定名称 */
        foo(); // 解析为 Foo\Bar\foo resolves to function Foo\Bar\foo
        foo::staticmethod(); // 解析为类 Foo\Bar\foo的静态方法staticmethod。resolves to class Foo\Bar\foo, method staticmethod
        echo FOO; // resolves to constant Foo\Bar\FOO
        /* 限定名称 */
        subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo
        subnamespace\foo::staticmethod(); // 解析为类 Foo\Bar\subnamespace\foo,
                                          // 以及类的方法 staticmethod
        echo subnamespace\FOO; // 解析为常量 Foo\Bar\subnamespace\FOO

        /* 完全限定名称 */
        \Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
        \Foo\Bar\foo::staticmethod(); // 解析为类 Foo\Bar\foo, 以及类的方法 staticmethod
        echo \Foo\Bar\FOO; // 解析为常量 Foo\Bar\FOO
    ?>
</body>
</html>
  • 命名空间和动态语言特征:命名空间的实现受到其语言自身的动态特征的影响,因此必须使用完全限定名称(包括命名空间前缀的类名称)。

    • 注意:因为在动态的类名称、函数名称或常量名称中,限定名称和完全限定名称没有区别,因此其前导的反斜杠是不必要的。(最开始的\
    • 扩展:动态语言特征
    • 动态类型:在 PHP 中,变量的类型是根据赋给它们的值来确定的,而不是在编译时静态定义的。这意味着同一个变量可以在不同的上下文中保存不同类型的值。
    • 弱类型:PHP 是一种弱类型语言,即变量的类型转换是隐式进行的。PHP会尝试将变量在需要时转换为适当的类型,而无需显式的类型声明或转换。
    • 动态变量:在 PHP 中,可以在运行时创建新的变量,也可以在不同变量之间动态地传递值。
    • 动态函数调用:PHP 允许通过变量名来调用函数,这使得在运行时动态地调用不同函数成为可能。
    • 动态类构建:PHP 支持在运行时动态创建类和对象,包括动态添加属性和方法到已经存在的类中。
    • 可变变量:PHP 允许使用可变变量名来引用变量的变量,这使得在运行时动态访问和操作变量成为可能。
    • 反射:PHP 提供了反射类(Reflection Class),可以在运行时检查类、方法和属性的信息,以及动态调用它们。
  • namespace关键字和__NAMESPACE__常量

    • 魔术常量__NAMESPACE__:当在命名空间中使用时,__NAMESPACE__ 会返回当前命名空间的名称字符串;而在全局作用域中使用时,它会返回空字符串。
    • namespace的使用:详见上面的笔记 命名空间的使用:定义
  • 使用命名空间:别名/导入

    • 命名空间支持两种使用别名或导入方式:为类名称使用别名,或为命名空间名称使用别名。注意PHP不支持导入函数或常量。
    • 别名:使用 use 关键字为一个长命名空间或类起一个短别名,这样在后续使用时可以直接使用短别名而无需写整个命名空间。
    • 举例:use My\Long\namespace\ClassName as newName;
    • 导入:想要导入命名空间下的所有类,可以使用 use 关键字后跟一个反斜杠 \ 来实现。
    • 举例1 - 导入所有:use \
    • 举例2 - 选择性导入:use My\Long\Namespace\SomeOtherNamespace\{Class1, Class2, Class3};
    • 举例3 - 多个导入:use My\Full\Classname as Another, My\Full\NSname;
  • 使用命名空间:后备全局函数/常量

    • 在一个命名空间中,当遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。类名称总是解析到当前命名空间中的名称。因此在访问系统内部或不包含在命名空间中的类名称时,必须使用完全限定名称

    • 对于函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP 会退而使用全局空间中的函数或常量。

    • 举例:

    在该例子中,当尝试访问 undefined_constant 常量和调用 undefined_function() 函数时,由于它们在当前命名空间中未定义,PHP 会自动回退到全局作用域去查找是否有定义这些常量和函数。如果全局作用域中也没有定义,那么 PHP 将会抛出相应的错误。

    这种机制使得在命名空间中可以方便地使用全局作用域中定义的函数和常量,同时也能保持灵活性和避免命名冲突。

<?php
    namespace A\B;

    const MY_CONSTANT = 123;

    function my_function() {
        echo 'Function in namespace A\B';
    }

    echo MY_CONSTANT; // 输出 123
    my_function(); // 输出 'Function in namespace A\B'

    // 调用未定义在命名空间内的函数和常量
    echo undefined_constant; // 将会尝试访问全局作用域中的 undefined_constant 常量
    undefined_function(); // 将会尝试调用全局作用域中的 undefined_function 函数
?>
  • 全局空间:如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间。
    • 使用:在名称前加上前缀 \ 表示该名称是全局空间中的名称,即使该名称位于其它的命名空间中时也是如此。
    • 举例:
<?php
    namespace A\B\C;
    /* 这个函数是 A\B\C\fopen */
    function fopen() { 
         /* ... */
         $f = \fopen(...); // 调用全局的fopen函数
         return $f;
    } 
?>
  • 命名空间的顺序:自从有了命名空间之后,最容易出错的该是使用类的时候,这个类的寻找路径是什么样的了。
    • 解析原则:
      1. 对完全限定名称的函数,类和常量的调用在编译时解析。例如 new \A\B 解析为类 A\B。
      2. 所有的非限定名称和限定名称(非完全限定名称)根据当前的导入规则在编译时进行转换。例如,如果命名空间A\B\C 被导入为 C,那么对 C\D\e() 的调用就会被转换为 A\B\C\D\e()。
      3. 在命名空间内部,所有的没有根据导入规则转换的限定名称均会在其前面加上当前的命名空间名称。例如,在命名空间 A\B 内部调用 C\D\e(),则 C\D\e() 会被转换为 A\B\C\D\e() 。
      4. 非限定类名根据当前的导入规则在编译时转换(用全名代替短的导入名称)。例如,如果命名空间A\B\C 导入为C,则 new C() 被转换为 new A\B\C() 。
      5. 在命名空间内部(例如A\B),对非限定名称的函数调用是在运行时解析的。例如对函数 foo() 的调用是这样解析的:
      6. 在当前命名空间中查找名为 A\B\foo() 的函数
      7. 尝试查找并调用 全局(global) 空间中的函数 foo()。
      8. 在命名空间(例如A\B)内部对非限定名称或限定名称类(非完全限定名称)的调用是在运行时解析的。下面是调用 new C() 及 new D\E() 的解析过程:new C()的解析: new D\E()的解析:为了引用全局命名空间中的全局类,必须使用完全限定名称 new \C()。
      9. 在类名称前面加上当前命名空间名称变成:A\B\D\E,然后查找该类。
      10. 尝试自动装载类 A\B\D\E。
      11. 在当前命名空间中查找A\B\C类。
      12. 尝试自动装载类A\B\C。
    • 代码:
<?php
    namespace A;
    use B\D, C\E as F;
    // 函数调用
    foo();      // 首先尝试调用定义在命名空间"A"中的函数foo()
                // 再尝试调用全局函数 "foo"
    \foo();     // 调用全局空间函数 "foo" 
    my\foo();   // 调用定义在命名空间"A\my"中函数 "foo" 
    F();        // 首先尝试调用定义在命名空间"A"中的函数 "F" 
                // 再尝试调用全局函数 "F"
    // 类引用
    new B();    // 创建命名空间 "A" 中定义的类 "B" 的一个对象
                // 如果未找到,则尝试自动装载类 "A\B"
    new D();    // 使用导入规则,创建命名空间 "B" 中定义的类 "D" 的一个对象
                // 如果未找到,则尝试自动装载类 "B\D"
    new F();    // 使用导入规则,创建命名空间 "C" 中定义的类 "E" 的一个对象
                // 如果未找到,则尝试自动装载类 "C\E"
    new \B();   // 创建定义在全局空间中的类 "B" 的一个对象
                // 如果未发现,则尝试自动装载类 "B"
    new \D();   // 创建定义在全局空间中的类 "D" 的一个对象
                // 如果未发现,则尝试自动装载类 "D"
    new \F();   // 创建定义在全局空间中的类 "F" 的一个对象
                // 如果未发现,则尝试自动装载类 "F"
    // 调用另一个命名空间中的静态方法或命名空间函数
    B\foo();    // 调用命名空间 "A\B" 中函数 "foo"
    B::foo();   // 调用命名空间 "A" 中定义的类 "B" 的 "foo" 方法
                // 如果未找到类 "A\B" ,则尝试自动装载类 "A\B"
    D::foo();   // 使用导入规则,调用命名空间 "B" 中定义的类 "D" 的 "foo" 方法
                // 如果类 "B\D" 未找到,则尝试自动装载类 "B\D"
    \B\foo();   // 调用命名空间 "B" 中的函数 "foo" 
    \B::foo();  // 调用全局空间中的类 "B" 的 "foo" 方法
                // 如果类 "B" 未找到,则尝试自动装载类 "B"
    // 当前命名空间中的静态方法或函数
    A\B::foo();   // 调用命名空间 "A\A" 中定义的类 "B" 的 "foo" 方法
                  // 如果类 "A\A\B" 未找到,则尝试自动装载类 "A\A\B"
    \A\B::foo();  // 调用命名空间 "A\B" 中定义的类 "B" 的 "foo" 方法
                  // 如果类 "A\B" 未找到,则尝试自动装载类 "A\B"
?>

15、面向对象★★★★★

(1)引子

  • 基本概念:在面向对象的程序设计(英语:Object-oriented programming,缩写:OOP)中,对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。简而言之,万物皆对象。
  • 对象的三个特性:
    • 对象的行为:对对象施加的操作,如开灯、关门就是行为
    • 对象的形态:当时加方法时对象是如何响应,如灯亮了、门关上了
    • 对象的表示:类似于对象的身份证,具体区分于相同的行为形态表示有什么不同
    • 举例:Animal(动物)是一个抽象类,具体到某种动物如Dog()、Cow()等是实例化的过程,Dog()、Cow()就是具体的对象,有颜色、名字、叫声等不同的行为状态。

(2)面向对象的内容

  • :定义了一件事物的抽象特点。类的定义包含了数据的形式以及对数据的操作。
    • 定义的语法:class 类名 { 定义变量和函数 }
    • 注意1:变量的声明需要使用关键字var,可以设置初始值也可以只声明不赋值
    • 注意2:函数的定义与一般的函数定义一致,但是使用类的函数则需要通过类及其实例化的对象访问
  • 对象:是类的实例。
  • 成员变量:定义在类内部的变量。该变量的值对外是不可见的,但是可以通过成员函数访问,在类被实例化为对象后,该变量即可称为对象的属性。
  • 成员函数:定义在类的内部,可用于访问对象的数据。
  • 继承(Extend):继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。
  • 父类:一个类被其他类继承,可将该类称为父类,或基类,或超类。
  • 子类:一个类继承其他类称为子类,也可称为派生类。
  • 多态:多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。
  • 重载 :简单说,就是函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
  • 抽象性:抽象性是指将具有一致的数据结构(属性)和行为(操作)的对象抽象成类。一个类就是这样一种抽象,它反映了与应用有关的重要性质,而忽略其他一些无关内容。任何类的划分都是主观的,但必须与具体的应用有关。
  • 封装:封装是指将现实世界中存在的某个客体的属性与行为绑定在一起,并放置在一个逻辑单元内。
  • 构造函数:主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。
  • 析构函数:析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做"清理善后" 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。

举例:

<?php
    // 创建一个类
  class Site {
    /* 成员变量 */
    var $url;
    var $title;

    /* 成员函数 */
    function setUrl($par){
      $this->url = $par;
    }

    function getUrl(){
      echo $this->url . PHP_EOL;
    }

    function setTitle($par){
      $this->title = $par;
    }

    function getTitle(){
      echo $this->title . PHP_EOL;
    }
  }

    // 实例化该类
    $runoob = new Site;
  $taobao = new Site;
  $google = new Site;

    // 调用成员函数,设置标题和URL
  $runoob->setTitle( "菜鸟教程" );
  $taobao->setTitle( "淘宝" );
  $google->setTitle( "Google 搜索" );

  $runoob->setUrl( 'www.runoob.com' );
  $taobao->setUrl( 'www.taobao.com' );
  $google->setUrl( 'www.google.com' );

  // 调用成员函数,获取标题和URL
  $runoob->getTitle();   // 菜鸟教程
  $taobao->getTitle();   // 淘宝
  $google->getTitle();   // Google 搜索

  $runoob->getUrl(); // www.runoob.com
  $taobao->getUrl(); // www.taobao.com
  $google->getUrl(); // www.google.com

?>

(3)构造函数

  • 构造函数是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,在创建对象的语句中与 new 运算符一起使用。
  • PHP 5 允许开发者在一个类中定义一个方法作为构造函数
    • 语法:void __construct ([ mixed $args [, $... ]] )
    • 优化上述案例的成员函数:(8~23行)
function __construct( $par1, $par2 ) {
  $this->url = $par1;
  $this->title = $par2;
}
  • 优化实例化对象:(26~47行)
$runoob = new Site('www.runoob.com', '菜鸟教程');
$taobao = new Site('www.taobao.com', '淘宝');
$google = new Site('www.google.com', 'Google 搜索');

// 调用成员函数,获取标题和URL
$runoob->getTitle();
$taobao->getTitle();
$google->getTitle();

$runoob->getUrl();
$taobao->getUrl();
$google->getUrl();

(4)析构函数

  • 析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。
    • 语法:void __destruct ( void )
    • 举例:
<?php
  class MyDestructableClass {
    function __construct() {
      print "构造函数\n";
      $this->name = "MyDestructableClass";
    }

    function __destruct() {
      print "销毁 " . $this->name . "\n";
    }
  }

  $obj = new MyDestructableClass();
    /** 
  *    构造函数
  *    销毁 MyDestructableClass
    */
?>

(5)继承

  • 使用关键字 extends 来继承一个类,PHP 不支持多继承
  • 语法:
class Child extends Parent {
  // 代码部分
}
  • 解释:
    • Child:子类,新创建的,通过继承获得Parent中可继承的变量和函数
    • Parent:父类,提前创建好的
  • 举例:创建子类Child_Site继承Site
<?php 
  // 子类扩展站点类别
  class Child_Site extends Site {
    var $category;

    function setCate($par){
      $this->category = $par;
    }

    function getCate(){
      echo $this->category . PHP_EOL;
    }
  }
?>

(6)方法重写

  • 如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 举例:子类Child_Site重写getUrl()getTitle()方法
<?php 
  class Child_Site extends Site {
    /* 其他代码 */

    // 方法重写
    function getUrl() {
      echo $this->url . PHP_EOL;
      return $this->url;
    }

    function getTitle(){
      echo $this->title . PHP_EOL;
      return $this->title;
    }
  }
?>

(7)访问控制

  • PHP 对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。
  • public(公有):公有的类成员可以在任何地方被访问。
  • protected(受保护):受保护的类成员则可以被其自身以及其子类和父类访问。
  • private(私有):私有的类成员则只能被其定义所在的类访问。
  • 注意1:类中的属性必须定义为公有受保护私有之一。如果用 var 定义,则被视为公有。
  • 注意2:类中的方法可以被定义为公有私有受保护。如果没有设置这些关键字,则该方法默认为公有。
  • 举例1:
<?php
  /**
     * Define MyClass
     */
  class MyClass {
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';

    function printHello() {
      echo $this->public;
      echo $this->protected;
      echo $this->private;
    }

    // 声明一个公有的构造函数
    public function __construct() { }

    // 声明一个公有的方法
    public function MyPublic() { }

    // 声明一个受保护的方法
    protected function MyProtected() { }

    // 声明一个私有的方法
    private function MyPrivate() { }

    // 此方法为公有
    function Foo() {
      $this->MyPublic();
      $this->MyProtected();
      $this->MyPrivate();
    }
  }

  // 测试父类属性的访问控制
  $obj = new MyClass();
  echo $obj->public; // 这行能被正常执行
  echo $obj->protected; // 这行会产生一个致命错误
  echo $obj->private; // 这行也会产生一个致命错误
  $obj->printHello(); // 输出 Public、Protected 和 Private

  // 测试父类方法的访问控制
  $myclass = new MyClass;
  $myclass->MyPublic(); // 这行能被正常执行
  $myclass->MyProtected(); // 这行会产生一个致命错误
  $myclass->MyPrivate(); // 这行会产生一个致命错误
  $myclass->Foo(); // 公有,受保护,私有都可以执行

  /**
       * Define MyClass2
       */
  class MyClass2 extends MyClass
  {
    // 可以对 public 和 protected 进行重定义,但 private 而不能
    protected $protected = 'Protected2';

    function printHello()
    {
      echo $this->public;
      echo $this->protected;
      echo $this->private;
    }

    // 此方法为公有
    function Foo2()
    {
      $this->MyPublic();
      $this->MyProtected();
      $this->MyPrivate(); // 这行会产生一个致命错误
    }
  }

  // 测试子类属性的访问控制
  $obj2 = new MyClass2();
  echo $obj2->public; // 这行能被正常执行
  echo $obj2->private; // 未定义 private
  echo $obj2->protected; // 这行会产生一个致命错误
  $obj2->printHello(); // 输出 Public、Protected2 和 Undefined
  // 测试子类方法的访问控制
  $myclass2 = new MyClass2;
  $myclass2->MyPublic(); // 这行能被正常执行
  $myclass2->Foo2(); // 公有的和受保护的都可执行,但私有的不行
?>
  • 举例2:
<?php
  class Bar {
    public function test() {
      $this->testPrivate();
      $this->testPublic();
    }

    public function testPublic() {
      echo "Bar::testPublic\n";
    }

    private function testPrivate() {
      echo "Bar::testPrivate\n";
    }
  }

  class Foo extends Bar {   
    // 在 Foo 类中重写了 Bar 类中的 testPublic() 方法和创建了一个同名方法 testPrivate() 方法
    public function testPublic() {
      echo "Foo::testPublic\n";
    }

    private function testPrivate() {
      echo "Foo::testPrivate\n";
    }
  }

  // 由于子类没有重写test()方法,所以子类的实例化对象$myFoo的test()来自父类,从而导致调用test()方法时访问的是父类testPrivate()方法和子类重写后的testPublic()方法
  $myFoo = new foo();
  $myFoo->test(); // Bar::testPrivate 
  // Foo::testPublic
?>

(8)接口

  • 接口(interface):可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
  • 接口的定义语法:interface 接口名称 { 方法 }
    • 解释:接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的。
    • 注意:接口中定义的所有方法都必须是公有,这是接口的特性。
  • 接口的使用语法:class 类名 implements 接口1,接口2,... { 属性和方法 }
    • 解释:要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。
  • 举例:
<?php
  // 定义第一个接口
  interface Logger {
    public function log($message);
  }

  // 定义第二个接口
  interface Messenger {
    public function sendMessage($to, $message);
  }

  // 创建一个类来实现上述两个接口
  class MyCommunication implements Logger, Messenger {
    public function log($message) {
      echo "Logging message: " . $message;
    }

    public function sendMessage($to, $message) {
      echo "Sending message to " . $to . ": " . $message;
    }
  }

  // 实例化类并调用方法
  $communication = new MyCommunication();
  $communication->log("This is a log message");
  $communication->sendMessage("John", "Hello, how are you?");
?>

(9)常量

  • 可以把在类中始终保持不变的值定义为常量。
  • 常量的定义语法:const 常量名 = 值;
    • 解释:在定义和使用常量的时候不需要使用 $ 符号。
    • PS:类中的常量可以直接通过类名::常量名的方式调用
    • 注意:常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用
  • 动态访问类常量:可以在运行时使用变量或对象来访问类中定义的常量。这意味着我们不需要在编写代码时就确定要访问的类常量,而是可以根据程序运行时的情况来动态确定要访问的常量。
    • 注意:PHP 5.3.0版本之后才支持动态访问类常量
    • 举例:
<?php
  class MyClass {
    const CONSTANT = 'Hello, World!';
  }

  $className = 'MyClass';
  echo $className::CONSTANT; // 输出:Hello, World!

  $obj = new MyClass();
  echo $obj::CONSTANT; // 输出:Hello, World!
?>

(10)抽象类

  • 定义:任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。
  • 解释:抽象类是一个不能被实例化的类,只能用来被其他类继承的类。抽象类通常包含了一些方法的声明,但没有为这些方法提供具体的实现,而是留给子类去实现这些方法。
  • 抽象类定义的语法:abstract 抽象类名 { 抽象方法(必选)和普通方法(可选) }
    • 抽象类中可以包含抽象方法(即只有方法的声明,没有具体实现),也可以包含普通方法(有具体实现的方法)
  • 举例:
<?php
  abstract class Animal {
    // 抽象方法,子类需要实现
    abstract public function makeSound();

    // 普通方法
    public function sleep() {
      echo "Zzzz...";
    }
  }

  class Dog extends Animal {
    public function makeSound() {
      echo "Woof! Woof!";
    }
  }

  class Cat extends Animal {
    public function makeSound() {
      echo "Meow!";
    }
  }

  $dog = new Dog();
  $dog->makeSound(); // 输出:Woof! Woof!
  $dog->sleep(); // 输出:Zzzz...

  $cat = new Cat();
  $cat->makeSound(); // 输出:Meow!
  $cat->sleep(); // 输出:Zzzz...
?>
  • 解释:
    • Animal 是一个抽象类,其中定义了一个抽象方法 makeSound() 和一个普通方法 sleep()
    • Dog 和类 Cat 继承自抽象类 Animal,并实现了抽象方法 makeSound(),每个子类都有自己特定的声音。

(11)static关键字

  • 作用:声明类属性或方法为 static(静态),就可以不实例化类而直接访问。
  • 注意1:
    • 静态属性不能通过一个类已实例化的对象来访问,即静态属性不可以由对象通过 -> 操作符来访问。
    • 静态方法可以
  • 注意2:由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用。
  • 举例:
<?php
  class MyClass {
    public static $staticProperty = 'Hello, World!';

    public function getStaticProperty() {
      return self::$staticProperty;
    }
  }

  $obj = new MyClass();
  // 通过对象访问静态属性会导致错误
  // echo $obj->staticProperty;

  // 正确的方式是通过类名来访问静态属性
  echo MyClass::$staticProperty; // 输出:Hello, World!

  // 也可以在类内部使用 self 关键字来访问静态属性
  echo $obj->getStaticProperty(); // 输出:Hello, World!
?>

(12)Final关键字

  • 如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。
  • 举例:
<?php
  class ParentClass {
    final public function someMethod() {
      echo "This method cannot be overridden by child classes.";
    }
  }

    class ChildClass extends ParentClass {
    // 以下代码会导致错误,因为无法覆盖final方法
    /**
    * public function someMethod() {
    *     echo "This won't work!";
    * }
    */
    }
?>

(13)调用父类构造方法

  • 问题:PHP 不会在子类的构造方法中自动的调用父类的构造方法。
  • 解决方案:要执行父类的构造方法,需要在子类的构造方法中调用 parent::__construct()
  • 案例:
<?php
  class ParentClass {
    public function __construct() {
      echo "ParentClass 构造方法被调用";
    }
  }

  class ChildClass extends ParentClass {
    public function __construct() {
      parent::__construct(); // 调用父类构造方法
      echo "ChildClass 构造方法被调用";
    }
  }

  $child = new ChildClass();
?>

16、测验(可选)

到此为止入门相关的知识已经结束了,可以通过此链接检测一下知识的掌握情况:PHP 测验 | 菜鸟教程 (runoob.com)