PHP 面向对象

作者: 给我一炷香的时间 | 来源:发表于2015-02-13 17:50 被阅读709次

    简介


    PHP 对待对象的方式与引用和句柄相同,即每个变量都持有对象的引用,而不是整个对象的拷贝。

    基本概念


    类的定义:

    <?php
    class SimpleClass
    {
        // property declaration
        public $var = 'a default value';
    
        // method declaration
        public function displayVar() {
            echo $this->var;
        }
    }
    ?>
    

    类的实例化:

    <?php
    $instance = new SimpleClass();
    
    // 也可以这样做:
    $className = 'Foo';
    $instance = new $className(); // Foo()
    ?>
    
    <?php
    
    $instance = new SimpleClass();
    
    $assigned   =  $instance;
    $reference  =& $instance;
    
    $instance->var = '$assigned will have this value';
    
    $instance = null; // $instance and $reference become null
    
    var_dump($instance);
    var_dump($reference);
    var_dump($assigned);
    ?>
    

    输出:

    NULL
    NULL
    object(SimpleClass)#1 (1) {
       ["var"]=>
         string(30) "$assigned will have this value"
    }
    

    继承

    PHP不支持多重继承,一个类只能继承一个基类。
    被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。
    当覆盖方法时,参数必须保持一致否则 PHP 将发出 E_STRICT 级别的错误信息。但构造函数例外,构造函数可在被覆盖时使用不同的参数。

    ::class

    自 PHP 5.5 起,关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。

    <?php
    namespace NS {
        class ClassName {
        }
        
        echo ClassName::class;
    }
    ?>
    

    输出:

    NS\ClassName
    

    属性


    属性中的变量可以初始化,但是初始化的值必须是常数,这里的常数是指 PHP 脚本在编译阶段时就可以得到其值,而不依赖于运行时的信息才能求值。

    如果直接使用 var 声明属性,而没有用 public,protected 或 private 之一,PHP 5 会将其视为 public。

    在类的成员方法里面,可以用 ->(对象运算符):$this->property(其中 property 是该属性名)这种方式来访问非静态属性。静态属性则是用 ::(双冒号):self::$property 来访问。

    <?php
    class SimpleClass
    {
       // 错误的属性声明
       public $var1 = 'hello ' . 'world';
       public $var2 = <<<EOD
    hello world
    EOD;
       public $var3 = 1+2;
       public $var4 = self::myStaticMethod();
       public $var5 = $myVar;
    
       // 正确的属性声明
       public $var6 = myConstant;
       public $var7 = array(true, false);
    
       //在 PHP 5.3.0 及之后,下面的声明也正确
       public $var8 = <<<'EOD'
    hello world
    EOD;
    }
    ?>
    

    类常量


    可以把在类中始终保持不变的值定义为常量。在定义和使用常量的时候不需要使用 $ 符号。
    常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。
    接口(interface)中也可以定义常量。

    自 PHP 5.3.0 起,可以用一个变量来动态调用类。但该变量的值不能为关键字(如 self,parent 或 static)。

    <?php
    class MyClass
    {
        const constant = 'constant value';
    
        function showConstant() {
            echo  self::constant . "\n";
        }
    }
    
    echo MyClass::constant . "\n";
    
    $classname = "MyClass";
    echo $classname::constant . "\n"; // 自 5.3.0 起
    
    $class = new MyClass();
    $class->showConstant();
    
    echo $class::constant."\n"; // 自 PHP 5.3.0 起
    ?>
    

    构造函数和析构函数


    构造函数

    void __construct ([ mixed $args [, $... ]] )
    

    如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。

    <?php
    class BaseClass {
       function __construct() {
           print "In BaseClass constructor\n";
       }
    }
    
    class SubClass extends BaseClass {
       function __construct() {
           parent::__construct();
           print "In SubClass constructor\n";
       }
    }
    
    class OtherSubClass extends BaseClass {
        // inherits BaseClass's constructor
    }
    
    // In BaseClass constructor
    $obj = new BaseClass();
    
    // In BaseClass constructor
    // In SubClass constructor
    $obj = new SubClass();
    
    // In BaseClass constructor
    $obj = new OtherSubClass();
    ?>
    

    为了实现向后兼容性,如果 PHP 5 在类中找不到 __construct() 函数并且也没有从父类继承一个的话,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。因此唯一会产生兼容性问题的情况是:类中已有一个名为 __construct() 的方法却被用于其它用途时。
    与其它方法不同,当 __construct() 被与父类 __construct() 具有不同参数的方法覆盖时,PHP 不会产生一个 E_STRICT 错误信息。
    自 PHP 5.3.3 起,在命名空间中,与类名同名的方法不再作为构造函数。这一改变不影响不在命名空间中的类。

    析构函数


    void __destruct ( void )
    

    PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

    <?php
    class MyDestructableClass {
       function __construct() {
           print "In constructor\n";
           $this->name = "MyDestructableClass";
       }
    
       function __destruct() {
           print "Destroying " . $this->name . "\n";
       }
    }
    
    $obj = new MyDestructableClass();
    ?>
    

    和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。
    析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。

    试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。

    访问控制


    对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。被定义为公有的类成员可以在任何地方被访问。被定义为受保护的类成员则可以被其自身以及其子类和父类访问。被定义为私有的类成员则只能被其定义所在的类访问。
    类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。
    类中的方法可以被定义为公有,私有或受保护。如果没有设置这些关键字,则该方法默认为公有。

    其它对象的访问控制

    同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。

    <?php
    class Test
    {
        private $foo;
    
        public function __construct($foo)
        {
            $this->foo = $foo;
        }
    
        private function bar()
        {
            echo 'Accessed the private method.';
        }
    
        public function baz(Test $other)
        {
            // We can change the private property:
            $other->foo = 'hello';
            var_dump($other->foo);
    
            // We can also call the private method:
            $other->bar();
        }
    }
    
    $test = new Test('test');
    
    $test->baz(new Test('other'));
    ?>
    

    输出:

    string(5) "hello"
    Accessed the private method.
    

    对象继承


    除非使用了自动加载,否则一个类必须在使用之前被定义。如果一个类扩展了另一个,则父类必须在子类之前被声明。此规则适用于类继承其它类与接口。

    <?php
    
    class foo
    {
        public function printItem($string) 
        {
            echo 'Foo: ' . $string . PHP_EOL;
        }
        
        public function printPHP()
        {
            echo 'PHP is great.' . PHP_EOL;
        }
    }
    
    class bar extends foo
    {
        public function printItem($string)
        {
            echo 'Bar: ' . $string . PHP_EOL;
        }
    }
    
    $foo = new foo();
    $bar = new bar();
    $foo->printItem('baz'); // Output: 'Foo: baz'
    $foo->printPHP();       // Output: 'PHP is great' 
    $bar->printItem('baz'); // Output: 'Bar: baz'
    $bar->printPHP();       // Output: 'PHP is great'
    
    ?>
    

    范围解析操作符(::)


    范围解析操作符(也可称作 Paamayim Nekudotayim)或者更简单地说是一对冒号,可以用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法。

    在类的外部使用 :: 操作符

    <?php
    class MyClass {
        const CONST_VALUE = 'A constant value';
    }
    
    $classname = 'MyClass';
    echo $classname::CONST_VALUE; // 自 PHP 5.3.0 起
    
    echo MyClass::CONST_VALUE;
    ?>
    

    在类定义内部使用 ::

    selfparentstatic 这三个特殊的关键字是用于在类定义的内部对其属性或方法进行访问的。

    <?php
    class OtherClass extends MyClass
    {
        public static $my_static = 'static var';
    
        public static function doubleColon() {
            echo parent::CONST_VALUE . "\n";
            echo self::$my_static . "\n";
        }
    }
    
    $classname = 'OtherClass';
    echo $classname::doubleColon(); // 自 PHP 5.3.0 起
    
    OtherClass::doubleColon();
    ?>
    

    当一个子类覆盖其父类中的方法时,PHP 不会调用父类中已被覆盖的方法。是否调用父类的方法取决于子类。这种机制也作用于构造函数和析构函数,重载以及魔术方法。

    <?php
    class MyClass
    {
        protected function myFunc() {
            echo "MyClass::myFunc()\n";
        }
    }
    
    class OtherClass extends MyClass
    {
        // 覆盖了父类的定义
        public function myFunc()
        {
            // 但还是可以调用父类中被覆盖的方法
            parent::myFunc();
            echo "OtherClass::myFunc()\n";
        }
    }
    
    $class = new OtherClass();
    $class->myFunc();
    ?>
    

    Static(静态)关键字


    声明类属性或方法为静态,就可以不实例化类而直接访问。静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)。

    由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用。
    静态属性不可以由对象通过 -> 操作符来访问。
    用静态方式调用一个非静态方法会导致一个 E_STRICT 级别的错误。

    抽象类


    PHP 5 支持抽象类和抽象方法。定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
    继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;另外,这些方法的访问控制必须和父类中一样(或者更为宽松)。此外方法的调用方式必须匹配,即类型和所需参数数量必须一致。例如,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。 这也适用于 PHP 5.4 起的构造函数。在 PHP 5.4 之前的构造函数声明可以不一样的。

    <?php
    abstract class AbstractClass
    {
     // 强制要求子类定义这些方法
        abstract protected function getValue();
        abstract protected function prefixValue($prefix);
    
        // 普通方法(非抽象方法)
        public function printOut() {
            print $this->getValue() . "\n";
        }
    }
    
    class ConcreteClass1 extends AbstractClass
    {
        protected function getValue() {
            return "ConcreteClass1";
        }
    
        public function prefixValue($prefix) {
            return "{$prefix}ConcreteClass1";
        }
    }
    
    class ConcreteClass2 extends AbstractClass
    {
        public function getValue() {
            return "ConcreteClass2";
        }
    
        public function prefixValue($prefix) {
            return "{$prefix}ConcreteClass2";
        }
    }
    
    $class1 = new ConcreteClass1;
    $class1->printOut();
    echo $class1->prefixValue('FOO_') ."\n";
    
    $class2 = new ConcreteClass2;
    $class2->printOut();
    echo $class2->prefixValue('FOO_') ."\n";
    ?>
    

    输出:

    ConcreteClass1
    FOO_ConcreteClass1
    ConcreteClass2
    FOO_ConcreteClass2
    
    <?php
    abstract class AbstractClass
    {
        // 我们的抽象方法仅需要定义需要的参数
        abstract protected function prefixName($name);
    
    }
    
    class ConcreteClass extends AbstractClass
    {
    
        // 我们的子类可以定义父类签名中不存在的可选参数
        public function prefixName($name, $separator = ".") {
            if ($name == "Pacman") {
                $prefix = "Mr";
            } elseif ($name == "Pacwoman") {
                $prefix = "Mrs";
            } else {
                $prefix = "";
            }
            return "{$prefix}{$separator} {$name}";
        }
    }
    
    $class = new ConcreteClass;
    echo $class->prefixName("Pacman"), "\n";
    echo $class->prefixName("Pacwoman"), "\n";
    ?>
    

    输出:

    Mr. Pacman
    Mrs. Pacwoman
    

    对象接口


    使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
    接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的。
    接口中定义的所有方法都必须是公有,这是接口的特性。

    实现(implements)

    要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。

    实现多个接口时,接口中的方法不能有重名。
    接口也可以继承,通过使用 extends 操作符。
    类要实现接口,必须使用和接口中所定义的方法完全一致的方式。否则会导致致命错误。

    常量

    接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。

    <?php
    
    // 声明一个'iTemplate'接口
    interface iTemplate
    {
        public function setVariable($name, $var);
        public function getHtml($template);
    }
    
    
    // 实现接口
    // 下面的写法是正确的
    class Template implements iTemplate
    {
        private $vars = array();
      
        public function setVariable($name, $var)
        {
            $this->vars[$name] = $var;
        }
      
        public function getHtml($template)
        {
            foreach($this->vars as $name => $value) {
                $template = str_replace('{' . $name . '}', $value, $template);
            }
     
            return $template;
        }
    }
    
    // 下面的写法是错误的,会报错,因为没有实现 getHtml():
    // Fatal error: Class BadTemplate contains 1 abstract methods
    // and must therefore be declared abstract (iTemplate::getHtml)
    class BadTemplate implements iTemplate
    {
        private $vars = array();
      
        public function setVariable($name, $var)
        {
            $this->vars[$name] = $var;
        }
    }
    ?>
    
    <?php
    interface a
    {
        public function foo();
    }
    
    interface b extends a
    {
        public function baz(Baz $baz);
    }
    
    // 正确写法
    class c implements b
    {
        public function foo()
        {
        }
    
        public function baz(Baz $baz)
        {
        }
    }
    
    // 错误写法会导致一个致命错误
    class d implements b
    {
        public function foo()
        {
        }
    
        public function baz(Foo $foo)
        {
        }
    }
    ?>
    
    <?php
    interface a
    {
        public function foo();
    }
    
    interface b
    {
        public function bar();
    }
    
    interface c extends a, b
    {
        public function baz();
    }
    
    class d implements c
    {
        public function foo()
        {
        }
    
        public function bar()
        {
        }
    
        public function baz()
        {
        }
    }
    ?>
    
    <?php
    interface a
    {
        const b = 'Interface constant';
    }
    
    // 输出接口常量
    echo a::b;
    
    // 错误写法,因为常量不能被覆盖。接口常量的概念和类常量是一样的。
    class b implements a
    {
        const b = 'Class constant';
    }
    ?>
    

    重载


    PHP所提供的"重载"(overloading)是指动态地"创建"类属性和方法。我们是通过魔术方法(magic methods)来实现的。
    所有的重载方法都必须被声明为 public。

    这些魔术方法的参数都不能通过引用传递。

    PHP中的"重载"与其它绝大多数面向对象语言不同。传统的"重载"是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

    遍历对象


    PHP 5 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用 foreach 语句。默认情况下,所有可见属性都将被用于遍历。

    相关文章

      网友评论

        本文标题:PHP 面向对象

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