面向对象具有封装性,继承性(php支持的是单继承性,一个子类有且只有一个父类),多态性(同一个类的不同对象调用同一个方法出现不同的结果)。
在静态方法中,只能调用静态变量,不能调用普通变量。普通方法可以调用静态变量。
同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。
面向过程、面向对象、函数式编程是编程语言中的三大范式,是三种不同编码和设计风格。
php中对象和数组的区别就是,对象比数组多了一个指向所属类的指针。因此,对象本身并不包含方法。
对象都有一个指向所属类的指针,如果这个对象是由标量强制类型转化而来,这个对象的指针就指向stdClass。
::的使用
- parent: 可以调用父类的成员方法,成员变量,常量。
- self: 可以调用当前的静态成员和常量。
- 类名: 可以调用本类的变量,常量和方法。
静态成员的调用 关键字::静态成员
- self:在类的内部调用静态成员时使用。
- 静态成员所在的类名:在类外调用类内部的静态成员时使用。
interface
php是单继承的,要多继承必须使用接口:class student implements people,classroom
接口中只能包含未实现的方法和一些成员变量。
对象比较,判断对象属于哪个类
== 是用来判断两个对象里面的内容是否一样。
=== 是用来判断对象的引用地址是否一致。
if($o instanceof c){
echo '该对象属于类c';
}
__clone、__set、__get、__call、__sleep、__wakeup、__toString、__autoload
/*********************__set、__get*****************************/
<?php
class account{
private $user=1;
private $pwd=2;
public function __set($n,$v){
echo "set $n $v\n";
$this->$n=$v;
}
public function __get($n){
if(!isset($this->$n)){
echo 'not set,'."\n";
$this->$n='is set for u '."\n";
}
return $this->$n;
}
}
$a=new account();
echo $a->user;
$a->name=3;
echo $a->name;
echo $a->mm;
/*******************************输出***************************************
1set name 3
3not set,
set mm is set for u
is set for u
*************************************************************/
//__callStatic 体会简单的 对象关系映射 orm
<?php
abstract class ActiveRecord{
protected static $table;
protected $fieldvalues;
public $select;
static function findById($id){
$query="select * from ".static::$table." where id = $id ";
return self::createDomain($query);
}
function __get($fieldname){
return $this->fieldvalues[$fieldname];
}
static function __callStatic($method,$args){
$field=preg_replace('/^findBy(\w*)$/','$(1)',$method);
var_dump($field.';;;;;;;;;;;');
$query="select * from ".static::$table." where $field = '$args[0]' ";
return self::createDomain($query);
}
private static function createDomain($query){
$klass=get_called_class();
echo "\n";
echo $klass."";
echo "\n";
$domain=new $klass();
$domain->fieldvalues=array();
$domain->select=$query;
foreach ($klass::$fields as $field=>$type){
$domain->fieldvalues[$field]='TODO:set from sql result';
}
return $domain;
}
}
class Customer extends ActiveRecord{
protected static $table='custdb';
protected static $fields=array(
'id'=>'int',
'email'=>'varchar',
'lastname'=>'varchar',
);
}
class Sales extends ActiveRecord{
protected static $table='salesdb';
protected static $fields=array(
'id'=>'int',
'item'=>'varchar',
'qty'=>'int',
);
}
var_dump(Customer::findById(123)->select);
var_dump(Customer::findById(123)->email);
var_dump(Sales::findById(123)->select);
var_dump(Customer::findByLastname('Leon')->select);
/***********************************
*
Customer
string(36) "select * from custdb where id = 123 "
Customer
string(24) "TODO:set from sql result"
Sales
string(37) "select * from salesdb where id = 123 "
string(15) "$(1);;;;;;;;;;;"
Customer
string(41) "select * from custdb where $(1) = 'Leon' "
************************************/
php不会执行未曾定义的魔术方法。
__call包含两个参数,第一个是方法名,第二个类型是数组,元素为方法的各个参数。
$this 用法
<?php
class A{
function foo(){
if (isset($this)) {
echo '$this is defined (';
echo get_class($this); //只有该类被实例化,那么$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();
/*********************以上例程会输出:****************
$this is defined (A)
$this is not defined.
$this is defined (B)
$this is not defined.
***************************************************/
ClassName::class
自 PHP 5.5 起,关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。
<?php
namespace NS {
class ClassName {
}
echo ClassName::class;
}
/*********************以上例程会输出:****************
NS\ClassName
***************************************************/
2017.02.13
简介
PHP 对待对象的方式与引用和句柄相同,即每个变量都持有对象的引用,而不是整个对象的拷贝。参见对象和引用。
基本概念
// $this and static
<?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();
$className = 'B';
$className::bar();
/*********
$this is defined (A)
$this is not defined.
$this is defined (B)
$this is not defined.
PHP Strict Standards: Non-static method A::foo() should not be called statically in F:\WWW\l.php on line 29
PHP Deprecated: Non-static method A::foo() should not be called statically, assuming $this from incompatible context in F:\WWW\l.php on line 21
PHP Strict Standards: Non-static method B::bar() should not be called statically in F:\WWW\l.php on line 34
PHP Strict Standards: Non-static method A::foo() should not be called statically in F:\WWW\l.php on line 21
在类定义内部,可以用 new self 和 new parent 创建新对象。
<?php
class SimpleClass{
public $var = 12;
}
$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"
}
*****
class a{}
$a = new a();
$b = new $a; //is same to: $b = $a;
当覆盖方法时,参数必须保持一致否则 PHP 将发出 E_STRICT 级别的错误信息。但构造函数例外,构造函数可在被覆盖时使用不同的参数。
属性
使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。
可以用 nowdocs 初始化属性。因为会解析变量,所以heredocs 不行。默认的定义就是heredocs。
//nowdocs
$str = <<<'MMM'
I love apple.
MMM;
//heredocs
$str = <<<'NNN'
I love apple.
NNN;
//default
$str = <<<'CCC'
I love apple.
CCC;
类常量
自动加载类
spl_autoload_register() 提供了一种更加灵活的方式来实现类的自动加载。因此,不再建议使用 __autoload()函数,在以后的版本中它可能被弃用。
自动加载不可用于 PHP 的 CLI 交互模式。
构造函数和析构函数
自 PHP 5.3.3 起,在命名空间中,与类名同名的方法不再作为构造函数。这一改变不影响不在命名空间中的类。
析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。
析构函数在脚本关闭时调用,此时所有的 HTTP 头信息已经发出。脚本关闭时的工作目录有可能和在 SAPI(如 apache)中时不同。
访问控制(可见性)
同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。
对象继承
范围解析操作符 (::)
self,parent 和 static 这三个特殊的关键字是用于在类定义的内部对其属性或方法进行访问的。
Static(静态)关键字
抽象类
子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。
对象接口
接口中定义的所有方法都必须是公有,这是接口的特性。
类中必须实现接口中定义的所有方法,否则会报一个致命错误。
实现多个接口时,接口中的方法不能有重名。
接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。
Trait
Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
从基类继承的成员会被 trait 插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。
为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。
以上方式仅允许排除掉其它方法,as 操作符可以将其中一个冲突的方法以另一个名称来引入。
//解决冲突
<?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;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
//使用 as 语法还可以用来调整方法的访问控制。
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// 修改 sayHello 的访问控制
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// 给方法一个改变了访问控制的别名
// 原版 sayHello 的访问控制则没有发生变化
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
//正如 class 能够使用 trait 一样,其它 trait 也能够使用 trait。
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World!';
}
}
trait HelloWorld {
use Hello, World;
}
class MyHelloWorld {
use HelloWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
//为了对使用的类施加强制要求,trait 支持抽象方法的使用。
<?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;
}
}
//Traits 可以被静态成员静态方法定义。
<?php
trait Counter {
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
}
class C1 {
use Counter;
}
class C2 {
use Counter;
}
$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>
//静态方法
<?php
trait StaticExample {
public static function doSomething() {
return 'Doing something';
}
}
class Example {
use StaticExample;
}
Example::doSomething();
//Trait 同样可以定义属性。
<?php
trait PropertiesTrait {
public $x = 1;
}
class PropertiesExample {
use PropertiesTrait;
}
$example = new PropertiesExample;
$example->x;
//如果 trait 定义了一个属性,那类将不能定义同样名称的属性,否则会产生一个错误。如果该属性在类中的定义与在 trait 中的定义兼容(同样的可见性和初始值)则错误的级别是 E_STRICT,否则是一个致命错误。
<?php
trait PropertiesTrait {
public $same = true;
public $different = false;
}
class PropertiesExample {
use PropertiesTrait;
public $same = true; // Strict Standards
public $different = true; // 致命错误
}
匿名类
PHP 7 开始支持匿名类。
匿名类被嵌套进普通 Class 后,不能访问这个外部类(Outer class)的 private(私有)、protected(受保护)方法或者属性。 为了访问外部类(Outer class)protected 属性或方法,匿名类可以 extend(扩展)此外部类。 为了使用外部类(Outer class)的 private 属性,必须通过构造器传进来:
重载
PHP所提供的”重载”(overloading)是指动态地”创建”类属性和方法。我们是通过魔术方法(magic methods)来实现的。
所有的重载方法都必须被声明为 public。
这些魔术方法的参数都不能通过引用传递。
PHP中的”重载”与其它绝大多数面向对象语言不同。传统的”重载”是用于提供多个同名的类方法,但各方法的参数类型和个数不同。
//属性重载
public void __set ( string $name , mixed $value )
public mixed __get ( string $name )
public bool __isset ( string $name )
public void __unset ( string $name )
//当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
//当对不可访问属性调用 unset() 时,__unset() 会被调用。
//属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。
//所以这些方法都不能被 声明为 static。
在除 isset() 外的其它语言结构中无法使用重载的属性,这意味着当对一个重载的属性使用 empty() 时,重载魔术方法将不会被调用。
为避开此限制,必须将重载属性赋值到本地变量再使用 empty()。
//方法重载
public mixed __call ( string $name , array $arguments )
public static mixed __callStatic ( string $name , array $arguments )
//在对象中调用一个不可访问方法时,__call() 会被调用。
//在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
遍历对象
foreach
魔术方法
public array __sleep ( void )
void __wakeup ( void )
__sleep:如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
__sleep() 不能返回父类的私有成员的名字。可以用 Serializable 接口来替代。
__sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存, 这个功能就很好用。
__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
public string __toString ( void )
__toString() 此方法必须返回一个字符串。
mixed __invoke ([ $... ] )
__invoke: 当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用。
<?php
class CallableClass{
function __invoke($x){
var_dump($x);
}
}
$obj = new CallableClass;
// $obj(5);
var_dump(is_callable($obj));
static object __set_state ( array $properties )
__set_state :var_export() 导出类时,此静态 方法会被调用。
array __debugInfo ( void )
var_dump() 导出类时,方法会被调用。
Final 关键字
属性不能被定义为 final,只有类和方法才能被定义为 final。
对象复制
对象比较
运算符(==)比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。
全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
类型约束
函数的参数可以指定必须为对象(在函数原型里面指定类的名字),接口,数组(PHP 5.1 起)或者 callable(PHP 5.4 起)。
如果一个类或接口指定了类型约束,则其所有的子类或实现也都如此。
类型约束不能用于标量类型如 int 或 string。Traits 也不允许。
<?php
//如下面的类
class MyClass{
/**
* 测试函数
* 第一个参数必须为 OtherClass 类的一个对象
*/
public function test(OtherClass $otherclass) {
echo $otherclass->var;
}
/**
* 另一个测试函数
* 第一个参数必须为数组
*/
public function test_array(array $input_array) {
print_r($input_array);
}
/**
* 第一个参数必须为递归类型
*/
public function test_interface(Traversable $iterator) {
echo get_class($iterator);
}
/**
* 第一个参数必须为回调类型
*/
public function test_callable(callable $callback, $data) {
call_user_func($callback, $data);
}
}
// OtherClass 类定义
class OtherClass {
public $var = 'Hello World';
}
后期静态绑定
后期静态绑定的功能,用于在继承范围内引用静态调用的类。
<?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
//简单地说,这个关键字能够让你在上述例子中调用 test() 时引用的类是 B 而不是 A。
//最终决定不引入新的关键字,而是使用已经预留的 static 关键字
<?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
//后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。
//另一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。
<?php
class A {
public static function foo() {
echo 1;
static::who();
echo 2;
}
public static function who() {
echo 3;
echo __CLASS__."\n";
echo 4;
}
}
class B extends A {
public static function test() {
echo 5;
A::foo();
echo 6;
parent::foo();
echo 7;
self::foo();
echo 8;
}
public static function who() {
echo 9;
echo __CLASS__."\n";
echo 'a';
}
}
class C extends B {
public static function who() {
echo 'b';
echo __CLASS__."\n";
echo 'd';
}
}
C::test();
/**********
513A
4261bC
d271bC
d28
**************/
对象序列化
如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。