기본 개념
PHP는 객체를 참조 및 핸들과 동일한 방식으로 처리합니다. 즉, 각 변수는 전체 객체의 복사본이 아닌 객체에 대한 참조를 보유합니다.
새 객체가 생성되면 객체가 생성자를 정의하고 오류 시 예외가 발생하지 않는 한 객체에는 항상 값이 할당됩니다. 클래스는 인스턴스화되기 전에 정의되어야 합니다.
객체를 생성할 때 클래스가 네임스페이스에 속하는 경우 전체 이름을 사용해야 합니다.
클래스 정의 내에서 new self
및 new parent
을 사용하여 객체를 생성할 수 있습니다.
<code><?php $instance = new stdClass(); $assigned = $instance; $reference = & $instance; $instance->var = '$assigned will have this value.'; $instance = null; var_dump($instance); var_dump($reference); var_dump($assigned);</code>
이 코드의 출력은 다음과 같습니다.
<code>null null object(stdClass)[1] public 'var' => string '$assigned will have this value.' (length=31)</code>
PHP 5.3에는 객체의 인스턴스를 생성하는 두 가지 새로운 방법이 도입되었습니다. 다음 방법을 사용하여 인스턴스를 생성할 수 있습니다.
<code><?php class Test { static public function getNew() { return new static; } } class Child extends Test {} $obj1 = new Test(); $obj2 = new $obj1; var_dump($obj1 !== $obj2); // true $obj3 = Test::getNew(); var_dump($obj3 instanceof Test); // true $obj4 = Child::getNew(); var_dump($obj4 instanceof Child); // true var_dump($obj1 == $obj2); // true</code>
PHP는 다중 상속을 지원하지 않습니다. 상속된 메서드와 속성은 동일한 이름으로 다시 선언하여 재정의할 수 있습니다. 물론 생성자를 제외하고 매개변수는 일관성을 유지해야 합니다. 그러나 상위 클래스가 메서드 정의 시 final
을 사용하면 해당 메서드를 재정의할 수 없습니다. 재정의된 메서드와 속성은 parent::
을 통해 액세스할 수 있습니다. parent::
은 상위 클래스의 상수 const
에만 액세스할 수 있으며 변수에는 액세스할 수 없습니다.
<code><?php class A { private $name = 'A'; const conname = 'A'; public function getName() { return $this->name; } } class B extends A { private $name = 'B'; const conname = 'B'; public function getName() { return $this->name; } public function getParent() { return parent::conname; } } class C extends B { private $name = 'C'; const conname = 'C'; public function getName() { return $this->name; } public function getParent() { return parent::conname; } } $a = new A; var_dump($a->getName()); // A $b = new B; var_dump($b->getName()); // B var_dump($b->getParent()); // A $c = new C; var_dump($c->getName()); // C var_dump($c->getParent()); // B</code>
PHP 5.5부터는 class
키워드를 클래스 이름 확인에도 사용할 수 있습니다. ClassName::class
을 사용하면 ClassName
클래스의 정규화된 이름이 포함된 문자열을 얻을 수 있습니다.
<code><?php namespace NS { class ClassName {} echo ClassName::class; // NS\ClassName }</code>
속성
속성은 클래스의 변수 멤버입니다. 속성의 변수는 초기화할 수 있지만 초기화된 값은 상수여야 합니다. 여기서 상수는 PHP 스크립트가 컴파일 단계에서 값을 얻을 수 있으며 평가하기 위해 런타임 정보에 의존하지 않는다는 것을 의미합니다.
클래스의 멤버 메서드에서 $this->property
을 사용하여 비정적 속성에 액세스하고 self::$property
를 사용하여 정적 속성에 액세스합니다. 정적 속성을 선언할 때는 static
키워드를 사용하세요.
클래스 상수
상수를 정의할 때 $
기호와 액세스 제어 키워드는 필요하지 않습니다.
인터페이스에서도 상수를 정의할 수 있습니다.
수업 자동 로딩
객체 지향 애플리케이션을 작성할 때 각 클래스 정의에 대해 PHP 소스 파일을 생성하는 것이 일반적입니다. 파일이 이러한 클래스를 호출해야 하는 경우 포함된 파일의 긴 목록을 파일 시작 부분에 작성해야 합니다. 실제로, 아직 정의되지 않은 클래스를 사용하려고 할 때 자동으로 호출되는 __autoload()
함수를 정의할 수 있습니다.
수동 팁에는 spl_autoload_register()
이 클래스 자동 로딩을 구현하는 보다 유연한 방법을 제공한다고 나와 있습니다. 이에 대해서는 나중에 살펴보겠습니다.
PHP의 CLI 대화형 모드인 명령줄 모드에서는 자동 로딩을 사용할 수 없습니다.
사용자 입력에 위험한 문자가 있을 수 있습니다. 최소한 __autoload()
시 입력을 확인해야 합니다.
다음 방법을 통해 클래스를 자동으로 로드할 수 있습니다.
<code><?php function __autoload($class_name) { require_once $class_name.'.php'; } $obj1 = new MyClass1(); $obj2 = new MyClass2();</code>
예외 처리에 대해서는 나중에 살펴보겠습니다.
생성자와 소멸자
PHP 5에서는 개발자가 클래스의 생성자로 메서드를 정의할 수 있으며 생성자는 오버로드를 지원하지 않습니다.
하위 클래스에 생성자가 정의된 경우 부모 클래스의 생성자는 암시적으로 호출되지 않으며, 그렇지 않으면 일반 클래스 메서드처럼 부모 클래스에서 상속됩니다(private
으로 정의되지 않은 경우). 상위 클래스의 생성자를 실행하려면 하위 클래스의 생성자에서 parent::__construct()
을 호출해야 합니다.
은 다른 메소드와 달리 __construct()
과 상위 클래스 __construct()
의 매개변수가 다를 경우 재정의할 수 있습니다.
PHP 5.3.3부터 네임스페이스에서 클래스 이름과 동일한 이름을 가진 메서드는 더 이상 생성자가 아닙니다.
소멸자는 객체에 대한 모든 참조가 삭제되거나 객체가 명시적으로 소멸될 때 실행됩니다. exit()
을 사용하여 스크립트를 종료하는 경우에도 소멸자는 호출됩니다.
소멸자에서 예외를 발생시키려고 하면 치명적인 오류가 발생합니다.
접근 통제
클래스 속성은 public, protected, private 중 하나로 정의해야 하며, 키워드는 생략할 수 없습니다. 클래스의 메서드가 액세스 제어 키워드를 설정하지 않으면 해당 메서드는 기본적으로 공개됩니다.
동일한 클래스의 개체는 동일한 인스턴스가 아니더라도 서로의 private 및 protected 멤버에 액세스할 수 있습니다. 샘플 프로그램은 다음과 같습니다.
<code><?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) { $other->foo = 'hello'; var_dump($other->foo); $other->bar(); } } $test = new Test('test'); $test->baz(new Test('other'));</code>
객체 상속
한 클래스가 다른 클래스를 확장하는 경우 상위 클래스를 하위 클래스보다 먼저 선언해야 합니다.
범위 분해 연산자
간단히 말하면 범위 확인 연산자는 콜론 쌍으로, 정적 멤버, 클래스 상수에 액세스하는 데 사용할 수 있고 상위 클래스의 속성과 메서드를 호출하는 데도 사용할 수 있습니다.
클래스 정의 외부에서 이러한 항목을 참조할 때는 클래스 이름을 사용하세요.
정적
static
키워드를 사용하면 정적 메서드와 속성을 정의할 수 있을 뿐만 아니라 정적 변수와 후기 정적 바인딩을 정의하는 데에도 사용할 수 있습니다. 클래스 속성이나 메서드를 static으로 선언하면 클래스를 인스턴스화하지 않고도 직접 액세스할 수 있습니다.
정적 속성은 인스턴스화된 클래스의 객체를 통해 액세스할 수 없지만 정적 메서드는 액세스할 수 있습니다.
액세스 제어가 지정되지 않으면 속성과 메서드는 기본적으로 공개로 설정됩니다.
정적 메서드에서 비정적 메서드를 호출하면 E_STRICT
수준 오류가 발생합니다.
抽象类
PHP 5支持抽象类和抽象方法。类中如果有一个抽象方法,那这个类必须被声明为抽象的。
抽象类不能被实例化。抽象方法只是声明了其调用方式(参数),不能定义其具体的功能实现。继承抽象类时,子类必须定义父类中的所有抽象方法,且这些方法的访问控制必须和父类一样活更宽松。
方法的调用方式必须匹配。但是,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。这也试用与PHP 5.4起的构造函数。可以在子类中定义父类签名中不存在的可选参数。
<code><?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'); echo $class->prefixName('Pacwoman');</code>
对象接口
听说过接口,一直没用过。使用接口,可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容,也就是说接口中定义的所有方法都是空的。接口中定义的所有方法都必须是公有的,这是接口的特性。
接口也可以继承多个接口,用逗号分隔,使用extends
操作符。类中必须实现接口中定义的所有方法,否则会报错。要实现一个接口,使用implements
操作符。类可以实现多个接口,用逗号分隔。实现多个接口时,接口中的方法不能有重名。类要实现接口,必须使用和接口中所定义的方法完全一致的方式。
接口中也可定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口覆盖。
traits
从PHP 5.4.0开始,可以使用traits
实现代码复用。Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制。Trait 不能通过它自身来实例化。它为传统继承增加了水平特性的组合。
优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误,为解决冲突,需使用insteadof
操作符来指明使用冲突方法中的哪一个,这种方法仅允许排除掉其它方法。as
操作符可以将其中一个冲突的方法以另一个名称(别名)来引入。
<code><?php trait A { public function smallTalk() { echo 'a'; } public function bigTalk() { echo 'A'; } } trait B { public function smallTalk() { echo 'b'; } public function bigTalk() { echo 'B'; } } class Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; B::bigTalk as talk; } } $t = new Talker(); $t->smallTalk(); // b $t->bigTalk(); // A $t->talk(); // B</code>
使用as
操作符还可以用来调整方法的访问控制,或者给方法一个改变了访问控制的别名,原版方法的访问控制规则没有改变。
<code><?php trait HelloWorld { public function sayHello() { echo 'Hello World.'; } } class MyClass1 { use HelloWorld { sayHello as protected; } } class MyClass2 { use HelloWorld { sayHello as private myPrivateHello; } }</code>
就像类能够使用trait
那样,多个trait
能够组合为一个trait
。
为了对使用的类施加强制要求,trait 支持抽象方法的使用。
<code><?php trait Hello { public function sayHelloWorld() { echo 'Hello ' . $this->getWorld(); } abstract public function getWorld(); } class MyHelloWorld { private $world; use Hello; public function getWorld() { return $this->world; } public function setWorld($val) { $this->world = $val; } } $c = new MyHelloWorld; $c->setWorld('world'); $c->sayHelloWorld();</code>
如果trait
定义了一个属性,那类将不能定义同样名称的属性,否则会产生错误。
重载
PHP提供的重载是指动态地创建类属性和方法,与其它绝大多数面向对象语言不同。通过魔术方法来实现。当使用不可访问的属性或方法时,重载方法会被调用。所有的重载方法都必须被声明为public
。
使用__get()
,__set()
,__isset()
,__unset()
进行属性重载,示例如下。
<code><?php class PropertyTest { private $data = array(); public $declared = 1; private $hidden = 2; public function __set($name, $value) { echo "Setting $name to $value. " . '<br>'; $this->data[$name] = $value; } public function __get($name) { echo "Getting $name. <br>"; if(array_key_exists($name, $this->data)) { return $this->data[$name]; } return null; } public function __isset($name) { echo "Is $name set? <br>"; return isset($this->data[$name]); } public function __unset($name) { echo "Unsetting $name. <br>"; unset($this->data[$name]); } } $obj = new PropertyTest; $obj->a = 1; var_dump($obj->a); var_dump(isset($obj->a)); unset($obj->a); var_dump(isset($obj->a)); var_dump($obj->declared); var_dump($obj->hidden);</code>
输出结果如下:
<code>Setting a to 1. Getting a. int 1 Is a set? boolean true Unsetting a. Is a set? boolean false int 1 Getting hidden. null</code>
在对象中调用一个不可访问方法时,__call()
会被调用。用静态方式中调用一个不可访问方法时,__callStatic()
会被调用。参数为调用方法的名称和一个枚举数组,注意区分大小写。
使用__call()
和__callStatic()
对方法重载,示例如下。
<code><?php class MethodTest { public function __call($name, $arguments) { echo "Calling object method $name " . implode(', ', $arguments) . '<br>'; } public static function __callStatic($name, $arguments) { echo "Calling static method $name " . implode(', ', $arguments) . '<br>'; } } $obj = new MethodTest; $obj->runTest('in object context'); MethodTest::runTest('in static context');</code>
遍历对象
对象可以用过单元列表来遍历,例如用foreach
语句。默认所有可见属性都将被用于遍历。
<code><?php class MyClass { public $var1 = 'value 1'; public $var2 = 'value 2'; public $var3 = 'value 3'; private $var4 = 'value 4'; protected $var5 = 'value 5'; } $obj = new MyClass; foreach($obj as $key => $value) { echo "$key => $value <br>"; }</code>
示例程序2实现了Iterator接口的对象遍历,示例程序3通过实现IteratorAggregate来遍历对象。
魔术方法
PHP 将所有以__
(两个下划线)开头的类方法保留为魔术方法。定义类方法时,除魔术方法外,建议不要以__
为前缀。
前面遇到过的魔术方法有:__construct()
,__destruct()
,__call()
,__callStatic()
,__get()
,__set()
,__isset()
,__unset()
。后面将会介绍:__sleep()
,__wakeup()
,__toString()
,__invoke()
,__set_state()
,__clone()
和__debugInfo()
。
__sleep
和__wakeup
不清楚具体做什么用的,示例程序中给出了个数据库连接的例子。
__toString
方法用于一个类被当成字符串时应怎样回应。此方法必须返回一个字符串,且不能再方法中抛出异常。如果将一个未定义__toString()
方法的对象转换为字符串,将产生错误。
当尝试以调用函数的方式调用一个对象时,__invoke()
方法会被调用。
当调用var_export()
导出类时,__set_state()
会被调用。
当调用var_dump()
时,__debugInfo
会被调用。PHP 5.6新加入,没合适的环境无法测试。
final
果父类中的方法被声明为final
,则子类无法覆盖该方法。如果一个类被声明为final
,则不能被继承。属性不能被定义为final
,只有类和方法才能被定义为final
。
对象复制
多数情况,我们不需要完全复制一个对象,但有时确实需要。对象复制可以通过clone
关键字来完成。这种复制是通过调用对象的__clone()
方法实现的,但是对象中的__clone()
方法不能被直接调用。
对象比较
比较运算符==
为真的条件是:两个对象的属性和属性值都相等,而且两个对象是同一个类的实例。
继承与统一个基类的两个子类的对象不会相等==
。
<code><?php class Base {} class A extends Base {} class B extends Base {} $a = new A; $b = new B; var_dump($a == $b); // false</code>
全等运算符===
为真的条件是:两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
类型约束
类型约束是指函数的参数可以指定必须为对象、接口、数组或者callable
类型。但是类型约束不能用于标量类型如int
或string
,traits
也不允许。类型约束允许NULL
值。
后期静态绑定
后期静态绑定,用于在继承范围内引用静态调用的类。
转发调用,指的是通过以下几种方式进行的静态调用:self::
,parent::
,static::
以及forward_static_call()
。
后期静态绑定的工作原理是,存储了上一个非转发调用的类名。
当进行静态方法调用时,该类名即为明确指定的那个;当进行非静态方法调用时,即为该对象所属的类。
使用self::
或者__CLASS__
对当前类的静态引用,取决于定义当前方法所在的类。
<code><?php class A { public static function who() { echo __CLASS__; } public static function test() { self::who(); } } class B extends A { public static function who() { echo __CLASS__; } } B::test(); // A B::who(); // B</code>
用static::
关键字表示运行时最初调用的类,后期静态绑定就是这样使用。如下面程序所示,也就是说调用test()
时引用的类是B
而不是A
。
<code><?php class A { public static function who() { echo __CLASS__; } public static function test() { static::who(); } } class B extends A { public static function who() { echo __CLASS__; } } B::test(); // B B::who(); // B</code>
示例2给出的是非静态环境下使用static::
。
后期静态绑定的解析,会一直到取得一个完全解析了的静态调用为止。另外,如果静态调用使用parent::
或self::
将转发调用信息。
<code><?php class A { public static function foo() { static::who(); } public static function who() { echo __CLASS__; } } class B extends A { public static function test() { A::foo(); parent::foo(); self::foo(); } public static function who() { echo __CLASS__; } } class C extends B { public static function who() { echo __CLASS__; } } C::test(); // ACC</code>
那么问题来了,结果为什么是这样的呢?
对象和引用
默认情况下,对象时通过引用传递的。但这种说法不完全正确,其实两个对象变量不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。
对象序列化
所有PHP里面的值,都可以使用函数serialize()
来返回一个包含字节流的字符串来表示。unserialize()
函数能够重新把字符串变为原来的值。
序列化一个对象,将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。为了能够unserialize()
一个对象,这个对象的类必须已经定义过。在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。
(全文完)
以上就介绍了类与对象 - PHP手册笔记,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。