什么是面向对象?
面向对象是一种先进的编程模型,相对于面向过程编程,面向对象更具有逻辑性,使程序代码更加的健壮、易于维护和好扩展。被称为 OOP编程(Object Oriented Programming)。
面向对象具有封装、继承、多态三大特性
1. 封装:
所有的常量、变量和函数都要写在类的内部,归属于这个类,代码不能写在类的外部。
注意:因为PHP是从面向过程发展过来的,所以PHP里面支持两种写法,PHP变量、函数等等代码也可以写在类的外面,但如果是一个门纯的OOP的语言,比如Java,所有代码必须写在类里面,任何东西不能放在类的外面。
2. 继承:
一个类A可以继承自另一个类B,继承之后,这个类A【一般被称为子类】中就拥有了那个B类【一般被称为父类】中的属性和方法。
注意:比如写了一个类实现一个功能,然后想添加更多新功能,那么可以直接再定义一个子类继承自这个类,然后在子类中添加新功能,这样在不修改原代码的基础上添加新功能。
3. 多态:
同一个类的子类中的同一个方法会表现出不同的行为,比如:人都会走路,但不同的人走路的姿势是不一样的,这里的人就是父类,子类是不同的人,虽然都有同一个“走路”的方法,但不同的人走路的方式是不一样。
类
语法定义
在面向对象中所有的代码都要封装在一个类中。
定义类的语法:
<?php
class 类名
{
代码;
}
?>
类名由数字、字母和下划线组成,不能使用数字开头。
业内惯例:类名首字母大写。后面的单词首字母大写。
<?php
/**
* UserInfo类
*/
class UserInfo
{
//.... 这里书写代码
}
?>
类的常量、属性和方法
常量
在类中值不会改变的量,可以定义成类常量,类常量不需要使用 $ 符。
<?php
/**
* Math数学类
*/
class Math
{
// 定义常量 不需要使用$,并且以后值不能改变
const PI = 3.1415926;
}
// 调用语法
echo Math::PI;
?>
属性
在类中可以定义变量,习惯叫它为类的属性。
定义属性时前面要加上public、protected或者private。如果没有加PHP认为是public的。
<?php
/**
* People类
*/
class People
{
public $username;
protected $age;
private $salary;
}
?>
注意:也可以使用var,这个是PHP4时候的写法,相当于 public。
<?php
/**
* People类
*/
class People
{
var $username;
var $age;
private $salary;
}
?>
注意:定义属性的时候可以进行初始化赋值操作。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
}
?>
方法
类中也可以定义函数,称为类的方法。
定义函数前面也要加上public、protected或者private。如果不加认为是public的。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
}
}
?>
对象
一个类是不能使用的,如果要使用类里面的代码,必须要先new一个对象出来,然后使用这个对象来调用里面的代码。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
}
}
$p1 = new People();
var_dump($p1->username);
?>
类和对象的关系
类相当于图纸,用来描述都有哪些功能和特征,对象就是有真正有这些功能和特征的物品。
一个类可以生成多个对象,每个对象是彼此独立的个体,互不影响。
$this
在类中可以使用一个特殊变量$this,代表调用这个方法的那个对象。
简单点说:类里面要使用类中的属性必须使用$this就这样。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
return $this->username; // $this代表当前对象
}
}
$p1 = new People();
var_dump( $p1->getName() );
?>
对象默认都是按引入传递的【不需要加&】
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
return $this->username; // $this代表当前对象
}
}
$p1 = new People();
$p2 = $p1; // 指向的是同一个地址空间
$p2->username = 'p2Username';
var_dump($p1->username);
?>
注意:
如果就想要拷贝一个新的出来而不是引入同一个:可以使用clone来克一个新对象
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
return $this->username; // $this代表当前对象
}
}
$p1 = new People();
$p2 = clone $p1; // 复制一个对象,指向的不是同一个地址空间
$p2->username = 'p2Username';
var_dump($p1->username);
?>
构造函数和析构函数
每一个类都有两个特殊的函数,这两个函数会自动在两个时刻调用。
构造函数
每次new一个新对象时,类中的构造函数就自动被调用了。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
public function __construct()
{
echo 'i am coming....';
}
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
return $this->username; // $this代表当前对象
}
}
$p1 = new People();
var_dump($p1->username);
?>
注意:
构造函数的参数:在构造函数中可以设置参数,如果设置了参数,那么在new时必须要把参数加上,如果不传会报错。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
public function __construct($username)
{
echo 'i am coming....';
}
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
return $this->username; // $this代表当前对象
}
}
$p1 = new People();
var_dump($p1->username);
?>
构造函数可以设置默认值。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
public function __construct($username = 'default')
{
echo 'i am coming....';
}
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
return $this->username; // $this代表当前对象
}
}
$p1 = new People();
var_dump($p1->username);
?>
析构函数
当一个对象被删除时,被自动调用的函数。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
public function __construct($username = '')
{
echo 'i am coming....';
}
// 注意:这里没有加上 public 相当于 public function getAge
function getAge(){
}
public function getName()
{
return $this->username; // $this代表当前对象
}
public function __destruct()
{
echo 'i am destory';
}
}
$p1 = new People();
unset($p1);// 消耗对象
echo 'last';
?>
继承-extends
一个类可以使用extends继承自另一个类,继承之后就拥有了父类中的非私有的常量、属性和方法,一个类只能有一个父类。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
public function __construct($username = '')
{
echo 'i am coming....';
}
}
/**
* Male类继承People类
*/
class Male extends People
{
public function getInfo()
{
echo 'i am male';
}
}
?>
重写
子类中可以定义一个一样的东西就覆盖了父类。
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
public function __construct($username = '')
{
echo 'i am coming....';
}
public function getUserName()
{
return $this->username;
}
}
/**
* Male类继承People类
*/
class Male extends People
{
// 重写
public function getUserName()
{
echo 'i am getUserName';
}
public function getInfo()
{
echo 'i am male';
}
}
?>
重载
PHP所提供的"重载"(overloading)是指动态地"创建"类属性和方法。是通过魔术方法(magic methods)来实现的。
当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。
父类方法调用
子类中可以明确指定调用父类的方法:使用parent关键字
<?php
/**
* People类
*/
class People
{
var $username = 'andy';
var $age = 12;
private $salary = 10000;
public function __construct($username = '')
{
echo 'i am coming....';
}
public function getUserName()
{
return $this->username;
}
}
/**
* Male类继承People类
*/
class Male extends People
{
public function getInfo()
{
parent::getUserName();
echo 'i am male';
}
}
?>
继承中的构造函数
如果父类中有构造函数,然后子类又有一个构造函数,就会导致子类的覆盖父类的,导致父类的构造函数无法执行。
解决办法:一般子类中如果写了构造函数那么必须先调用父类的构造函数。
<?php
/**
* People类
*/
class People
{
public $username = 'andy';
private $salary = 10000;
public function __construct($username)
{
$this->username = $username;
}
public function getUserName()
{
return $this->username;
}
}
/**
* Male类继承People类
*/
class Male extends People
{
public function __construct($username){
parent::__construct($username);
}
public function getInfo()
{
return $this->username;
}
}
$m = new Male('li xiaoming');
var_dump( $m->getInfo() );
?>
final关键字
可以把final放到类和方法的前面:代表这个类或者方法不能被继承。加在类前面:这个类不能被继承。
<?php
final Class A{
}
// 出错
Class B extends A{
}
?>
访问控制
在类中可以使用public、protected和private来控制属性和方法的访问权限:
public
公开的:所有位置都可以直接访问。
protected
受保护的:只有当前类和子类中可以访问。
private
私有的:只有当前类中可以访问。
static 静态
在类中可以使用static定义一个静态属性和静态方法。
普通属性和静态属性的区别
- 语法区别
- 静态变量属于这个类,所以不需要new对象可以直接使用类名访问
- 普通变量属于一个具体对象,每new出一个对象,里面都有一个这个属性,必须new出来的对象,所以需要先new一个对象才能使用
<?php
/**
* Static学习
*/
class Test
{
public static $count = 0;
}
echo Test::$count;
?>
静态属性
静态属性可以被继承,只属于这个类,跟具体new出来的对象没关系,可以直接使用类名来操作。
<?php
/**
* Static学习
*/
class Test
{
public static $count = 0;
}
/**
* B
*/
class B extends Test
{
}
echo B::$count;
?>
静态方法
静态方法属于一个类不属于任何对象, 所以不需要new可以直接调用。
<?php
/**
* Static学习
*/
class Test
{
public static $count = 0;
public static function getCount()
{
return 'count';
}
}
echo Test::getCount();
?>
注意:
在静态方法中不能访问普通属性。
<?php
/**
* Static学习
*/
class Test
{
public static $count = 0;
public $number = 12;
public static function getCount()
{
return $this->number;
}
}
echo Test::getCount();
?>
self
在类内部如果要访问自己的静态属性或者方法时可以使用self。
<?php
/**
* Static学习
*/
class Test
{
public static $count = 0;
public $number = 12;
public static function getCount()
{
return self::$count;
}
}
echo Test::getCount();
?>
后期绑定,也叫作晚绑定
代码
<?php
class A{
public function abc()
{
echo 'abc in A Class';
}
public function bcd()
{
$this->abc();
}
}
class B extends A{
public function abc()
{
echo 'abc in B Class';
}
}
$b = new A();
$b->bcd();
// 1. 先定义一个 A ,定义 abc
// 2. 在定义一个 B,定义 abc
// 3. 让B继承A,这个时候 B的 abc会覆盖 A的abc
// 4. 在A里面在定义 bcd
// 5. 实例化 A类,调用 bcd,则输出什么?
// 6. 那么实例化 B类,调用 bcd,则输出什么?
?>
总结:$this是取决于谁调用,是在真正运行的时候才知道的。不是事先定义好的(在编译的时候已经固定了),将来看怎么运行的,是属于后面的才知道,称为绑定。那么什么是早绑定呢?
后期静态绑定
self-早绑定
self是早绑定的,self写到哪个类里就代表哪个类,无论是哪个对象调用。
<?php
class A{
public static function abc()
{
echo 'abc in A Class';
}
public function bcd()
{
self::abc(); // 已经固定,在编译的时候就确定 self 代表A,无论是哪个类的对象调用,都是A。
}
}
class B extends A{
public static function abc()
{
echo 'abc in B Class';
}
}
$b = new B();
$b->bcd();
// 1. 先定义一个 A ,定义 abc
// 2. 在定义一个 B,定义 abc
// 3. 让B继承A,这个时候 B的 abc会覆盖 A的abc
// 4. 在A里面在定义 bcd
// 5. 实例化 A类,调用 bcd,则输出什么?
// 5. 那么实例化 B类,调用 bcd,则输出什么?
?>
static-晚绑定
为了给静态方法也提供一个晚绑定的功能,所以PHP从5.3开始出了一个static关键字可以用来晚绑定来调用一个静态方法
<?php
class A{
public static function abc()
{
echo 'abc in A Class';
}
public function bcd()
{
static::abc(); // static 是静态方法的晚绑定,有运行时候决定
}
}
class B extends A{
public static function abc()
{
echo 'abc in B Class';
}
}
$b = new B();
$b->bcd();
// 1. 先定义一个 A ,定义 abc
// 2. 在定义一个 B,定义 abc
// 3. 让B继承A,这个时候 B的 abc会覆盖 A的abc
// 4. 在A里面在定义 bcd
// 5. 实例化 A类,调用 bcd,则输出什么?
// 5. 那么实例化 B类,调用 bcd,则输出什么?
?>
parent-早绑定
如果要调用父类中的静态方法
<?php
class A{
public static function abc()
{
echo 'abc in A Class';
}
}
class B extends A{
public static function abc()
{
echo 'abc in B Class';
}
public function bcd(){
parent::abc(); // 调用父类的静态方法,属于早绑定
}
}
$b = new B();
$b->bcd();
?>
总结:
- self和parent是静态绑定,是早绑定。
- $this和static是晚绑定 :主要要看是哪个对象调用的。
参数的类型约束
可以规定一个函数上的参数必须是某种类型的,目前只能做这四种的类型约束:类、数组、接口或者callable。
<?php
class Test{
public function init(A $a)
{
var_dump($a);
}
}
class A{
}
$a = new A();
$t1 = new Test();
$t1->init($a);
?>
注意:
类约束时可以传类和这个类子类的对象都可以,比如下面这个例子:要求传一个Animal类的对象。但传了一个Dog类的对象也可以,因为Dog是Animal的子类。
魔术方法
我们在类中定义好这些方法之后,在某一个特定时刻自动被调用的方法。比如构造函数是在new一个对象时被自动调用。
__get和__set
当要读取和设置一个对象中不存在的属性时该方法就会被调用了。
类的自动加载
PHP中提供了一个函数__autoload也是一个魔术方法,当使用一个类时,如果这个类找不到那么这个方法就被调用。
__autoload
<?php
function __autoload($className)
{
var_dump($className);
}
$a = new A();
?>
现在我们用一个类时必须先把这个类所在的PHP文件引入进来这样比较麻烦,是否可以让PHP自动加载我们需要使用的类?
可以使用autoload实现:
- 一个文件中只写一个类
- 文件名和类名相同
- 定义一个autoload函数根据类名自动加载相应的类
<?php
function __autoload($className)
{
include $className . '.php'; // 完成类的载入
}
$a = new A();
?>
spl_autoload_register和spl_autoload_unregister
前面的__autoload很早以前就有比较旧,后来又提供了两个函数可以提供更加载的自动加载功能:
__autoload只能是这一个函数,使用spl_autoload_register注册多个自动加载函数。
注意:
加载函数也可以是一个类中的一个方法。
<?php
/**
* Loader加载器
*/
class Loader
{
/**
* Loader加载器
*/
public static function load($className)
{
include $className . '.php';
}
}
spl_autoload_register('Loader::load');
$a = new A();
?>
spl_autoload_unregister:删除一个已经注册的函数。
抽象方法和抽象类-abstract
抽象类:只要类中有一个抽象类方法那么这个类必须定义成抽象类。
抽象方法:先定义这个方法的名字和参数,但先不写具体里面的代码,这种抽象类必须被子类继承并且重写所有抽象类中的抽象方法然后才可以使用。
<?php
/**
* Db类是抽象类
1. 里面存在一个抽象方法
2. 类的前面必须加上 abstract
*/
abstract class Db
{
abstract public function init();
}
/**
* Mysql
*/
class Mysql extends Db
{
public function init()
{
echo 'mysql init';
}
}
$mysql = new Mysql();
$mysql->init();
?>
抽象语法:
1 . 如果一个方法前面加上abstract代表这是一个抽象的方法,抽象方法不能有方法体,只有方法名。
- 如果一个类中有抽象方法,那么这个类就是抽象类(未完成的类),所以类前面要加上abstract。
- 抽象类不能直接实例对象,必须被继承,子类只有重写了所有父类中的抽象方法之后这个子类才能使用。
- 如果子类没有重写父类中所有的抽象方法,那么这个子类也是抽象类。
接口
完全抽象的抽象类就是接口,类里全是抽象方法。
<?php
/**
* Db类里面的方法全部为抽象方法,则一般定义为接口,同时去除 abstract关键字,该为 interface
*/
<?php
/**
* Db接口
*/
interface Db
{
const PORT = 3306;
public function init();
public function open();
public function read();
public function write();
}
?>
使用interface定义接口,接口里面只有:
- 没有方法体的方法
- 常量
接口是不能用的,必须有一个类实现(implement)了这个接口,这个类才能使用。如果一个类要实现一个接口,那么这个类中必须重写接口中所有的方法。
<?php
/**
* Db接口
*/
interface Db
{
const PORT = 3306;
public function init();
public function open();
public function read();
public function write();
}
/**
* Mysql 实现Db接口
*/
class Mysql implements Db
{
public function init()
{
}
public function open()
{
}
public function read()
{
}
public function write()
{
}
}
?>
接口继承
一个接口可以继承另外一个接口,然后在添加抽象方法,等待其他类去实现。
<?php
/**
* Db接口
*/
interface Db
{
const PORT = 3306;
public function init();
public function open();
public function read();
public function write();
}
/**
* Mysql 继承Db接口,增加了一个sql注入处理
*/
interface Mysql extends Db
{
public function sqlInjectRemove();
}
?>
接口实现
<?php
/**
* Db接口
*/
interface Db
{
const PORT = 3306;
public function init();
public function open();
public function read();
public function write();
}
/**
* Mysql 实现Db接口
*/
class Mysql implements Db
{
public function init()
{
}
public function open()
{
}
public function read()
{
}
public function write()
{
}
}
?>
注意:
一个类可以同时实现多个接口。
<?php
/**
* Db接口
*/
interface Db
{
const PORT = 3306;
public function init();
public function open();
public function read();
public function write();
}
interface SecretDb{
// 过滤参数
public function filterParams();
}
/**
* Mysql 继承Db接口,增加了一个sql注入处理
*/
class Mysql implements Db,SecretDb
{
// 实现 SecretDb 接口
public function filterParams(){
}
// 实现 Db即可
public function init()
{
}
public function open()
{
}
public function read()
{
}
public function write()
{
}
}
?>
总结:
PHP中的类是单继承、多实现。
trait
trait是什么?
trait 和 类 是比较类似的,可以有自己的属性和方法,但是不能单独被使用实例化。主要用来扩展另外一个类的功能,在一个类中需要使用use来引入trait里面的功能。
定义
和类的编写方式一致。只是将 class 关键字换为 trait。
使用trait
在类中使用use来拥有trait里面所有的东西。
注意:
可以同时use多个trait。
trait方法的冲突
如果同时拥有的多个trait有同名的方法那么就冲突会报错。
trait属性的冲突
如果trait里面定义了一个属性那么类里面不能再定义同名属性,否则会出错。
使用trait时可以使用as把冲突的名字起个别名。
使用trait组成trait
在一个trait里面,可以使用其他的trait。
对象序列化
当要把一个对象持久化,保存到数据库、文件等等时需要先序列化,转化成字符串保存,取出来用时要先反序列化,把字符串转化回对象再使用。
- serialize:序列化
- unserialize:反序列化
匿名类
没有名字的类。
如果一个类只使用一次,那么可以不用定义这个类直接new一个匿名类即可。
异常处理
什么是异常处理
异常处理是在面向对象中新引入的处理错误的一种方式。当出错时程序throw一个Exception类的对象,然后在通过 try{代码块}catch(Exception){处理错误信息}finally{ }来捕获并处理错误。
在面向过程中我们每做一个操作都要根据返回值判断这个操作是否成功了,一般都是如下的编写处理方式:
<?php
@$link = mysql_connect('127.0.0.1', 'root', 'admin');
if(!$link){
exit('can not link mysql');
}else{
$status = mysql_selectdb('test');
if(!$status){
exit('can not open test datatbase');
}else{
//....
}
}
?>
面向对象中提供了一种新的报错(这个错误一般被称为异常)的语法:
<?php
/**
* File类,主要用于文件的操作
*/
class File
{
public $file;
public function open($fileName)
{
throw new Exception('file can not open');
}
public function write()
{
throw new Exception('file can not write');
}
public function close()
{
//file close
var_dump('file close');
}
}
$file = new File();
try {
$file->open('a.txt');
} catch (Exception $e) {
var_dump($e->getMessage()); // 获取错误信息
var_dump($e->getCode()); // 获取错误代码
var_dump($e->getFile()); // 获取出错文件信息
var_dump($e->getLine()); // 获取出错信息所在的行号
}
注意:
异常处理是新的处理错误的语法:只有面向对象的东西支持。
PHP中内置的Exception异常类
出错时所有throw的对象都这个类或者这个类的子类。
我们可以写自己的异常类扩展这个PHP中自带的Exception异常类。
<?php
/**
* Connect
*/
class ConnectException extends Exception
{
protected $messsage = '连接数据库失败';
}
class QueryException extends Exception
{
protected $messsage = '执行SQL语句失败';
}
/**
* Goods模型
*/
class GoodsModel
{
public function open()
{
if(!mysql_connect('127.0.0.1', 'root', 'admin88')){
throw new ConnectException();
}
if(!mysql_query('select * from test2')){
throw new QueryException();
}
}
}
?>
抛出错误:throw
当失败时就throw一个Exception类或者子类的对象,throw之后的代码就不再执行了。
<?php
class GoodsModel
{
public function open()
{
var_dump('first');
throw new Exception('连接数据库失败');
var_dump('second'); // 这里的代码不在执行
}
}
$goodsModel = new GoodsModel();
try {
$goodsModel->open();
} catch (Exception $e) {
var_dump($e->getMessage());
}
?>
捕获:try...catch[...catch....]
当发生异常的时候,然后使用catch捕获异常。
<?php
class GoodsModel
{
public function open()
{
var_dump('first');
throw new Exception('连接数据库失败');
var_dump('second'); // 这里的代码不在执行
}
}
$goodsModel = new GoodsModel();
try {
$goodsModel->open();
} catch (Exception $e) {
var_dump($e->getMessage());
}
?>
一定会执行:finally
finally代表是最终的意思,最后的代码一定会执行,例如我们打开一个文件后,无论是读写是否正常,最后一定都是需要关闭文件的。
<?php
/**
* File类,主要用于文件的操作
*/
class File
{
public $file;
public function open($fileName)
{
throw new Exception('file can not open');
}
public function write()
{
throw new Exception('file can not write');
}
public function close()
{
//file close
var_dump('file close');
}
}
$file = new File();
try {
$file->open('a.txt');
} catch (Exception $e) {
var_dump($e->getMessage());
} finally{
$file->close();
}