基本概念

class

每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号,里面包含有类的属性与方法的定义。

类名可以是任何非 PHP 保留字 的合法标签。一个合法类名以字母或下划线开头,后面跟着若干字母,数字或下划线。以正则表达式表示为: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$

一个类可以包含有属于自己的 常量变量(称为"属性")以及函数(称为"方法")。

Example #1 简单的类定义

<?php
class SimpleClass
{
    
// 声明属性
    
public $var 'a default value';

    
// 声明方法
    
public function displayVar() {
        echo 
$this->var;
    }
}
?>

当一个方法在类定义内部被调用时,有一个可用的伪变量 $this$this 是一个到当前对象的引用。

Warning

以静态方式去调用一个非静态方法,将会抛出一个 Error。 在 PHP 8.0.0 之前版本中,将会产生一个废弃通知,同时 $this 将会被声明为未定义。

Example #2 使用 $this 伪变量的示例

<?php
class A
{
    function 
foo()
    {
        if (isset(
$this)) {
            echo 
'$this is defined (';
            echo 
get_class($this);
            echo 
")\n";
        } else {
            echo 
"\$this is not defined.\n";
        }
    }
}

class 
B
{
    function 
bar()
    {
        
A::foo();
    }
}

$a = new A();
$a->foo();

A::foo();

$b = new B();
$b->bar();

B::bar();
?>

以上例程在 PHP 7 中的输出:

$this is defined (A)

Deprecated: Non-static method A::foo() should not be called statically in %s  on line 27
$this is not defined.

Deprecated: Non-static method A::foo() should not be called statically in %s  on line 20
$this is not defined.

Deprecated: Non-static method B::bar() should not be called statically in %s  on line 32

Deprecated: Non-static method A::foo() should not be called statically in %s  on line 20
$this is not defined.

以上例程在 PHP 8 中的输出:

$this is defined (A)

Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically in %s :27
Stack trace:
#0 {main}
  thrown in %s  on line 27

new

要创建一个类的实例,必须使用 new 关键字。当创建新对象时该对象总是被赋值,除非该对象定义了 构造函数 并且在出错时抛出了一个 异常。类应在被实例化之前定义(某些情况下则必须这样)。

如果在 new 之后跟着的是一个包含有类名的字符串 string,则该类的一个实例被创建。如果该类属于一个命名空间,则必须使用其完整名称。

Note:

如果没有参数要传递给类的构造函数,类名后的括号则可以省略掉。

Example #3 创建实例

<?php
$instance 
= new SimpleClass();

// 也可以这样做:
$className 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>

在类定义内部,可以用 new selfnew parent 创建新对象。

当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。可以用 克隆 给一个已创建的对象建立一个新实例。

Example #4 对象赋值

<?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"
}

有几种方法可以创建一个对象的实例。

Example #5 创建新对象

<?php
class Test
{
    static public function 
getNew()
    {
        return new static;
    }
}

class 
Child extends Test
{}

$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);

$obj3 Test::getNew();
var_dump($obj3 instanceof Test);

$obj4 Child::getNew();
var_dump($obj4 instanceof Child);
?>

以上例程会输出:

bool(true)
bool(true)
bool(true)

可以通过一个表达式来访问新创建对象的成员:

Example #6 访问新创建对象的成员

<?php
echo (new DateTime())->format('Y');
?>

以上例程的输出类似于:

2016

Note: 在 PHP 7.1 之前,如果类没有定义构造函数,则不对参数进行执行。

属性和方法

类的属性和方法存在于不同的"命名空间"中,这意味着同一个类的属性和方法可以使用同样的名字。 在类中访问属性和调用方法使用同样的操作符,具体是访问一个属性还是调用一个方法,取决于你的上下文,即用法是变量访问还是函数调用。

Example #7 访问类属性 vs. 调用类方法

<?php
class Foo
{
    public 
$bar 'property';

    public function 
bar() {
        return 
'method';
    }
}

$obj = new Foo();
echo 
$obj->barPHP_EOL$obj->bar(), PHP_EOL;

以上例程会输出:

property
method

这意味着,如果你的类属性被分配给一个 匿名函数 你将无法直接调用它。因为访问类属性的优先级要更高,在此场景下需要用括号包裹起来调用。

Example #8 类属性被赋值为匿名函数时的调用示例

<?php
class Foo
{
    public 
$bar;

    public function 
__construct() {
        
$this->bar = function() {
            return 
42;
        };
    }
}

$obj = new Foo();

echo (
$obj->bar)(), PHP_EOL;

以上例程会输出:

42

extends

一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。PHP 不支持多重继承,一个类只能继承一个基类。

被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。

Example #9 简单的类继承

<?php
class ExtendClass extends SimpleClass
{
    
// 同样名称的方法,将会覆盖父类的方法
    
function displayVar()
    {
        echo 
"Extending class\n";
        
parent::displayVar();
    }
}

$extended = new ExtendClass();
$extended->displayVar();
?>

以上例程会输出:

Extending class
a default value

签名兼容性规则

当覆盖(override)方法时,签名必须兼容父类方法。 否则会导致 Fatal 错误(PHP 8.0.0 之前是 E_WARNING 级错误)。 兼容签名是指:遵守协变与逆变规则; 强制参数可以改为可选参数;新参数为可选参数。 这就是著名的里氏替换原则(Liskov Substitution Principle),简称 LSP。 不过构造器和 私有(private)方法不需要遵循签名兼容规则, 哪怕签名不匹配也不会导致 Fatal 错误。

Example #10 兼容的子类方法

<?php

class Base
{
    public function 
foo(int $a) {
        echo 
"Valid\n";
    }
}

class 
Extend1 extends Base
{
    function 
foo(int $a 5)
    {
        
parent::foo($a);
    }
}

class 
Extend2 extends Base
{
    function 
foo(int $a$b 5)
    {
        
parent::foo($a);
    }
}

$extended1 = new Extend1();
$extended1->foo();
$extended2 = new Extend2();
$extended2->foo(1);

以上例程会输出:

Valid
Valid

下面演示子类与父类方法不兼容的例子:通过移除参数、修改可选参数为必填参数。

Example #11 子类方法移除参数后,导致 Fatal 错误

<?php

class Base
{
    public function 
foo(int $a 5) {
        echo 
"Valid\n";
    }
}

class 
Extend extends Base
{
    function 
foo()
    {
        
parent::foo(1);
    }
}

Output of the above example in PHP 8 is similar to:

Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5) in /in/evtlq on line 13

Example #12 子类方法把可选参数改成强制参数,导致 Fatal 错误

<?php

class Base
{
    public function 
foo(int $a 5) {
        echo 
"Valid\n";
    }
}

class 
Extend extends Base
{
    function 
foo(int $a)
    {
        
parent::foo($a);
    }
}

Output of the above example in PHP 8 is similar to:

Fatal error: Declaration of Extend::foo(int $a) must be compatible with Base::foo(int $a = 5) in /in/qJXVC on line 13
Warning

重命名子类方法的参数名称也是签名兼容的。 然而我们不建议这样做,因为使用命名参数时, 这种做法会导致运行时的 Error

Example #13 在子类中重命一个命名参数,导致 Error

<?php

class {
    public function 
test($foo$bar) {}
}

class 
extends {
    public function 
test($a$b) {}
}

$obj = new B;

// 按 A::test() 的签名约定传入参数
$obj->test(foo"foo"bar"bar"); // ERROR!

以上例程的输出类似于:

Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14
Stack trace:
#0 {main}
  thrown in /in/XaaeN on line 14

::class

关键词 class 也可用于类名的解析。使用 ClassName::class 可以获取包含类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。

Example #14 类名的解析

<?php
namespace NS {
    class 
ClassName {
    }

    echo 
ClassName::class;
}
?>

以上例程会输出:

NS\ClassName

Note:

使用 ::class 解析类名操作会在底层编译时进行。这意味着在执行该操作时,类还没有被加载。 因此,即使要调用的类不存在,类名也会被展示。在此种场景下,并不会发生错误。

Example #15 解析不存在的类名

<?php
print Does\Not\Exist::class;
?>

以上例程会输出:

Does\Not\Exist

自 PHP 8.0.0 起,::class 关键字也可以对象上使用。 与上述情况不同,此时解析将会在运行时进行。此操作的运行结果和 get_class() 函数一致。

Example #16 类名解析

<?php
namespace NS {
    class 
ClassName {
    }
}
$c = new ClassName();
print 
$c::class;
?>

以上例程会输出:

NS\ClassName

Nullsafe 方法和属性

自 PHP 8.0.0 起,类属性和方法可以通过 "nullsafe" 操作符访问: ?->。 除了一处不同,nullsafe 操作符和以上原来的属性、方法访问是一致的: 对象引用解析(dereference)为 null 时不抛出异常,而是返回 null。 并且如果是链式调用中的一部分,剩余链条会直接跳过。

此操作的结果,类似于在每次访问前使用 is_null() 函数判断方法和属性是否存在,但更加简洁。

Example #17 Nullsafe 操作符

<?php

// 自 PHP 8.0.0 起可用
$result $repository?->getUser(5)?->name;

// 上边那行代码等价于以下代码
if (is_null($repository)) {
    
$result null;
} else {
    
$user $repository->getUser(5);
    if (
is_null($user)) {
        
$result null;
    } else {
        
$result $user->name;
    }
}
?>

Note:

仅当 null 被认为是属性或方法返回的有效和预期的可能值时,才推荐使用 nullsafe 操作符。如果业务中需要明确指示错误,抛出异常会是更好的处理方式。

User Contributed Notes

thisleenoble at DOPEOPLESTILLNOSPAM dot me dot com 23-Nov-2020 09:50
Instantiating an object with a string variable defaults to non-namespaced scope. Given two classes in the same namespace.

<?php
namespace foo;

class
bar {
  public function
createSubclass(string $type) {
     return new
$type();
  }
?>
<?php
namespace foo;

class
baz {

}
?>
<?php
$barObj
= new bar();
$barObj->createSubclass('baz');

// result: Uncaught Error: Class 'baz' not found
?>

Change bar class to:
<?php
namespace foo;

class
bar {
  public function
createSubclass(string $type) {
    
type = '\\'.__NAMESPACE__.'\\'.type;
     return new
$type();
  }
?>
Anonymous 15-Jun-2018 05:05
At first I was also confused by the assignment vs referencing but here's how I was finally able to get my head around it. This is another example which is somewhat similar to one of the comments but can be helpful to those who did not understand the first example. Imagine object instances as rooms where you can store and manipulate your properties and functions.  The variable that contains the object simply holds 'a key' to this room and thus access to the object. When you assign this variable to another new variable, what you are doing is you're making a copy of the key and giving it to this new variable. That means these two variable now have access to the same 'room' (object) and can thus get in and manipulate the values. However, when you create a reference, what you doing is you're making the variables SHARE the same key. They both have access to the room. If one of the variable is given a new key, then the key that they are sharing is replaced and they now share a new different key. This does not affect the other variable with a copy of the old key...that variable still has access to the first room
Hayley Watson 21-Jan-2018 10:52
Class names are case-insensitive:
<?php
class Foo{}
class
foo{} //Fatal error.
?>

Any casing can be used to refer to the class
<?php
class bAr{}
$t = new Bar();
$u = new bar();
echo (
$t instanceof $u) ? "true" : "false"; // "true"
echo ($t instanceof BAR) ? "true" : "false"; // "true"
echo is_a($u, 'baR') ? "true" : "false"; // "true"
?>

But the case used when the class was defined is preserved as "canonical":
<?php
echo get_class($t); // "bAr"
?>

And, as always, "case-insensitivity" only applies to ASCII.
<?php
class пасха{}
class
Пасха{} // valid
$p = new ПАСХА(); // Uncaught warning.
?>
kStarbe at gmail point com 30-Mar-2017 07:36
You start using :: in second example although the static concept has not been explained. This is not easy to discover when you are starting from the basics.
Anonymous 04-Feb-2017 09:59
Understanding what does $this exactly do:

<?php
class toop {
    public
$i = "1234";
    public function
setv($a) {
       
$this->i = $a;
    }
    public function
returnthis() {
        return
$this;
    }
}
$ob = new toop();
$ob1 = new toop();
$obthis = $ob->returnthis();
echo
$ob->i."\n";
$ob->setv("\$ob set");
echo
$ob->i."\n";
$obthis->setv("\$obthis set");
echo
$ob->i."\n";
$ob1->setv('$ob1 set');
echo
$ob->i."\n";
?>

This will output:

1234
$ob set
$obthis set
$obthis set
Doug 07-Oct-2010 07:29
What is the difference between  $this  and  self ?

Inside a class definition, $this refers to the current object, while  self  refers to the current class.

It is necessary to refer to a class element using  self ,
and refer to an object element using  $this .
Note also how an object variable must be preceded by a keyword in its definition.

The following example illustrates a few cases:

<?php
class Classy {

const      
STAT = 'S' ; // no dollar sign for constants (they are always static)
static     $stat = 'Static' ;
public    
$publ = 'Public' ;
private   
$priv = 'Private' ;
protected 
$prot = 'Protected' ;

function
__construct( ){  }

public function
showMe( ){
    print
'<br> self::STAT: '  self::STAT ; // refer to a (static) constant like this
   
print '<br> self::$stat: ' . self::$stat ; // static variable
   
print '<br>$this->stat: '  . $this->stat ; // legal, but not what you might think: empty result
   
print '<br>$this->publ: '  . $this->publ ; // refer to an object variable like this
   
print '<br>' ;
}
}
$me = new Classy( ) ;
$me->showMe( ) ;

/* Produces this output:
self::STAT: S
self::$stat: Static
$this->stat:
$this->publ: Public
*/
?>
moty66 at gmail dot com 16-Aug-2009 07:59
I hope that this will help to understand how to work with static variables inside a class

<?php

class a {

    public static
$foo = 'I am foo';
    public
$bar = 'I am bar';
   
    public static function
getFoo() { echo self::$foo;    }
    public static function
setFoo() { self::$foo = 'I am a new foo'; }
    public function
getBar() { echo $this->bar;    }           
}

$ob = new a();
a::getFoo();     // output: I am foo   
$ob->getFoo();    // output: I am foo
//a::getBar();     // fatal error: using $this not in object context
$ob->getBar();    // output: I am bar
                // If you keep $bar non static this will work
                // but if bar was static, then var_dump($this->bar) will output null

// unset($ob);
a::setFoo();    // The same effect as if you called $ob->setFoo(); because $foo is static
$ob = new a();     // This will have no effects on $foo
$ob->getFoo();    // output: I am a new foo

?>

Regards
Motaz Abuthiab
Notes on stdClass 09-Jul-2009 03:26
stdClass is the default PHP object. stdClass has no properties, methods or parent. It does not support magic methods, and implements no interfaces.

When you cast a scalar or array as Object, you get an instance of stdClass. You can use stdClass whenever you need a generic object instance.
<?php
// ways of creating stdClass instances
$x = new stdClass;
$y = (object) null;        // same as above
$z = (object) 'a';         // creates property 'scalar' = 'a'
$a = (object) array('property1' => 1, 'property2' => 'b');
?>

stdClass is NOT a base class! PHP classes do not automatically inherit from any class. All classes are standalone, unless they explicitly extend another class. PHP differs from many object-oriented languages in this respect.
<?php
// CTest does not derive from stdClass
class CTest {
    public
$property1;
}
$t = new CTest;
var_dump($t instanceof stdClass);            // false
var_dump(is_subclass_of($t, 'stdClass'));    // false
echo get_class($t) . "\n";                   // 'CTest'
echo get_parent_class($t) . "\n";            // false (no parent)
?>

You cannot define a class named 'stdClass' in your code. That name is already used by the system. You can define a class named 'Object'.

You could define a class that extends stdClass, but you would get no benefit, as stdClass does nothing.

(tested on PHP 5.2.8)
Jeffrey 08-Oct-2008 03:49
A PHP Class can be used for several things, but at the most basic level, you'll use classes to "organize and deal with like-minded data". Here's what I mean by "organizing like-minded data". First, start with unorganized data.

<?php
$customer_name
;
$item_name;
$item_price;
$customer_address;
$item_qty;
$item_total;
?>

Now to organize the data into PHP classes:

<?php
class Customer {
 
$name;          // same as $customer_name
 
$address;       // same as $customer_address
}

class
Item {
 
$name;          // same as $item_name
 
$price;         // same as $item_price
 
$qty;           // same as $item_qty
 
$total;         // same as $item_total
}
?>

Now here's what I mean by "dealing" with the data. Note: The data is already organized, so that in itself makes writing new functions extremely easy.

<?php
class Customer {
  public
$name, $address;                   // the data for this class...

  // function to deal with user-input / validation
  // function to build string for output
  // function to write -> database
  // function to  read <- database
  // etc, etc
}

class
Item {
  public
$name, $price, $qty, $total;        // the data for this class...

  // function to calculate total
  // function to format numbers
  // function to deal with user-input / validation
  // function to build string for output
  // function to write -> database
  // function to  read <- database
  // etc, etc
}
?>

Imagination that each function you write only calls the bits of data in that class. Some functions may access all the data, while other functions may only access one piece of data. If each function revolves around the data inside, then you have created a good class.
wbcarts at juno dot com 20-Aug-2008 06:11
CLASSES and OBJECTS that represent the "Ideal World"

Wouldn't it be great to get the lawn mowed by saying $son->mowLawn()? Assuming the function mowLawn() is defined, and you have a son that doesn't throw errors, the lawn will be mowed.

In the following example; let objects of type Line3D measure their own length in 3-dimensional space. Why should I or PHP have to provide another method from outside this class to calculate length, when the class itself holds all the neccessary data and has the education to make the calculation for itself?

<?php

/*
 * Point3D.php
 *
 * Represents one locaton or position in 3-dimensional space
 * using an (x, y, z) coordinate system.
 */
class Point3D
{
    public
$x;
    public
$y;
    public
$z;                  // the x coordinate of this Point.

    /*
     * use the x and y variables inherited from Point.php.
     */
   
public function __construct($xCoord=0, $yCoord=0, $zCoord=0)
    {
       
$this->x = $xCoord;
   
$this->y = $yCoord;
       
$this->z = $zCoord;
    }

   
/*
     * the (String) representation of this Point as "Point3D(x, y, z)".
     */
   
public function __toString()
    {
        return
'Point3D(x=' . $this->x . ', y=' . $this->y . ', z=' . $this->z . ')';
    }
}

/*
 * Line3D.php
 *
 * Represents one Line in 3-dimensional space using two Point3D objects.
 */
class Line3D
{
   
$start;
   
$end;

    public function
__construct($xCoord1=0, $yCoord1=0, $zCoord1=0, $xCoord2=1, $yCoord2=1, $zCoord2=1)
    {
       
$this->start = new Point3D($xCoord1, $yCoord1, $zCoord1);
       
$this->end = new Point3D($xCoord2, $yCoord2, $zCoord2);
    }

   
/*
     * calculate the length of this Line in 3-dimensional space.
     */
   
public function getLength()
    {
        return
sqrt(
           
pow($this->start->x - $this->end->x, 2) +
           
pow($this->start->y - $this->end->y, 2) +
           
pow($this->start->z - $this->end->z, 2)
        );
    }

   
/*
     * The (String) representation of this Line as "Line3D[start, end, length]".
     */
   
public function __toString()
    {
        return
'Line3D[start=' . $this->start .
           
', end=' . $this->end .
           
', length=' . $this->getLength() . ']';
    }
}

/*
 * create and display objects of type Line3D.
 */
echo '<p>' . (new Line3D()) . "</p>\n";
echo
'<p>' . (new Line3D(0, 0, 0, 100, 100, 0)) . "</p>\n";
echo
'<p>' . (new Line3D(0, 0, 0, 100, 100, 100)) . "</p>\n";

?>

  <--  The results look like this  -->

Line3D[start=Point3D(x=0, y=0, z=0), end=Point3D(x=1, y=1, z=1), length=1.73205080757]

Line3D[start=Point3D(x=0, y=0, z=0), end=Point3D(x=100, y=100, z=0), length=141.421356237]

Line3D[start=Point3D(x=0, y=0, z=0), end=Point3D(x=100, y=100, z=100), length=173.205080757]

My absolute favorite thing about OOP is that "good" objects keep themselves in check. I mean really, it's the exact same thing in reality... like, if you hire a plumber to fix your kitchen sink, wouldn't you expect him to figure out the best plan of attack? Wouldn't he dislike the fact that you want to control the whole job? Wouldn't you expect him to not give you additional problems? And for god's sake, it is too much to ask that he cleans up before he leaves?

I say, design your classes well, so they can do their jobs uninterrupted... who like bad news? And, if your classes and objects are well defined, educated, and have all the necessary data to work on (like the examples above do), you won't have to micro-manage the whole program from outside of the class. In other words... create an object, and LET IT RIP!
aaron at thatone dot com 15-Dec-2007 06:46
I was confused at first about object assignment, because it's not quite the same as normal assignment or assignment by reference. But I think I've figured out what's going on.

First, think of variables in PHP as data slots. Each one is a name that points to a data slot that can hold a value that is one of the basic data types: a number, a string, a boolean, etc. When you create a reference, you are making a second name that points at the same data slot. When you assign one variable to another, you are copying the contents of one data slot to another data slot.

Now, the trick is that object instances are not like the basic data types. They cannot be held in the data slots directly. Instead, an object's "handle" goes in the data slot. This is an identifier that points at one particular instance of an obect. So, the object handle, although not directly visible to the programmer, is one of the basic datatypes.

What makes this tricky is that when you take a variable which holds an object handle, and you assign it to another variable, that other variable gets a copy of the same object handle. This means that both variables can change the state of the same object instance. But they are not references, so if one of the variables is assigned a new value, it does not affect the other variable.

<?php
// Assignment of an object
Class Object{
   public
$foo="bar";
};

$objectVar = new Object();
$reference =& $objectVar;
$assignment = $objectVar

//
// $objectVar --->+---------+
//                |(handle1)----+
// $reference --->+---------+   |
//                              |
//                +---------+   |
// $assignment -->|(handle1)----+
//                +---------+   |
//                              |
//                              v
//                  Object(1):foo="bar"
//
?>

$assignment has a different data slot from $objectVar, but its data slot holds a handle to the same object. This makes it behave in some ways like a reference. If you use the variable $objectVar to change the state of the Object instance, those changes also show up under $assignment, because it is pointing at that same Object instance.

<?php
$objectVar
->foo = "qux";
print_r( $objectVar );
print_r( $reference );
print_r( $assignment );

//
// $objectVar --->+---------+
//                |(handle1)----+
// $reference --->+---------+   |
//                              |
//                +---------+   |
// $assignment -->|(handle1)----+
//                +---------+   |
//                              |
//                              v
//                  Object(1):foo="qux"
//
?>

But it is not exactly the same as a reference. If you null out $objectVar, you replace the handle in its data slot with NULL. This means that $reference, which points at the same data slot, will also be NULL. But $assignment, which is a different data slot, will still hold its copy of the handle to the Object instance, so it will not be NULL.

<?php
$objectVar
= null;
print_r($objectVar);
print_r($reference);
print_r($assignment);

//
// $objectVar --->+---------+
//                |  NULL   |
// $reference --->+---------+
//                          
//                +---------+
// $assignment -->|(handle1)----+
//                +---------+   |
//                              |
//                              v
//                  Object(1):foo="qux"
?>