相关定义
对象(object):现实生活中的实体,在编程语言中的体现。实体都有属性和功能。一组数据,和操作管理这些数据的操作,定义在一起就形成了一个实体,称之为对象。(属性和方法的集合)
属性(property),对象中,对象所拥有的数据,称之为对象的属性。
方法(method):对象中,对象所拥有的管理数据的能力,称之为方法。
在php中,对象通过对类的实体化形成的对象。
类(class): 对象的模子。 一类的对象抽取出来。一个将对象的行为和属性的一个抽象。就是一个定义。规定了对象应该有哪些属性,应该有哪些操作(方法)。
实例化:一个动作,根据类中所定义的对象的特征,形成一个对象的过程。
注意: php 中,对象一定是通过类的实例化来的。类的地位,只是得到对象的方法。
可以使用php 预定义的类。
stdclass . 通过实例化该类,就可以得到一个对象。
实例化: 通过关键字new 完成.
基本语法
定义类
class 关键字
class 类名 {
成员
}
在定义一个类的时,需要知道这个类所实例化的对象,应该具有哪些属性和方法。
增加属性: 对象所拥有的数据,就是定义在类中变量。
增加方法: 对象所拥有的操作,可执行代码就是操作.定义在类中的函数.
注意:类中的成员(属性和方法),需要增加访问修饰限定符,简单的增加--public
在声明成员时,不能直接使用变量,需要使用关键字来声明
实例化对象
通过类得到对象 ,操作符 new 完成.
实例化好后,典型的应该保存在变量内
$stu = new Student();
一个类,可以实例化多个对象,多个不同的对象
对象操作属性和方法
使用操作符 ->
// 对象 -> 成员
$stu->a; // 10
注意,属性名前$没有了
如果存在$, 语法就 变成 属性名(属性标识符) 由变量充当,变成了可变属性的语法
$property_name = 'stu_id';
echo $stu->$property_name;
属性名,同变量名一样,严格区分大小写。
保存数据标识符,就区分大小写。
如果是结构标识符,就区分大小写。 比如:函数,类,方法名。
访问方法:
$stu->study();
方法名,不区分大小写
方法名,支持可变方法,方法名有变量来代替
类名,可以使用变量来代替。
$class_name = 'Student';
$stu = new $class_name;
属性
可以在定义时,为属性直接设置初始值,但是必须已经存在的数据(类似于函数参数的默认值)
如果没有默认值,则值为null
属性,每个对象的同名属性,都可以具有不同的值, 每个对象所拥有的属性时不同的数据空间。
因为在实例化对象时,php会为每个对象分配一个独立的空间,保存对象的属性
由于,常规的,每个对象,应该拥有不同的属性值。
建议,在得到该对象时,应该对对象所拥有的属性,进行初始化。
方法
方法,也是属于某个对象。但是通常,方法内所操作的数据,都是该对象所拥有的数据。
在使用对象调用某个方法时,会自动地将当前对象传递到该方法内(类似于参数的传递)。 方法内,会自动使用变量 $this 来接收这个对象。因此,可以在方法内,通过$this方法调用该方法的对象。
如何在方法中,访问对象,$this
$this,这个对象。调用该方法的对象。
$this 就是一个方法内的局部变量。特殊在,方法被对象调用执行时,PHP会自动确定是那个对象调用的该方法,会使用该对象为方法内的$this赋值
构造&析构
类,没有作用域, 作用域,只是讲,函数内和函数外。
注意,想访问到对象的成员(属性和方法),一定要先找到对象和方法。
class Student {
public $stu_id;
public $stu_name;
public function sayName() {
echo $this->stu_name; // zf
var_dump($stu_name); // 报错 Undefined variable
var_dump($GLOBALS['stu_name']); // 报错 Undefined variable
}
}
$stu = new Student();
$stu->stu_id = '16.7.3';
$stu->stu_name = 'zf';
$stu->sayName();
类中定义的属性,不是相当于类中定义的方法的全局变量。不能直接在方法中使用属性变量的形式访问:
如果直接操作(输出)这个类名,则不会当做类来看待,而是当做常量来看待。
构造
在得到对象时,几乎都需要,对对象属性进行初始化,而且都是一样的操作。在一个操作中完成初始化,然后对该方法进行多次调用。
在实例化类得到对象时被自动地调用
主要承担的工作是初始化对象属性
对象的属性初始化
只要得到了对象,对象的属性就应该被赋予新的值。
如果某些属性,在对象出现时,可以被设置为某些特定的值。就可以在声明类时,为声明的属性设置默认值。
<?php
public function init ( $id, $name ) {
$this->stu_id = $id;
$this->stu_name = $name;
}
调用该方法初始化
<?php
Class Student {
public function init ( $id, $name ) {
$this->stu_id = $id;
$this->stu_name = $name;
}
}
$stu1 = new Student;
// $stu1->stu_id = 100;
// $stu1->stu_name = '李寻欢';
$stu1->init(100,'李寻欢');
var_dump($stu1);
再前进一步:
是否可以在实例化后,自动调用该初始化方法的方法。
PHP的 oop 机制,在new完成时,会试着调用一个叫做 __constructor() 的方法.如果将初始化的代码,写到这个方法内,就可以完成自动初始化。
该方法,在通过类实例化对象,也叫构造对象时,被自动调用的,常常用于初始化对象,这个方法被叫做 构造方法。(此方法,就是比普通方法多了一个自动调用的功能)
只要得到了对象,对象的属性就应该被赋予新的值
<?php
Class Student {
public function __construct () {
echo 'run';
}
}
$stu1 = new Student(100,'李寻欢');
由于不用去调用这个构造函数,如何传参呢?
在实例化时,通过在类名后,增加实例化列表形式,为构造函数方法传参。
<?php
Class Student {
public function __construct ( $id, $name ) {
$this->stu_id = $id;
$this->stu_name = $name;
}
}
$stu1 = new Student(100,'李寻欢');
此时,需要注意,实例化时,类名后,可以增加括号,取决于,该对象的构造方法,是否需要参数,如果不需要,则可以省略,或者是一个空括号。如果需要则一定有括号,括号内是实参列表
$stu1 = new Student;
$stu2 = new Student();
// 需要参数
$stu2 = new Student('1000','阿飞');
构造方法的兼容性问题
PHP5,构造方法的名字, 就是 __construct(); 在php5之前,构造方法名字与类同名。为了兼容,也同时支持这个与类同名的构造方法
class Student {
public function Student ( $id, $name ) {
}
}
如果同时出现,如何处理
先写__construct(); 后写Student;
找,__construct.
先写Stuendt(); 后写__construct();
找 __construct. 有一个错误提示。
常见的写法:
public function __construct ( $id, $name ) {
$this->Student($id,$name);
}
public function Student ( $id,$name ) {
$this->stu_id = $id;
$this->stu_name = $name;
}
构造是这个对象来的时候的方法,对象实例化类的时候,才形成的。
如果某些属性,在对象出现时,可以被设置为某些特定的值。就可以在声明类时,为声明的属性设置默认值
如果没有定义__construct()可以不用执行
但是一旦定义了构造方法,那么构造(实例化)的过程,一定要包括这个调用构造方法的过程(构造方法一定会执行)。
析构
在对象消失(对象被销毁时),也会自动执行一个方法,称之为析构方法。
析构方法名字为 __destruct();
也会自动被调用。完全取决于这个对象是否被销毁。
该方法,用于释放对象所占用的额外资源。并不是释放对象本身.(对象本身,是由垃圾回收机制去销毁) 不是对象本身的内存空间。
public function __destruct () {
// 释放资源
mysql_close();
}
什么情况下,对象会被销毁:
脚本周期结束,自动销毁,几个对象销毁几次。
-
销毁保存该对象的变量时:
unset($stu1);
-
保存对象的变量,被赋值了其他数据。 任何新值都可以,甚至是原来类的新对象。都会导致原对象被销毁。常见的使用是null.表示销毁对象的含意
$stu = null;
对象间的copy&clone
对象间的复制
支持引用传递,不用 & 符号, 因此不能通过 = 赋值的形式,得到一个新的对象。
克隆 clone
利用已有的对象,得到相同的新对象。
需要使用关键字 clone 完成。
新对象 = clone 旧对象.
常见的操作,在克隆对象时,需要对对象的某些特殊属性进行修改。意味着,在克隆的时,需要做一些特殊的处理。
使用 在克隆时,自动调用的方法, __clone() 来实现。
自动使用克隆出来的对象,来调用这个__clone()方法,意味着,该方法内的$this,表示新对象;
public function __clone () {
// $this 指向克隆对象
$this->stu_id = '1000';
}
$stu3 = clone $stu1;
PHP中得到新对象两个方法
实例化(通过类构造对象) ,需要使用构造方法对对象进行初始化。
克隆(通过对象克隆新对象) ,得到一个属性值与克隆的就对象一模一样的对象,但是,可以通过__clone 方法进行修改。
魔术方法
特定的情况下,会被自动调用
__ 魔术方法
静态成员
场景: 学生类,每个学生是一个类的对象,需要统计,当前已经存在几个学生?
如何定义这个计数器?
不能直接用属于对象的属性,每个对象所独有的。
应该找一个对象所共有的数据。
构造方法静态局部变量,也是不行,原因析构时不能使用。
public function __construct () {
$this->count++;
static $count = 0;
$count ++;
echo $coun;
}
public function __destruct () {
$this->count--;
$count--;
}
应该找一个能够被对象所共有的数据并且能够在多个方法内使用的变量。
使用全局变量即可,在方法内,是可以通过$GLOBALS访问到全局变量。
public function __construct () {
$GLOBALS['count']++;
}
public function __destruct () {
$GLOABLS['count']--;
}
全局变量不应该属于任何的对象或者类.$count 与 类 没有丝毫的逻辑上的联系.
应该找一个能够被对象所共有并且能够在多个方法内使用的变量,还应该与当前的对象类有逻辑的关系的数据?
可以使用类的静态成员.
静态成员,指的是逻辑上被所有对象所共享,属于类的成员称之为类的静态成员。
静态属性和静态方法
保存数据的是静态属性,执行功能的是静态方法
静态属性
使用static 关键字声明的属性。
该静态属性,在逻辑上,是定义在类上面的属性。(与定义对象上的属性对应).
public static $stu_count = 0;
保证一个类,对应一个属性,意味着,该类的所有对象共用这个属性。
访问静态属性
通过类来访问,再利用静态访问符号(双冒号::,范围解析操作符)
类::成员
Student::$stu_count++;
:: 的访问称之为静态访问,相对的箭头(->)称为非静态访问(对象访问)
var_dump(Student::$stu_count);
在访问时,如果是在类内访问:则可以使用self关键字,来代替当前类.
self::$stu_count++;
注意,$this和self的区别
$this表示的是这个对象。 $this->
self表示类自己。 self::
parsent当前父类 parent::
PHP还支持的写法:
对象也支持静态访问,但是需要使用静态访问符号。容易造成混淆,不建议经常使用。
$stu3::$stu_count;
静态方法
静态方法的逻辑意义,也是定义在类上的方法。同样,调用形式,也是通过类:: 来访问
定义:
public static function sayCount () {
echo 'run';
}
访问:
Student::sayCount();
静态方法和非静态方法的主要区别,在于是否可以接收一个对象的执行环境
就是是否可以为方法内的$this 是否可以被赋值
只有在对象调用方法时,才能将对象的执行环境传递到方法内,$this才可以被赋值.
Class Student {
public static $stu_count = 0;
public static function sayCount () {
var_dump($this); //报错
echo 'run';
}
}
$stu1 = new Student();
Student::sayCount();
无论静态和非静态方法,都是一个函数,找到他并执行即可。
一个静态方法,只能处理静态数据(静态属性)
public static function sayCount () {
echo self::$stu_count;
echo 'run';
}
PHP中特殊的方法访问问题
静态方法应该类来调用,非静态方法,应该对象调用。
但是:
无论静态还是非静态,都可以使用类来访问。
不过如果类静态调用一个非静态方法,会报告一个strict standards 语法不标准的错误。
对象也可以都调用
Class Student {
public function fn1 () {
echo 'fn1';
}
public static function fn2 () {
echo 'fn2';
}
}
$stu1 = new Student();
Student::fn1();
Student::fn2();
$stu1->fn1();
$stu1->fn2();
区别就在于$this上
只有在使用对象调用非静态方法时,才可以使用方法内的$this;
静态方法,无论如何也不能对$this做处理
而非静态方法,只有确定了对象,才能确定$this的值
类常量
类中,保存运行运行周期内,不变的数据。就是常量。
定义
const 关键字
const 常量名 = 常量值
没有访问修饰限定符.
const PI = 3.14;
访问
类::常量名
把属性值,固定值,定义成常量。
self::PI;
常见,类中 属性的选项, 多定义成 常量。
类中可以定义的成员一共有
常量,静态属性,静态方法,非静态属性,非静态方法
除了以上5个,类中,不能直接出现其他语句,例如echo的执行性语句,var_dump(), 等等
简化一点:常量,属性,方法
$this 表示当前对象
永远表示$this所在类的对象么?
不是,因为$this的值,不取决于$this所在的类,而是$this所在方法在被调用时执行对象(执行环境)。
方法的执行环境(context),当前方法是在哪个对象的环境下执行,方法在哪个对象的环境下执行,该方法内的$this 就表示哪个对象。
执行环境向下环境.
继承和重写
继承
一个对象拥有或者使用另一个对象的成员信息,称之为这个对象继承自另一个对象。
PHP中,通过在类上,使用特殊的操作达到目的。
继承是对象的概念,只不过语法上面要通过类来实现。
通过在在定义类时,利用extends 来指明当前类的对象 , 继承那个类的对象。
class C {
public $p_c = 'value C';
}
class D extends C {
public $p_d = 'value D';
}
$d = new D();
var_dump($d->p_d);
var_dump($d->p_c);
继承,指的是两个对象之间,那么哪有这两个对象?
class C {
public $p_c = 'value C';
}
class D extends C {
public $p_d = 'value D';
}
$d = new D();
var_dump($d instanceof D); // true
var_dump($d instanceof C); // true
相关概念
class D extends C {}
D 类对象 继承自 C 类对象
父类:被继承的类,C类
子类:需要继承的类,D类
功能上:
基类:C类是D类的基类
扩展类:D类是C类的扩展类
继承概念体现在对象上,语法体现在类上。
语法意义就是,面向对象语法中的,代码的重用
PHP是单继承
单继承,一个类只能继承自另一个类,不能同时继承多个类。但是一个类可以被多个类继承。
继承的目的:
在于扩展,或使用某个类已经存在的操作和数据。
继承,在编程上,可以是理解成OOP代码的共用。
重写override
只有发生在继承上,才会出现,是一个现象。
如果子类,与父类,出现同名的成员(属性方法),则在实例化子类对象时,只会得到子类中定义的成员,称之为重写。
成员冲突
继承时,如果发生成员冲突,PHP的处理方式,为重写。就是子类同名成员会覆盖父类的同名成员。不能看到父类的成员。
class P {
public $name = 'p';
public function sayNmae () {
echo 'parent::name', $this->name;
}
}
class C extends P {
public $name = 'C';
public function sayName () {
echo 'self::name',$this->name;
}
}
$obj = new C();
echo $obj->name; // C
echo '<br/>';
$obj->sayName(); // self::nameC
某些情况下,重写时一定发生的,例如构造等。
如果需要,强制执行被重写的父类方法,可以显式的使用父类来调用相应的父类方法即可。
public function __construct () {
P::__construct();
}
可以使用一个关键字,在类内,代替当前类的父类。
parent 关键字,代替父类。
一旦重写,父类代码就不会在执行了。
P::__construct();
parent::__construct();
如果说父类的构造需要相应的参数,则需要将再调用时,将父类的构造方法需要的参数,也传递到方法内。
class GoodsBook extends Goods {
public $page;
public function __construct ( $name, $price, $pages ) {
parent::__constuct($name, $price);
$this->pages = $pages;
}
}
一般2到3层的继承,就基本上可以
instanceof 操作符
用于判断一个对象是否是某个类的实例
$this的确定
只有在使用对象调用非静态方法时,才可以使用$this!
哪个对象调用方法,方法内$this就是那个对象。
对象环境,是可以向下传递。
如果当前方法内,已经确定了对象环境。在该方法内,如果出现了静态调用非静态方法,此时当前的对象环境,会传递到静态方法调用的非静态方法中。
class A {
public function in_a () {
var_dump($this);
}
}
class B {
public function in_b () {
var_dump($this); // B 实例化对象
echo '<br/>';
A::in_a(); // B 实例化对象
}
}
$obj = new B();
$obj->in_b();
PHP连接MySql
设置属性和连接方法
构造方法内,使用数组作为参数,如果参数过多,便于管理。
利用参数,为对象属性进行赋值
常见的,在实际操作中:为属性设置默认值。如果用户在实例化时,传递了属性参数,则使用用户传递的,否则使用默认值。
// $params Array
public function __construct ( $params ) {
$this->host = isset($params['host']) ? $params['host'] : '127.0.0.1';
$this->port = isset($params['port']) ? $params['port'] : '3306';
$this->user = isset($params['user']) ? $params['user'] : 'root';
$this->pass = isset($params['pass']) ? $params['pass'] : '';
$this->charset = isset($params['charset']) ? $params['charset'] : 'utf8';
$this->dbname = isset($params['dbname']) ? $params['dbname'] : '';
$this->prefix = isset($params['prefix']) ? $params['prefix'] : '';
}
连接
应该在构造方法内完成
要求,一个功能,尽量使用一个方法独立完成,一个大的功能,是由多个小功能组合起来。
// 连接数据库
public function connect () {
mysql_connect("$this->host:$this->port",$this->user,$this->pass);
}
为当前的功能类,增加一个属性,保存这个链接资源
// 运行时生成的信息
public $link;
在连接成功后,为属性赋值
连接失败,给出提示
public function connect () {
if ( $link = mysql_connect("$this->host:$this->port",$this->user,$this->pass) ) {
$this->link = $link;
} else {
echo '连接失败';
exit;
}
}
设置字符集,提取执行SQL
设置字符集
// 设置字符集
public function setCharset () {
$query = "set names $this->charset";
if ( mysql_query($query) ) {
// 成功
} else {
// 失败
echo '字符串设置失败';
exit;
}
}
执行sql语句
// 执行SQL语句
public function query ( $query ) {
if ( $reslut = mysql_query($query,$this->link) ) {
// 执行成功
return $reslut;
} else {
// 执行错误 ,给出提示
echo 'SQL执行错误,错误信息:<br/>';
echo '出错的SQL为:', $query ,'<br/';
echo '错误的代码为:',mysql_error($this->link), '<br/>';
echo '错误的信息为:', mysql_error($this->link), '<br/>';
// 简单错误处理,一旦出错,直接停止掉脚本执行
die;
// 如果在完整的处理过程中, 出错应该余下代码继续执行
// 需要判断是否执行成功 ,返回false
return false;
}
}
<?php
class MYSQLDB {
public $host; //主机
public $port; //端口
public $user; //用户名
public $pass; //密码
public $charset; //字符集编码
public $dbname; //默认数据库名
public $prefix; //表名前缀
//运行时,生成的信息。
public $link;
// 构造方法
// @param $params array 关联数组
// array('host'=>'127.0.0.1','port','3306','user',);
public function __construct( $params ){
//对属性初始化
//如果配置项选项存在,则使用用户的,否则使用默认的。
$this->host = isset( $params['host'] ) ? $params['host'] : '127.0.0.1' ;
$this->port = isset( $params['port'] ) ? $params['port'] : '3306';
$this->user = isset( $params['user'] ) ? $params['user'] : 'root';
$this->pass = isset( $params['pass'] ) ? $params['pass'] : '';
$this->charset = isset( $params['charset'] ) ? $params['charset'] : 'utf8';
$this->dbname = isset( $params['dbname'] ) ? $params['dbname'] : '';
$this->prefix = isset( $params['prefix'] ) ? $params['prefix'] : '';
//连接
$this->connect();
//设置字符集
$this->setCharset();
//选择默认数据库
$this->selectDB();
}
// 连接数据库服务器
public function connect(){
if( $link = mysql_connect("$this->host:$this->port",$this->user,$this->pass) ){
//连接成功
$this->link = $link;
} else {
//连接失败
echo '连接失败 !';
exit;
}
}
// 设置字符集
public function setCharset(){
$query = "set names $this->charset";
$this->query($query);
}
// 选择默认数据库
public function selectDB(){
$query = "use $this->dbname";
$this->query($query);
}
// 执行SQL语句
// @param $query string 需要执行的SQL语句.
// @return mixed 成功返回资源结果集或者true. 失败返回false.
public function query( $query ){
if( $result = mysql_query($query,$this->link) ){
//执行成功
return $result;
} else {
//执行失败
//给出错误提示
echo 'SQL执行出错,错误信息如下:<br />';
echo '出错的SQL语句为:',$query,'<br />';
echo '错误的代码为:',mysql_errno($this->link),'<br />';
echo '错误的信息为:',mysql_error($this->link),'<br />';
//简单错误处理,一旦出错,直接停止脚本执行.
die;
//如果再完整的处理过程中,出错应该余下的代码继续执行。
//需要判断是否执行成功,返回false.
// return false;
}
}
}
$db = new MYSQLDB( array('host'=>'127.0.0.1','port'=>3306,'user'=>'root','pass'=>'') );
var_dump($db);
?>
访问控制说明
访问修饰限定符
public , protacted, private
用于描述,一个成员(属性,方法)在哪里才能被访问的。
php5中,要求所有的成员(属性和方法)都应该受访问修饰的控制,声明时,前面都应该存在访问修饰限定符
存在例外,为了兼容.
在声明属性时,可以使用特殊的 var 关键字.相当于使用public来声明.
class Student {
var $stu_id;
}
方法中,声明时,可以省略访问修饰限定符.相当于public.
class Student {
function sayCount () {}
}
public ,公共的,成员在类内,继承链上类内,和类外都可以访问到(任何地方)
protacted , 保护的,就是类内和继承链上的类内 都可以访问.
private , 私有的, 类内.
PHP是采用类的概念,进行成员的限制访问的。
PHP将访问的代码,分成三大区域:类内,类外,继承链上的类内。
类内: 定义该成员所在类的内部.
继承链上的类内
类外
类外访问:只有公共可以访问
类内访问:类中所定义的方法内
所有的限定都可以
继承链上的类内:
保护的和公共的可以被访问,而私有的不可以被访问.
根据:成员在哪里定义 与 成员在哪里访问 来决定 类内 , 类外 还是 继承链上的类内。
注意
受保护,继承链上的类内
父类内定义,子类内访问. 反过来,子类内定义父类内可以访问.
私有成员的问题
出现在 私有属性 的重写上
PHP会记录所有的私有属性,同时会记录私有属性所在的类,在不同类内访问不同的私有属性时是不同的。而, 公共的和 受保护的, 则只会记录一次,明显的被重写了;
写继承的时候,如果是有属性被重写了,要注意到,当前所访问的私有属性是哪一个私有属性.
私有成员不能被重写。意味着,在相应的私有属性定义在类中,才能访问到相应的私有属性。
建议是:如果需要通过继承,就使用保护的,少用私有的。在没有继承时,尽量使用私有的。
重写的问题,要先明确访问的究竟是哪里所定义的。
在重写时,如果重写成员的访问级别不一致。子类的级别比父类的级别,相等或者弱,可以。强,不行。
封装
怎么使用访问修饰,如何选择
原则:尽量提高,类对其成员的控制能力. 能用private,尽量使用private,能用protected就不用public.
一个原则,尽量体现封装性。封装性,指的是,尽量隐藏内部实现,而仅仅开发外部操作接口!
语法上,就是,将不需要外部使用的属性,方法,都私有化(保护化),而仅仅留下一些必要的公共方法!
选择访问修饰限定符,体现oop思想的封装性的特点.
封装性:隐藏对象的内部实现细节,只提供外部操作方法。
对象的外部操作方法,称之为对象的接口(interface). 接口在语法上,就是一些公共的方法.
封装:只有操作接口可以被看到,内部实现都被隐藏.
经典的:几乎所有的属性和大部分的方法都是私有(如果有继承的话,会有受保护).只有一些供外部调用者使用的方法,是公共的.
类: 继承和实例化
类: 调用其静态成员
类: 作为其他类的基础类,被继承
两大功能:实例化对象, 基础类被继承
抽象类: 只能被继承,不能被实例化对象
final类: 只能被实例化,不能被继承
final类
该类,只能被实例化对象不能用于被继承。
设计时,该类不能再扩展,就应该通过语法,final限制,其它用户扩展该类.
最终,在继承链条上, 最末的一个类,其下不能再出现子类,意味着不能被继承。
定义
在 class 前 加 final关键字,即可.
final class GoodsBook extends Goods {}
如果继承该类,会报错
因此,final 类的作用,是在语法上,人为的限定哪些类不能被继承.
final 关键字的另一个用法, 用于限制方法,在所属类,被继承时,该方法不能被重写.
// 所有商品输出价格方式一致
final public function sayPrice() {
echo '¥',$this->shop_price;
}
抽象类abstract
有一种类,只能被继承,不能被实例化对象。原因就是这个类的定义不完整. (有了一半的藏宝图,需要找到其它的藏宝图,补充完整) 为什么会不完整? 因为PHP支持定义一种,只有方法的声明部分,而没有方法的实现部分的不完整方法。(类所包含的元素,不完整,可以不完整的是方法)。
如果某个类,包含了这种不完整的方法,就不是一个完整的类,就不能实例化对象!
不完整的类, 称之为 抽象类. abstract class
所包含的不完整的方法,称之为:抽象方法. abstract method
定义
包含了抽象方法的类,就是抽象类
语法
定义抽象方法,利用一个叫 abstruct 的关键字,告知PHP某个方法时一个抽象方法,没有方法体
abstract public funciton sayName(); // 要注意分号
定义一个抽象类
如果一个类包含了抽象方法,也就是抽象类,因此也需要使用 abstruct 关键字声明.
没有实例化的能力
实例化报错
只有被继承的能力
如果继承某个抽象类的类是非抽象类的话,就一定要将不完整的抽象方法实现。否则该类也应该是一个抽象类.
抽象类的具体功能
1:限制子类的结构
由于抽象类,只能被继承,而且如果其子类不是一个抽象类的话,要求必须实现抽象类所定义的抽象方法,因此抽象类的功能也可以理解成,用于限制其子类(扩展类)的结构.
就可以保证,同一系列的处理类,所拥有的结构是一致的。将来可以实现无缝对接,任意切换(热插拔) 。
2:可以限制
方法名,参数个数.
访问修饰控制.
扩展类(子类)的权限限制,弱于抽象类
抽象类功能
在可以为子类(扩展类)提供共用操作的同时,限制子类(扩展类)所拥有的方法的结构.
牺牲掉了实例化对象的功能.
接口 interface
一个对象的封装,分为两大部分
内部实现
就是操作接口
PHP中,可以规定,一个对象应该具有哪些公共的外部操作,使用interface来规定
公共的方法就是接口
用于规定一个对象应该拥有哪些公共的操作方法(接口),这个东西也叫接口(公共操作方法的集合).
接口(interface 结构,公共方法的集合)
公共方法(接口方法)
定义:用于限定某个对象所必须拥有的公共操作方法的一种结构,称之为接口(interface).
就是用于限制公共方法。
语法:
定义这个接口结构,使用interface 关键字.接口定义的都是公共的方法
interface 接口名 {
公共操作方法列表
}
interfece I_Goods {
public function sayName();
public function sayPrice();
}
注意:
接口方法,必须都是公共的
接口内只能有公共方法,不能存在成员变量属性
interfece I_Goods {
public $goods_name; //报错
public function sayName();
public function sayPrice();
}
接口内,只能含有未被实现的方法,也叫抽象方法,但是不用abstract关键字().
如果是一个完整方法,会报错。
使用接口
限制对象的必要的公共操作
类实现接口 (实现--把你想做的事完成). 利用关键字, implements;
class Goods implements I_Goods {}
这样,实现该接口的类,必须实现接口内所有的抽象方法。而且可以肯定,该方法一定是公共的外部操作方法
// 定义接口
interface I_Goods {
public function sayName();
public function sayPrice();
}
// 实现接口
class Goods implements I_Goods {
public $goods_name;
public $shop_price;
public function __construct ( $name, $price ) {
$this->goods_name = $name;
$this->shop_price = $price;
}
public function sayName () {
echo $this->goods_name;
}
public function sayPrice () {
}
}
$goods = new Goods('php', 234);
$goods->sayName();
$goods->sayPrice();
类似于抽象类,比较与接口的区别:
抽象类与普通类之间是继承
关系
普通类继承抽象了,第一可以得到抽象类中的已有的常规成员,第二才需要实现抽象方法(也不一定是public)
接口与普通类之间是实现
关系
普通类实现了接口,只能将其没有实现的公共方法实现
接口只用于定义 公共的方法。 而抽象类,什么都可以有。
多实现
上面的功能,理论上讲,可以通过抽象类完成。 但是抽象类,不专业
接口专业体现在实现上,因为PHP支持多实现,而仅支持单继承.
class Goods implements I_Goods, I_Shop {
}
继承和实现区别
继承:体现的是将别的对象已经存在的东西,拿来自己用
实现:将某个地方的规定,自己来完成
接口之间,也可以继承
interface I_Shop extend I_Goods {
public function saySafe();
}
抽象类 pk 接口
继承 pk 实现
PHP对接口的支持,可以定义常量
interface I_Shop extend I_Goods {
const PAI = 3.14;
}
通过类去访问
class Goods implements I_Shop {}
echo Goods::PAI;
辨别
接口是不是类 ?
接口不是类,接口就是独立的一种结构,适用于限制类的结构。但是可以理解成"接口类似于一个所有的方法都是公共的抽象方法,而且没有其它成员的抽象类",并不是一个概念。
class_exists(); 判断一个类是否存在?
继承应用
表操作模型
项目中操作数据库的典型模式: 数据操作模型(model).
常见的操作
PHP项目中,由于不同的数据实体对应的业务逻辑是不同的,通常的操作数据的方法:为每张表创建一个操作对象
class BaseTable {}
class GoodsTable extends BaseTable {}
class CrtegoryTable extends BaseTable {}
如果两个类有具体的关系,使用继承.
聚合
如果是多个类需要使用另一个类,所提供的功能,还有一种方式,称之为聚合.
class MySQLDB {
public function query () {
}
}
class BrandTable {
public $db;
public function __construct () {
$this->db = new MysqlDB();
$this->db->query();
}
}
$brand = new BarndTable();
$brand->db;
如果,BrandTbale 类 对象,需要使用MySQLDB类对象的相应功能,可以BrandTable对象内,增加一个属性保存MySQLDB类对象。打到 BarndTable使用MySQLDB 对象的功能目的
这种方式,称之为 对象的聚合。
既然聚合
和 继承
都可以达到 共用一个类功能的目的,使用的标准
如果说两个类之间存在逻辑关系上的必然联系,就建议使用继承。
反之,如果是逻辑上,一个对象使用另一个对象,此时应该使用对象间的聚合。
优劣性
使用继承,可以增加类之间的联系,增加关联性。
而使用聚合,可以降低类之间的关联性,可以降低耦合性!
自动加载机制
保存类的方法:
将类的定义代码,放置在一个独立的文件中. 如果需要该类,则直接调用.
为了识别,某个类定义文件,常见的,类定义文件的命名方式为:
类名.class.php
使用时引入:
require('./MySQLDB.php');
思考:
如果项目中,多处使用OOP,导致整个项目中,出现多个 xxx.class.php 文件,每当需要都需要引入 ?
使用自动加载,按需引入类文件
需要解决的问题:
1,什么时候需要类?
实例化时,静态调用时,一个类被继承时如果使用其子类时。
2,在需要时,应该主动require 系列的代码, 才可能加载成功,如何做到需要自动执行某段代码?
3,需要的是类,而载入的是文件,需要解决如何通过 类 来确定 类文件的所在文件.
PHP解决了前2个问题,需要解决第三个问题。
PHP提供了一个机制(功能),可以在需要某个类,但是没有载入这个定义类时.会自动调用某个函数。并同时将当前所需要的类名,作为参数传递到这个自动调用的函数内。
PHP提供一个时机,完成自动加载。
这个函数默认不存在,需要用户自己定义。 函数名为 __autoload();
function __autolaod ( $class_name ) {
var_dump($class_name);
require $class_name.'.class.php';
}
一旦函数存在,在特定情况(需要类但是没有找到),则PHP内部会自动调用__autoload().执行在函数体定义的函数,从而完成加载类所在的文件。
分工:
用户脚本:定义函数,接收需要的类名作为参数,函数需要实现通过类名找到类所在的文件,将其载入即可!
PHP核心(Zend Engine):判断是否需要类,并且判断该类是否已经载入了,如果需要但是没有载入,则尝试调用用户定义的__autoload()函数,并将需要的类名传递即可!
__autoload 函数名得到是类名
而载入的是类文件定义的文件。
因此,自动加载机制的前提:必须要能够通过类名,推导出,类所在的文件才可以。(定义类文件时,要有规律)
注意:
PHP提供了加载代码的执行时机。用户脚本,需要完成加载代码,其中核心任务是,通过所得到的类名,找到类定义所在的文件。
对象序列化
序列化,反序列化(串行化,反串行化)
数据持久化
在数据保存,面临一个问题:数据格式。
PHP的数据有八种类型之多,文件只能存储字符串。
一旦数据类型不是字符串类型。就会导致数据不能原样保存,不能取得原始数据。
如何解决
任何形式的数据,都可以存储到文件中,并且,在取出来时,原样得到数据。
在保存,与读取时,对数据进行转换与反转换。
序列化
serialize();
原始数据转成能够记录的原始数据信息的字符串
反序列化
unserialize();
通过序列化的字符串结果,将原始数据还原!
只有在涉及到,数据需要被存储,或者 传输时, 需要对数据进行序列化。
注意的数据类型都可以被序列化与反序列外。资源是例外!
对象的序列化与反序列化
在对象被反序列化时,需要找到当前对象所属的类才可以被完美的反序列化,如果不能找到属于的类,那么会变成php内置类:__PHP_Incompleta_Class(不完整类)的一个对象.
因此,只要在反序列话之前,将类载入即可。
反序列化,也会触发自动加载机制。
在序列化时,可以自定义需要序列化的属性名单!
通过对象的特殊的方法 __sleep();
该方法会在对象被序列化时,自动调用,不需要参数,返回一个数组,每个元素,表示一个属性名,数组内存在的属性名,就会被序列化。反之则不会被序列化。
// 在序列化时被调用
// 用于负责指明那些属性需要被序列化
// @return array
public function __sleep () {
return array('host', 'port', 'user', 'pass', 'charset', 'dbname');
}
在反序列化时,可以自动再次执行某些代码,从而完成某些资源的初始化!
通过 对象方法:__wakeup()方法
会在 对象被反序列过程中自动调用,所负责的功能,执行反序列话(醒来之后)的初始化工作!
// 在反序列化时调用
// 用于 对对象的属性进行初始化
public function __wakeup () {
// 连接数据库
$this->connect();
// 设置字符集
$this->setCharset();
// 设置默认数据库
$this->selectDB();
}
PHP 自动调用, 用户脚本只需要定义。在特定的功能调用特定的方法。称之为:魔术方法。
单例模式
单例,单个实例。设计模式的一种,通过一种语法的设计,保证类(对象)上具有某些特征,这样的一种语法实现,就可以称之为设计模式。
其中,设计可以保证一个类,有且只有一个对象,这种设计模式就称之为单例模式。
单例模式的作用,单例的设计,是适用于使用一个对象可以完成所有业务逻辑的类
典型的实现,在类上,做限制:三私一公。
某个类只要实例化一次就可以完成所有的业务逻辑!
但是实例化多次也可以。
显然,应该保证对象被实例化一次,从而节约资源空间!
MySQLDB类,如何设计,从而达到保证该类 有且只有一个对象.
原因:使用者,可以无限次的去实例化 这个类。
三私一公
1:限制,用户无限制执行new
在实例化的过程中,需要执行构造方法,如果执行不成功,那么实例化过程失败。因此,将构造方法,私有化(保护化),可以保证在类外进行实例化的工作都会失败。
class MySQLDB {
private function __construct () {
}
}
2,类,不能实例化了,不能再类外实例化,可以在类内实例化,类内执行new操作。
因此:在类中,增加一个公共的静态的方法,在方法内,执行new实例化类对象。
class MySQLDB {
// 私有化构造函数
private function __construct () {
}
// 执行new
public static function getInstance () {
return new MySQLDB();
}
}
$db1 = MySQLDB::getInstance();
$db2 = MySQLDB::getInstance();
var_dump($db1, $db2);
3,可以无限制的调用getInstance得到任意多个对象!但是此时得到对象都需要经过 getInstance,可以在getInstance内,增加限制,使用户得到对象的改变!
在实例化好对象后,将对象存起来,下次再执行时,判断是否已经有保存的,有了,直接返回已经存在的,没有,实例化后,保存起来,再返回!
增加一个私有的静态属性,在 getinstance内做判断
class MySQLDB {
// 判断是否实例化过
public static $getinstance;
// 私有化构造函数
private function __construct () {
}
// 执行new
public static function getInstance () {
// 没有实例化
if ( !(self::$getinstance instanceof self) ) {
self::$getinstance = new self;
}
return self::$getinstance;
}
}
4:还可以通过克隆来得到新对象
需要私有化__clone()方法
// 私有化 __clone 方法
private function __clone () {
}
注意:单例模式传参问题
在项目的设计层面解决单例的问题
增加一个实例化的方法(函数)
作用:用户如果想要得到单例对象,就通过调用该函数完成!
function getInstace ( $class_name ) {
static $objects = array();
if ( !isset($objects[$class_name]) ) {
$objects[$class_name] = new $class_name;
}
return $objects[$class_name];
}
优点:灵活,可以针对多个类同时实现单例,而且,类还可以回归到非单例的效果。
重载overload
属性重载 __set() __get()
传统意义的重载,一个方法的多种状态,通常使用,参数类型或者参数个数进行区分,决定当前使用那个函数。
PHP不支持同名方法。
PHP的重载:指的是对不可访问的成员的操作。
不可访问的成员: 指的是 不存在 , 或者是,由于访问修饰控制访问不到的。
// PHP允许为对象增加类中没有定义的属性
class Student {
public $stu_name;
private $stu_id;
}
$stu = new Student();
var_dump($stu);
echo '<br/>';
$stu->stu_gender = 'male';
var_dump($stu);
// PHP对属性除了增加,还支持删除
class Student {
public $stu_name;
private $stu_id;
}
$stu = new Student();
$stu->stu_gender = 'male';
unset($stu->stu_name);
var_dump($stu);
对类后续进行操作,用户可以操作对象任意的修改对象的结构。
PHP如何处理上面的情况,称之为重载(属性的重新加载,成员的重新加载).
重载:对成员的重新加载
成员分为:属性和方法,因此,php支持属性重载 和 方法重载.
属性重载
PHP对,不可访问的属性进行操作处理方法,称之为属性重载
PHP需要通过支持 魔术方法完成。
PHP在处理重载属性时,支持使用4个魔术方法,完成处理属性重载的情况.
__set()
当为不可访问的属性赋值时,会被自动调用
会得到两个参数,当前操作的属性名,和属性值!
// $p_name string 属性名
// $p_value mixed 属性值
public function __set ( $p_name, $p_value ) {
// 实现, 处理情况
return ;
}
典型的,__set()的作用, 用于严格控制对象结构,和批量处理可以被修改的属性。
// $p_name string 属性名
// $p_value mixed 属性值
public function __set ( $p_name, $p_value ) {
// 实现, 处理情况
// 不可增加任何属性, 但是可以对 age 和 gender 做出修改
$allow_preperties = array('s_age', 's_gender');
if ( in_array($p_name, $allow_preperties) ) {
$this->$p_name = $p_value; // 利用可变属性,属性名由变量代替
}
}
$s = new Student();
$s->s_name = '天机老人';
$s->s_age = 87;
__get()
当访问不可访问的属性时,会被自动调用
需要的参数是:一个,为当前操作的属性名
public function __get ( $p_name ) {
// 读取名字 性别
if ( $p_name == 's_name' || $p_name == 's_gender' ) {
return $this->$p_name; // 可变属性
} else {
// 读取年龄
if ( $this->s_gender == 'male' ) {
return $this->s_age;
}
}
}
__unset()
在删除一个不可访问的属性时,可以自动被调用!
需要一个参数 当前操作的属性名。
此时就可以利用业务逻辑完成 属性的删除处理!
public function __unset ( $p_name ) {
if ( $cound ) {
unset($this->$p_name);
}
}
__isset()
在判断一个不可访问的属性是否存在时,被自动调用
需要一个参数,属性名
注意,次函数需要返回true,或者false,表示属性是否存在
public function __isset ( $p_name ) {
if ( $p_name == 's_gender' ) {
return true;
} else {
return false;
}
}
方法重载
__call()
当访问一个不可访问的对象方法时,会触发__call()的魔术方法!
需要的参数时:
2个参数,第一是当前的方法名,第二是调用时使用的实参列表!
public function _call ( $m_name, $m_args ) {
var_dump($m_name, $m_args);
}
$s = new Student();
$s->sayName('zf', 'php');
典型应用
1,给出友好提示
2,执行默认操作
public function _call ( $m_name, $m_args ) {
var_dump($m_name, $m_args);
echo '执行了默认操作';
$this->default_action();
}
public function default_action () {
echo '这里是操作';
}
$s = new Student();
$s->sayName('zf', 'php');
static __callStatic()
与 __call类似,当静态调用一个不可访问的方法时,会自动执行!
public static function __callStatic ( $m_name, $m_args ) {
var_dump( $m_name, $m_args );
}
Student::sayCount();
魔术方法
magic method
在特定的情况下,会被自动调用的方法,通常负责完成某块独立的功能的方法称之为魔术方法!
特点:1,需要用户脚本定义,不定义不执行!2,命名方式都是以__开头
__invoke()
将一个对象,当作函数调用时,会触发该对象的__invoke()方法,由此方法,就可以调用,没有该方法就不能调用!
__invoke是PHP实现匿名函数 不可或缺的部分!
也可以传递参数,为对象传递参数,就是为invoke魔术方法传递参数!
Closure 类的对象(匿名函数,闭包函数) 可以当作函数调用,为什么我们自己定义的对象不可以当做函数调用? 内置的Closure 类中有 __invoke();方法.
在将一个对象当做函数调用时, 会触发该对象的 __invoke 方法, 如果方法不存在,对象不能当做函数调用。反之 如果需要调用这个对象, 则可以为该对象增加 __invoke 方法使之执行.
调用对象,其实就是调用该方法,可以接受参数!普通方法一样处理即可.
魔术方法,只会负责某个特定的功能。当某个特别的情况发生时,才会调用相应的魔术方法。
魔术方法可以没有, 一旦有的话就会被调用.
__toString()
转换到字符串的意思!
当将对象当作字符串使用时,会自动调用该对象的魔术方法!
存在toString 魔术方法,即可以完成转换。
toString的返回值,就是转换的结果,一般转换对象的标志性的属性即可!
静态延迟绑定
$this永远代表所在类的对象?
不是
$this的值取决于$this所在方法的执行对象环境.
self用于代表所在类么?
是,永远代表所在类的对象!
self 永远表示所在的类.
在那个类中被定义,就应该表示那个类.
因为self,parent关键字,类的编译阶段就确定了所代表的类.
class P {
public static $where = 'P static, <br/>';
public static function sayWhere () {
echo self::$where;
}
}
class C extends P {
public static $where = 'C static';
}
echo P::sayWhere(); // P static,
echo C::sayWhere(); // P static,
此时,应该是表示当前类的关键字,最好应该在调用时决定最好!(self不能做到)
此时,采用一个新的关键字,代表当前类,与self不同,在于是 运行时调用时决定,而不是在类编译时就确定好了的!
使用关键字:static
class P {
public static $where = 'P static, <br/>';
public static function sayWhere () {
echo self::$where;
}
public static function sayW () {
echo static::$where;
}
}
class C extends P {
public static $where = 'C static,';
}
echo P::sayWhere(); // P static,
echo C::sayWhere(); // P static,
echo '<br/>';
echo P::sayW(); // P static,
echo C::sayW(); // C static,
static关键字的功能
声明静态局部变量
声明静态成员
当前类,运行的当前类
类中,可以表示类的关键字:
self, 所在类
static,调用类
parent, 父类
反射机制
常见的类和对象操作函数:
instanceof,判断对象是否是某个类的实例。
get_class(); // 返回对象的类名
class_exists(); // 判断一个类是否存在
get_declared_classer() // 获得当前已经存在的类. 包括预定义和用户自定义的。
method_exists();
property_exists();
相应的魔术常量__CLASS__
, 当前类名__METHOD__
, 当前方法名
get_class_methods(); // 得到类的方法
get_class_vars(); // 得到类的属性
可以得到类内的方法和属性。
但是,得到的信息较少,只有名字,而且受到访问修饰符的限制。
如何才能够详细精确到类中的结构?
使用到反射机制
PHP提供的,用于获得某个类,或者某种结构(类,函数,方法,属性,参数,扩展....);
需要使用反射机制,反射机制时利用OOP的语法实现了。
反射机制提供了很多类,针对不同的结构,会使用到不同的反射类对象。
reflection: 反射类. 可以通过对象反过来获得对象的相关信息.
如果需要知道某个类的结构,就应该使用refletionClass
存在一个静态方法,refletionClass::export() 可以导出一个类的结构报告
反射机制可以获得目标结构的内部结构。
并同时可以驱动目标结构运行起来.(代理执行);
<?php
class Persion {
public $name = 'zf';
public function say() {
echo 'say:'. $this->name, '<br/>';
}
public function tan($c1, $c2) {
echo '<br/>' . $c1 . ' '. $c2;
}
}
$p = new Persion();
// $p->say();
// 利用反射对象调用方法
$method = new ReflectionMethod('Persion', 'say'); // 反射方法对象
$method->invoke($p);
$method2 = new ReflectionMethod('Persion', 'tan'); //参数的反射方法
$method2->invokeArgs($p, array('pink', 'red'));
?>
类型约束
约束函数,或者方法类参数的类型,只能是某个类的对象。
PHP是弱类型,变量可以存储任意类型的数据
函数,方法的参数也是可以接受任意类型
但是参数,可以被规定为,某个类的固定对象,在参数前增加类名即可。
function sayName ( Student $o ) {
echo $o->stu_name;
}
sayName('php');
支持类名,和数组。其它数据类型不支持。
function sayName ( Array $arr ) {
}
对象的遍历
对象是一个集合数据类型
foreach遍历
遍历对象,是依次获得对象拥有的属性的信息(注意,访问修饰符的限制)
class Student {
public $stu_name;
public $stu_age;
public $stu_gender;
}
$obj = new Student();
$obj->stu_name = '李寻欢';
$obj->stu_age = 30;
$obj->stu_gender = 'male';
foreach ( $obj as $val => $key ) {
var_dump($val,$key);
echo '<br/>';
}
自定义遍历,iterator
iterator迭代器接口
Iterator接口,PHP预定义完成。
实现Iterator接口
// 类实现Iterator 接口
class Team implements Iterator {
public function rewind () {
reset($this->stu_infos);
}
public function valid () {
return key($this->stu_infos) !== null;
}
public function current () {
return current($this->stu_infos);
}
public function key () {
return key($this->stu_infos);
}
public function next () {
next($this->stu_infos);
}
}
魔术常量__CLASS__
:当前类名。注意:可以new self 不可以 new __CLASS__
__METHOD__
,当前方法名。区别__FUNCTION__
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。