PHP的类与对象的概念还是比较复杂的,其中有一些不容易分清或容易使人产生混淆的理解与用法,稍不留神,就会产生误用,以至于在以后的程序中留下bug,下面让我们来进一步深入剖析PHP的类与对象用法:

 

这是关于成员函数使用$this的多态性例子:

<?php
class X {
    function foo() {
        echo 'X::foo()';
    }

    function bar() {
        $this->foo();
    }
}

class Y extends X {
    function foo() {
        echo 'Y::foo()';
    }
}

$x = new Y();
$x->bar();
?>

下面是通过将 self 用于成员函数来抑制多态行为的示例

<?php
class X {
    function foo() {
        echo 'X::foo()';
    }

    function bar() {
        self::foo();
    }
}

class Y extends X {
    function foo() {
        echo 'Y::foo()';
    }
}

$x = new Y();
$x->bar();
?>

这个想法是$this->foo()调用foo()当前对象的确切类型的成员函数。如果对象是type X,它就这样调用X::foo()。如果对象是type Y,则调用Y::foo()。但是对于self :: foo(),X::foo()总是被调用。

 

关键字 self 并不仅仅指 "当前类", 至少不是以限制您为静态成员的方式。。在非静态成员的上下文中,self还提供了绕过当前对象的vtable(请参阅vtable上的wiki)的方法。就像你可以parent::methodName()用来调用一个函数的父版本一样,这样你就可以调用self::methodName()当前类调用一个方法来实现。

class Person {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    public function getTitle() {
        return $this->getName()." the person";
    }

    public function sayHello() {
        echo "Hello, I'm ".$this->getTitle()."<br/>";
    }

    public function sayGoodbye() {
        echo "Goodbye from ".self::getTitle()."<br/>";
    }
}

class Geek extends Person {
    public function __construct($name) {
        parent::__construct($name);
    }

    public function getTitle() {
        return $this->getName()." the geek";
    }
}

$geekObj = new Geek("Ludwig");
$geekObj->sayHello();
$geekObj->sayGoodbye();

这将输出:

Hello, I'm Ludwig the geek
Goodbye from Ludwig the person

sayHello()使用$this指针,因此调用vtable进行调用Geek::getTitle()。 sayGoodbye()使用self::getTitle(),所以不使用vtable,Person::getTitle()被调用。在这两种情况下,我们都在处理实例化对象的方法,并有权访问被调用函数中的 $this 指针。

 

不要使用self::,使用static::

self::的另一个方面值得一提。令人讨厌的是self::指的是定义点的范围, 而不是在执行点。考虑这个简单的类有两种方法:

class Person
{

    public static function status()
    {
        self::getStatus();
    }

    protected static function getStatus()
    {
        echo "Person is alive";
    }

}

如果我们调用Person::status()我们会看到“Person is alive”。现在考虑当我们创建一个继承类时会发生什么:

class Deceased extends Person
{

    protected static function getStatus()
    {
        echo "Person is deceased";
    }

}

调用Deceased::status()我们希望看到“Person is deceased”但是我们看到的是“Person is alive”,因为当我们调用 self::getStatus()时,范围包含原始的方法定义。。

PHP 5.3 有一个解决方案。static::解析运算符实现 "后期静态绑定", 这是一种花哨的说法, 它绑定到调用的类的范围。将status()改为 static::getStatus(),结果是您所期望的。

请参阅PHP文档

所以回答问题不是问...

$this-> 是指当前对象 (类的实例), 而static::指的是类。

 

让我们先谈谈一个和一个对象是什么。

从概念上讲类和对象

那么,什么?很多人将它定义为对象的蓝图模板。实际上,您可以在此处阅读有关PHP中的类的更多信息。在某种程度上,这就是它的真实含义。我们来看一个类:

class Person {
    public $name = 'my name';
    public function sayHello() {
        echo "Hello";
    }
}

正如您所知道的, 该类上有一个名为 $name 的属性和一个名为 sayHello () 的方法 (函数)。

请注意, 类是一个静态结构, 这一点非常重要。这意味着, 一旦定义, 类 Person 在您查看它的任何地方都是相同的。

另一方面,对象是所谓的类的实例。这意味着我们采用类的“蓝图”,并使用它来制作一个动态副本。此副本现在与存储在其中的变量专门绑定在一起。因此,对实例的任何更改都是该实例的本地更改。

$bob = new Person;
$adam = new Person;
$bob->name = 'Bob';
echo $adam->name; // "my name"

我们使用"new"运算符创建类的新实例。

因此,我们说Class是一个全局结构,Object是一个本地结构。不要担心这个有趣的->语法,我们会一点点进入这个问题。

我们应该讨论的另一件事是, 我们可以检查实例是否是特定类的实例:  $bob 是Person的实列将会返回一个布尔值,如果$bob实例是用Person类实现的,或者是Person的子类。

定义状态

那么让我们深入了解一个类实际包含的内容。一个类包含5种类型的“东西”:

  1. 属性 - 将这些视为每个实例将包含的变量。
    class Foo {
        public $bar = 1;
    }
    
  2. 静态属性 - 将这些视为在类级别共享的变量。这意味着它们永远不会被每个实例复制。
    class Foo {
        public static $bar = 1;
    }
    
  3. 方法 - 这些是每个实例将包含的函数(并对实例进行操作)。
    class Foo {
        public function bar() {}
    }
    
  4. 静态方法 - 这些是在整个类中共享的函数。它们不在实例上操作,而是仅在静态属性上操作。
    class Foo {
        public static function bar() {}
    }
    
  5. 常量 - 类解析常量。这里没有更深入,添加只是为了完整性:
    class Foo {
        const BAR = 1;
    }
    

所以基本上,我们在类和对象容器上存储信息,这些信息用static标识信息是否是共享的(因而是静态的)或不是共享的(因此是动态的)。

状态和方法

在方法内部, 对象的实例由 $this 变量表示。该对象的当前状态就在那里, 并且改变 (更改) 任何属性都将导致对该实例 (但不是其他实例) 的更改。。

如果静态地调用方法,$this变量没有定义。这是因为没有与静态调用相关联的实例。

这里有趣的是如何进行静态调用。那么让我们谈谈我们如何访问状态:

访问状态

所以现在我们已经存储了那个状态,我们需要访问它。这能有点棘手(或方式超过一点),因此, 让我们将其拆分为两个视点:从一个实例/类外(例如从正常函数调用或从全局范围),以及实例/类内部 (从对象上的方法内)。

来自实例/类的外部

从实例/类的外部,我们的规则非常简单且可预测。我们有两个运算符,每个运算符都会立即告诉我们是否正在处理实例或静态类:

  • ->- 对象操作符 - 当我们访问实例时总是使用它。
    $bob = new Person;
    echo $bob->name;
    

    重要的是要注意调用Person->foo没有意义(因为Person是一个类,而不是一个实例)。因此,这是一个解析错误。

  • ::- 范围解析操作符 - 始终用于访问Class静态属性或方法。
    echo Foo::bar()

    另外,我们可以用相同的方式在对象上调用静态方法:

    echo $foo::bar()

    需要注意的是, 当我们从外部执行此操作时, 对象的实例在bar()方法中隐藏着。这意味着它与以下运行完全相同:

    $class = get_class($foo);
    $class::bar();
    

因此, 静态调用中没有定义 $this。

从实例/类的内部

事情在这里有所改变。使用相同的运算符,但它们的含义变得非常模糊。

对象操作符-> 仍然用于调用对象的实例状态。

class Foo {
    public $a = 1;
    public function bar() {
        return $this->a;
    }
}

使用$foo->bar()方式调用$foo(Foo的实例)的bar()方法,将导致实例的版本为$a

所以这就是我们的期望。

::操作符的意义有所改变。它取决于对当前函数的调用的上下文:

  • 在静态上下文中在静态上下文中, 使用:: 进行的任何调用也将是静态的。让我们看一个例子:
    class Foo {
        public function bar() {
            return Foo::baz();
        }
        public function baz() {
            return isset($this);
        }
    }
    

    调用 Foo::bar()将静态调用 baz()方法, 因此$this将不会被填充。值得注意的是, 在 PHP (5.3+) 的最新版本中, 这将触发 E_STRICT 错误, 因为我们静态调用非静态方法。。

  • 在实例上下文中另一方面, 在实例上下文中, 使用::调用依赖于调用的接收方 (我们正在调用的方法) 。如果该方法被定义为静态, 则它将使用静态调用。如果不是, 它将转发实例信息。因此,查看上面的代码,调用$foo->bar()将返回true,因为“静态”调用发生在实例上下文中。

合理吗?不这么认为。这令人困惑。

快捷方式关键词

因为使用类名将所有内容捆绑在一起相当脏,所以PHP提供了3个基本的“快捷方式”关键字,使范围解析更容易。

  • self - 这是指当前的类名。 所以在Foo类(它上的任何方法中)中self::baz()和Foo::baz()相同 。
  • parent - 这是指当前类的父类。
  • static - 这是指调用的类。由于继承, 子类可以覆盖方法和静态属性。因此, 使用static而不是类名调用它们可以使我们解析调用的来源, 而不是当前级别。。

例子

理解这一点的最简单方法是开始查看一些示例。我们选一个类:

class Person {
    public static $number = 0;
    public $id = 0;
    public function __construct() {
        self::$number++;
        $this->id = self::$number;
    }
    public $name = "";
    public function getName() {
        return $this->name;
    }
    public function getId() {
        return $this->id;
    }
}

class Child extends Person {
    public $age = 0;
    public function __construct($age) {
        $this->age = $age;
        parent::__construct();
    }
    public function getName() {
        return 'child: ' . parent::getName();
    }
}

现在,我们也在看这里的继承问题。暂时忽略这是一个糟糕的对象模型,但让我们来看看当我们运行这个时会发生什么:

$bob = new Person;
$bob->name = "Bob";
$adam = new Person;
$adam->name = "Adam";
$billy = new Child;
$billy->name = "Billy";
var_dump($bob->getId()); // 1
var_dump($adam->getId()); // 2
var_dump($billy->getId()); // 3

因此, ID 计数器在实例和子实例之间共享 (因为我们使用self来访问它。如果我们使用static, 我们可以在子类中覆盖它)。

var_dump($bob->getName()); // Bob
var_dump($adam->getName()); // Adam
var_dump($billy->getName()); // child: Billy

请注意, 我们每次都在执行 Person:: getName () 实例方法。但我们使用的是parent::getName()在其中一个情况下 (child例子) 执行此操作。这就是这种方法强大的原因。

警告语#1

请注意,调用上下文决定了是否使用了实例。因此:

class Foo {
    public function isFoo() {
        return $this instanceof Foo;
    }
}

并非总是如此。

class Bar {
    public function doSomething() {
        return Foo::isFoo();
    }
}
$b = new Bar;
var_dump($b->doSomething()); // bool(false)

现在这真的很奇怪。我们正在调用另一个类,但是传递给Foo::isFoo()方法的$this是$bar的实例。

这可能会导致各种错误和概念性的WTF-ery。因此,我强烈建议避免在实例内的方法用::操作符,除了(static, self, and parent)关键字。

警告语#2

请注意, 静态方法和属性由每个人共享。这使得它们基本上成为了全局变量。有着与全局变量相同的一些问题。所以我真的会犹豫是否要将信息存储在静态方法/属性中,除非你对它真正的全局性感到满意。

警告语#3

通常, 您需要使用static而不是self来使用所谓的 "后期静态绑定"。但要注意的是, 它们是不一样的, 所以说 "总是用static代替self真的是短视的。相反,停下来思考你想使用的调用,并思考你是否希望子类能够覆盖那个静态解析调用

更多

太糟糕了,回去看看吧。它可能太长了,但它很长,因为这是一个复杂的话题

更多#2

好吧, 简而言之, self 用于在类中引用当前类名,其中 $this 指的是当前对象实例。请注意,这self是一个复制/粘贴快捷方式。您可以安全地将其替换为您的类名, 它将会工作正常。 但 $this 是一个动态变量, 无法提前确定 (甚至可能不是你的类)。

更多#3

如果使用了对象操作符(->),那么你总是知道你正在处理一个实例。如果使用范围解析运算符(::),您需要有关上下文的更多信息(我们是否真的在对象上下文中?我们是否在对象之外?等等)。

更多#4

self (不是 $self) 指的是类的类型, 其中 $this 指的是类的当前实例。self用于静态成员函数, 以允许您访问静态成员变量。$this用于非静态成员函数,并且是对调用成员函数的类的实例的引用。

因为this是一个对象,你可以这样使用它:$this->member
因为self它不是一个对象,它基本上是一个自动引用当前类的类型,你可以这样使用它:self::member

因此,只有在$this不可用时,或者当您不希望允许后代类覆盖当前方法时,才应使用self 。

 

另一个说明:

$this-> 用于引用类的变量(成员变量)或方法的特定实例。

Example: 
$derek = new Person();

$ derek现在是Person的特定实例。每个Person都有first_name和last_name,但$ derek有一个特定的first_name和last_name(Derek Martin)。在$ derek实例中,我们可以将它们称为$ this-> first_name和$ this-> last_name

ClassName ::用于引用该类型的类及其静态变量,静态方法。如果有帮助,您可以在心理上将“静态”替换为“共享”。因为它们是共享的,所以它们不能引用$ this,它引用特定的实例(不共享)。静态变量(如 static $ db_connection)可以在一种对象的所有实例之间共享。例如,所有数据库对象共享一个连接(static $connection)。

静态变量示例: 假设我们有一个包含单个成员变量的数据库类:static $ num_connections; 现在,把它放在构造函数中:

function __construct()
{
    if(!isset $num_connections || $num_connections==null)
    {
        $num_connections=0;
    }
    else
    {
        $num_connections++;
    }
}

正如对象具有构造函数一样,它们也具有析构函数,这些析构函数在对象死亡或未设置时执行:

function __destruct()
{
    $num_connections--;
}

每次我们创建一个新实例时,它都会将我们的连接计数器增加一个。每次我们销毁或停止使用实例时,它都会将连接计数器减少一个。通过这种方式,我们可以监视我们使用的数据库对象的实例数:

echo DB::num_connections;

因为$ num_connections是静态的(共享),它将反映活动数据库对象的总数。您可能已经看到过这种技术用于在数据库类的所有实例之间共享数据库连接。这样做是因为创建数据库连接需要很长时间,因此最好只创建一个并共享它(这称为单例模式)。

静态方法(如public static View :: format_phone_number($ digits))可以在没有首先实例化其中一个对象的情况下使用(即它们不在内部指向$this)。

静态方法示例:

public static function prettyName($first_name, $last_name)
{
    echo ucfirst($first_name).' '.ucfirst($last_name);
}

echo Person::prettyName($derek->first_name, $derek->last_name);

如您所见,public static function prettyName对该对象一无所知。它只是处理你传入的参数,就像一个不属于对象的普通函数。 那么, 如果我们能把它不作为对象的一部分, 为什么还要费心呢?

  1. 首先,将函数附加到对象可以帮助您保持组织有序,因此您知道在哪里可以找到它们。
  2. 其次,它可以防止命名冲突。在一个大项目中,您可能有两个开发人员创建getName()函数。如果一个创建一个ClassName1 :: getName(),另一个创建ClassName2 :: getName(),那就没问题了。没有冲突。静态方法!

SELF :: 如果您在具有要引用的静态方法的对象之外进行编码,则必须使用对象的名称View :: format_phone_number($ phone_number)调用它; 如果您在编码里面有你要引用静态方法的对象,就可以使用对象的名称View:: format_phone_number($ pn),还可以使用self:: format_phone_number($ pn)的快捷方式。

静态变量也是如此: 示例: View :: templates_path与self :: templates_path

在DB类中,如果我们引用某个其他对象的静态方法,我们将使用该对象的名称: 示例: Session :: getUsersOnline();

但是如果DB类想要引用它自己的静态变量,它就会用self: 示例: self :: connection;

希望有助于清理事情:)

进一步说明:

  • self 是指当前的类
  • self 可用于调用静态函数和引用静态成员变量
  • self 可以在静态函数中使用
  • self 也可以通过绕过vtable来关闭多态行为
  • $this 指当前的对象
  • $this 可用于调用静态函数
  • $this不应该用来调用静态成员变量。请用self代替
  • $this 不能在静态函数内部使用

 

此外,$this::还没有讨论过。

仅供参考,从PHP 5.3开始,当处理实例化对象以获取当前作用域值时,与使用static::相反,也可以使用$this::类似的方法。

 class Foo
{
    const NAME = 'Foo';

    //Always Foo::NAME (Foo) due to self
    protected static $staticName = self::NAME;

    public function __construct()
    {
        echo $this::NAME;
    }

    public function getStaticName()
    {
       echo $this::$staticName;
    }
}

class Bar extends Foo
{
    const NAME = 'FooBar';

    /**
     * override getStaticName to output Bar::NAME
     */
    public function getStaticName()
    {
        $this::$staticName = $this::NAME;
        parent::getStaticName();
    }
}

$foo = new Foo; //outputs Foo
$bar = new Bar; //outputs FooBar
$foo->getStaticName(); //outputs Foo
$bar->getStaticName(); //outputs FooBar
$foo->getStaticName(); //outputs FooBar

使用上面的代码不是常见或推荐的做法,而只是为了说明它的用法,并且更像是“你知道吗?” 的问题。

它也代表了$object::CONSTANT例如echo $foo::NAME;与之相反的用法$this::NAME;

参考 https://stackoverflow.com/questions/151969/when-to-use-self-over-this#

原创文章,转载请注明:来自Lenix的博客 ,地址 http://blog.p2hp.com/archives/5375

 

最后更新于 2019年4月13日

关于PHP类与对象的进一步深入解释
标签: