面向对象思想的核心概念

PHP的魔术方法

__construct方法:PHP中没有构造方法的重载

__destruct方法:析构方法允许在销毁一个对象之前执行一些特定操作,例如关闭文件、释放结果集等

当堆内存段中的对象失去访问它的引用时,就不能被访问了,也就成为垃圾对象了。通常对象的引用被赋予其他的值或者是在页面运行结束时,对象都会失去引用

访问属性:使用下面的方法之后,访问不存在的属性时不会报错

__set方法:void __set(string name, mixed value)  第一个参数需要传入在为私有属性设置值时的属性名,第二个参数则需要传入为属性设置的值。这个方法不需要主动调用,可以在方法前面也加上private修饰,防止用户直接调用。这个方法是在用户值为私有属性设置值时自动调用的

__get方法:这两个方法是用来完成对所有私有属性都能获取和赋值的操作

__isset方法:用来检查私有属性是否存在的方法

__unset方法:用来删除对象中私有属性的方法

<?php
class Account {
    private $user = 1;
    private $pwd = 2;

    public function __set($name, $value) {
        echo "Setting $name to $value \r\n";
        echo "<br>";
        $this -> $name = $value;
    }

    public function __get($name) {
        if (!isset($this->$name)) {
            echo "未设置";
            echo "<br>";
            $this -> $name = "正在设置默认值";
        }
        return $this->$name;
    }
}

$a = new Account();
echo $a -> user;
echo "<br>";
$a -> name = 5;
echo $a -> name;
echo "<br>";
echo $a -> big;

?>

//1
Setting name to 5 
5
未设置
Setting big to 正在设置默认值 
正在设置默认值

访问方法:使用下面的方法之后,访问不存在的方法不会报错

__call方法

mixed __call(string $name, array $arguments)
//当调用一个不可访问的方法(未定义,或者不可见)时,__call()会被调用。其中$name参数是要调用的方法名称
//$arguments参数是一个数组,包含着要传递给方法的参数

//使方法的动态创建成为可能,在MVC等框架设计中是很有用的语法。假设一个控制器调用了不存在的方法,那么只要定义了__call魔术方法,就能友好地处理这种情况
<?php
class TestClass {
    function printHello(){
        echo "Hello<br>";
    }

    function __call($functionName, $args)
    {
        // TODO: Implement __call() method.
        echo "你所调用的函数: ".$functionName."(参数: ";
        print_r($args);
        echo ")不存在<br>\n";
    }
}

$obj = new TestClass();
$obj->myFun("one", 2, "three");
$obj->otherFun(8, 9);
$obj->printHello();
?>

 

__callStatic方法

 

__toString方法:是在直接输出对象引用时自动调用的方法

如果类定义了__toString方法,就能在测试时,echo打印对象体,对象就会自动调用它所属类定义的__toString方法,格式化输出这个对象所包含的数据

仍然可以用print_r和var_dump函数输出一个对象

直接echo一个对象就会报语法错误,而如果这个对象实现__toString方法后就可以直接输出==>echo本来可以打印一个对象,而且也实现了这个接口,但是PHP对其做了个限制,只有实现__toString后才允许使用

<?php
//声明一个测试类,在类中声明一个成员属性和一个__toString()方法

class TestClass {
    private $foo;

    function __construct($foo)
    {
        $this->foo = $foo;
    }

    public function __toString()
    {
        // TODO: Implement __toString() method.
        return $this->foo;
    }
}

$obj = new TestClass('Hello');
echo $obj;
?>

 

对象串行化

串行化  serialize()函数  参数为对象的引用名,返回值为一个对象被串行化后的字符串

反串行化  unserialize()函数  把对象串行化后转换的二进制字符串再转换为对象  参数为serialize()函数的返回值,返回值是重新组织好的对象

 

类的组合与继承

组合是指在一个类中创建另一个类的对象,并把后者作为前者的一个属性,并调用它的方法处理问题,这种复用方式叫“组合”

::操作符用于调用父类的方法,还用来作为类常量和静态方法的调用

低耦合指模块与模块之间,尽可能地使模块间独立存在;模块与模块之间的接口尽量少而简单

继承:

子类重写父类的方法:可以使用parent::方法名来获取父类的代码

 

常见关键字:

final

static  类名::静态属性名  类名::静态成员方法名

在类中声明的成员方法中,也可以使用self来访问其他静态成员,因为静态成员是属于类的,所以不能通过$this访问

self::静态成员属性名

self::静态成员方法名

如果在类的外部访问静态成员,可以使用类名或者对象名来引用

 

单态设计模式

<?php
/**
声明一个类Db,用于演示单态模式的使用
 */
class DB {
    //声明一个私有的、静态的成员属性$obj
    private static $obj = null;  
    
    //构造方法,使用private封装后则只能在类的内部使用new去创建对象
    private function __construct()
    {
        //完成数据库连接操作
        echo "连接数据库成功<br>";
    }
    
    //只有通过这个方法才能返回本类的对象
    static function getInstance(){
        if(is_null(self::$obj)) {
            self::$obj = new self();
        }
        return self::$obj;
    }
    
    //执行SQL语句完成对数据库的操作
    function query($sql) {
        echo $sql;
    }
}

$db = DB::getInstance();
$db->query("select * from user");
?>

 

const关键字:

在PHP中定义常量是通过调用define()函数来完成的,但要将类中的成员属性定义为常量,则只能使用const关键字。将类中的成员属性使用const关键字标识为常量,其访问的方式和静态成员一样,都是通过类名或者在成员方法中使用self关键字访问,也不能用对象来访问

使用const声明的常量名称前不使用$

instanceof关键字

 

克隆对象:克隆以后,原本和副本两个对象完全独立、互不干扰

<?php
class Person {
    private $name;
    private $sex;
    private $age;
    
    function __construct($name = "", $sex = "",$age=1)
    {
        $this->name = $name;
        $this->sex = $sex;
        $this->age = $age;
    }
    
    function say(){
        echo "我的名字: ".$this->name.", 性别: ".$this->sex.", 年龄: ".$this->age."<br>";
    }
}

$p1 = new Person("张三", "男", 20);
$p2 = clone $p1;
$p1 -> say();
$p2 -> say();
?>

<?php
//如果需要对克隆后的副本对象在克隆时重新为成员属性赋初值,则可以在类中声明一个魔术方法“__clone()”
class Person2 {
    private $name;
    private $sex;
    private $age;

    function __construct($name = "", $sex = "",$age=1)
    {
        $this->name = $name;
        $this->sex = $sex;
        $this->age = $age;
    }
    
    //声明此方法则在对象克隆时自动调用,用来为新对象重新赋值
    function __clone(){
        $this->name = "我是". $this->name. "的副本";
        $this->age = 10;
    }

    function say(){
        echo "我的名字: ".$this->name.", 性别: ".$this->sex.", 年龄: ".$this->age."<br>";
    }
}

$p1 = new Person("张三", "男", 20);
$p2 = clone $p1;
$p1 -> say();
$p2 -> say();
?>

 

抽象类与接口

abstract function fun1();
abstract function fun2();

//只要在声明类时有一个方法是抽象方法,那么这个类就是抽象类,抽象类也要使用abstract关键字修饰。在抽象类中可以有不是抽象的成员方法和成员属性,但访问权限不能使用private关键字修饰为私有的
抽象类中可以定义非抽象方法

//接口中不能声明非抽象方法,不能在接口中声明变量,只能使用const关键字声明为常量的成员属性,而且接口中的所有成员都必须有public的访问权限

 

匿名类

<?php
$person = new class {
    function say() {
        echo "匿名类!";
    }
};

$person->say();
?>

<?php
$person1 = new class ('哈哈'){
    public $name;
    function  __construct($name) {
        $this->name = $name;
    }

    function say() {
        echo "匿名类: {$this->name}";
    }
};
$person1->say();
?>

//匿名类! 匿名类: 哈哈

 

<?php
class SomeClass {}  //声明一个类SomeClass作为父类
interface SomeInterface {}  //声明一个接口SomeInterface用匿名类实现
trait SomeTrait {}  //声明一个trait,导入到匿名类中


//使用var_dump()直接输出匿名类的对象
//声明一个匿名类,通过构造函数为成员属性$num赋初值
//声明匿名类时继承SomeClass类
//声明匿名类时实现接口SomeInterface
var_dump(new class(100) extends SomeClass implements SomeInterface {
    private $num;
    public function __construct($num)
    {
        $this->num = $num;
    }
    use SomeTrait;
});


?>

<?php
//匿名类还可以在一个类的内部方法中声明,当匿名类被嵌套进普通类后,不能访问这个外部类的
//private、protected方法或者属性。为了访问外部类protected属性或方法,匿名类可以继承此外部类
//为了使用外部类的private属性,必须经过构造器传进来

class Outer {
    private $prop = 1;
    protected $prop2 = 2;

    protected function func1(){
        return 3;
    }

    //声明一个外部类的成员方法,在方法返回内部匿名类对象
    public function func2(){
        //声明和返回匿名类对象
        //通过构造方法将外部类的私有成员,访问外部类的私有属性
        //通过继承外部类,访问外部类的私有成员
        return new class($this->prop) extends Outer {
            private $prop3;  //内部类的私有成员

            public function __construct($prop)
            {
                $this->prop3 = $prop;
            }

            public function func3() {
                return $this->prop2 + $this->prop3 + $this->func1();
            }
        };
    }
}

//访问内部匿名类实例中的func3()
echo (new Outer) -> func2() ->func3();
?>

 

多态:同一类的对象收到相同消息时,会得到不同的结果,而这个消息是不可预测的。多态,就是多种状态,多种结果

多态是一种通过多种状态或阶段描述相同对象的编程方式。它的真正意义在于:实际开发中,只要关心一个接口或基类的编程,而不必关心一个对象所属于的具体类

//通过判断对象的类属性实现多态
<?php class employee { protected function working() { echo '本方法需重载才能运行'; } } class teacher extends employee { public function working() { echo '教书'; } } class coder extends employee { public function working() { echo '敲代码'; echo "<br>"; } } function dprint($obj) { if (get_class($obj) == 'employee') { echo 'Error'; } else { $obj->working(); } } dprint(new teacher()); dprint(new coder()); dprint(new employee());
//通过接口实现多态

<?php
interface employee {
    public function working();
}

class teacher implements employee {
    public function working() {
        echo '教书';
    }
}

class coder implements employee {
    public function working()
    {
        echo '敲代码';
    }
}

function dprint(employee $i) {
    $i -> working();
}

$a = new teacher;
$b = new coder();
dprint($a);
dprint($b);

PHP中父类和子类存在继承关系,但不存在血缘关系,子类无法向上转型为父类。

 

面向接口编程

接口本身什么也不做,系统在内部实现了接口的行为,所以只要实现了这个接口,就可以使用接口提供的方法,这就是接口“即插即用”思想

//Traits和接口很像,不同的是Traits可以导入包含代码的接口,Traits和接口都是对“多重继承”的一种变相实现
<?php
trait Hello {
    public function sayHello() {
        echo 'Hello';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o -> sayHello();
$o -> sayWorld();
$o -> sayExclamationMark();
?>

 

<?php
trait DemoTrait {
    public $property1 = true;
    static $property2 = 1;
    function method1() {
        //codes
    }
    abstract public function method2();
}

/**
trait的基本使用:Trait不能通过它自身来实例化对象,必须将其混入类中使用。相当于将Trait中的成员复制到类中,在应用类时就像使用自己的
 * 成员一样
 */
trait Demo1_trait {
    function method1() {

    }

    function method2() {
        
    }
}

class Demo1_class {
    use Demo1_trait;
}

$obj = new Demo1_class();

$obj->method1();
$obj->method2();

?>
<?php

trait Demo1_trait {
    function func() {
        echo "第一个Trait中的func方法";
    }
}

trait Demo2_trait {
    function func() {  //两个同名方法有冲突
        echo "第二个Trait中的func方法";  
    }
}

class Demo_class {
    use Demo1_trait, Demo2_trait {
        Demo1_trait::func insteadof Demo2_trait;  //Demo2_trait中声明的在这里声明使用Demo1_trait的func替换
    }
}

$obj = new Demo_class();

$obj->func();

?>

为了对使用的类施加强制要求,Trait支持抽象方法的使用。如果在Trait中声明需要实现的抽象方法,这样就使得使用它的类必须先实现它

 

注意:

  • Trait会覆盖调用类继承的父类方法
  • 从基类继承的成员被Trait插入的新成员所覆盖。来自当前类的成员覆盖了Trait的方法,而Trait则覆盖了被继承的方法
  • Trait不能像类一样使用new实例化对象
  • 单个Trait可由多个Trait组成
  • 在单个类中,用use引入Trait,可以引入多个
  • Trait支持修饰词,例如final、static、abstract
  • 可以使用insteadof及as操作符解决Trait之间的冲突
  • 使用as语法还可以用来调整方法的访问控制

 

 

反射

面向对象编程中对象被赋予了自省的能力,而这个自省的过程就是反射

动态获取信息以及动态调用对象方法的功能称为反射API

<?php
class person {
    public $name;
    public $gender;
    public function say() {
        echo $this->name, "\tis", $this->gender, "\r\n";
    }
    public function __set($name, $value) {
        echo "Setting $name to $value \r\n";
        $this -> $name = $value;
    }
    public function __get($name) {
        if (!isset($this -> $name)) {
            echo '未设置';
            $this -> $name="正在设置默认值";
        }
        return $this -> $name;
    }
}

$student = new person();
echo $student -> name;
$student -> name = "tom";
$student -> gender = "male";
$student -> age = 24;
$student -> say();
echo $student -> name;
print "<br>";

//获取对象属性列表
$reflect = new ReflectionObject($student);
$props = $reflect->getProperties();
foreach ($props as $prop) {
    print $prop -> getName()."\n";
    print "<br>";
//    print "<br>";
}
print "<br>";

//获取对象方法列表
$m = $reflect->getMethods();
foreach ($m as $prop) {
    print $prop->getName()."\n";
    print "<br>";

}

//也可以使用class函数,返回对象属性的关联数组以及更多的信息
//返回对象属性的关联数组
var_dump(get_object_vars($student));
//类属性
var_dump(get_class_vars(get_class($student)));
//返回由类的方法名组成的数组
var_dump(get_class_methods(get_class($student)));

//获取对象属性列表所属的类
echo get_class($student);

------------------------------
Setting age to 24 tom ismale tom
name 
gender 
age 

say 
__set 
__get 
array(3) { ["name"]=> string(3) "tom" ["gender"]=> string(4) "male" ["age"]=> int(24) } array(2) { ["name"]=> NULL ["gender"]=> NULL } array(3) { [0]=> string(3) "say" [1]=> string(5) "__set" [2]=> string(5) "__get" } person
//反射获取类的原型
$obj = new ReflectionClass('person');
$className = $obj -> getName();
$Methods = $Properties = array();
foreach ($obj -> getProperties() as $v)
{
    $Properties[$v->getName()] = $v;
}
foreach($obj->getMethods() as $v){
    $Methods[$v->getName()] = $v;
}

echo "class {$className}<br>{<br>";
is_array($Properties)&&ksort($Properties);

foreach($Properties as $k => $v) {
    echo "\t";
    echo $v -> isPublic()? 'public' : '', $v -> isPublic()? 'private':'', $v -> isProtected()? 'protected':'',
    $v->isStatic()? 'static' :' ';
    echo "{$k}<br>";
}

echo "\n";
if(is_array($Methods)) ksort($Methods);
foreach($Methods as $k => $v) {
    echo "function{$k}(){}<br>";
}

echo "}<br>";

 

异常和错误处理

<?php
class emailException extends exception {

}

class pwdException extends exception {
    function __toString()
    {
        return "<div class = \"error\">Exception{$this->getCode()}:
        {$this->getMessage()}
        in File:{$this->getFile()} on line:{$this->getLine()} </div>";
        //改写抛出异常结果
    }
}


//异常分发
function reg($reginfo=null){
    if (empty($reginfo) || isSet($reginfo)) {
        throw new Exception("参数非法");
    }
    if (empty($reginfo['email'])){
        throw new emailException("邮件为空");
    }
    if ($reginfo['pwd'] != $reginfo['repwd']) {
        throw new pwdException("两次密码不一致");
    }
    echo "注册成功";
}

//对异常进行分拣并做处理
try {
    reg(array('email'=>'waitfox@qq.com', 'pwd'=>123456, 'repwd'=>12345678));
} catch(emailException $ee) {
    echo $ee -> getMessage();
} catch(pwdException $ep) {
    echo $ep;
    echo PHP_EOL, '特殊处理';
} catch(Exception $e) {
    echo $e->getTraceAsString();
    echo PHP_EOL, '其他情况,统一处理';
}
<?php
    try{
        //可能出错的代码段
        if (文件上传不成功) throw(上传异常);
        if (插入数据库不成功) throw(数据库操作异常);
    } catch(异常){
        必须的补救措施,如删除文件、删除数据库插入记录
    }
?>



<?php
    上传{
        if(文件上传不成功) throw(上传异常);
        if(插入数据库不成功) throw(数据库操作异常);
    }

    //其他代码...
    try{
        上传;
        其他;
    } catch(上传异常) {
        必须的补救措施,如删除文件,删除数据库插入记录
    } catch(其他异常){
        记录log
    }
?>

 

错误处理机制

PHP里有一套错误处理机制,可以使用set_error_handler接管PHP错误处理,也可以使用trigger_error函数主动抛出一个错误

set_error_handler函数设置用户自定义的错误处理函数,函数用于创建运行期间的用户自己的错误处理方法。它需要先创建一个错误处理函数,然后设置错误级别

PHP程序的错误发生一般归属于:语法错误、运行时错误、逻辑错误

 

trigger_error()函数和die()函数

//set_error_handler()函数设置用户自定义的错误处理函数。函数用于创建运行期间的用户自己的错误处理方法。它需要先创建一个错误处理函数,然后设置错误级别
set_error_handler(error_function, error_types)
error_function:规定发生错误时运行的函数
error_types:规定在哪个错误报告级别会显示用户定义的错误,默认为“E_ALL

 

命名空间

解决重名问题。命名空间将代码划分出不同的区域,每个区域的常量、函数和类的名字互不影响

在命名空间里,define的作用是全局的,const则作用于当前空间

//独立的命名空间使用namespace关键字声明
<?php
namespace MyProject;


?>

//namespace需要写在PHP脚本的顶部,必须是第一个PHP指令(declare除外)

<?php
namespace MyProject1;

const TEST = 'this is a const';

function demo(){
    echo "this is a function";
}

class User {
    function fun() {
        echo "this is User's fun()";
    }
}

echo TEST;
demo();

namespace MyProject2;

const TEST2 = "this is MyProject2 const";
echo TEST2;

//调用MyProject1空间中的demo函数
\MyProject1\demo();

//使用MyProject1空间中的类实例化对象
$user = new \MyProject1\User();
$user->fun();

?>

命名空间的子空间和公共空间

 

posted @ 2020-06-24 10:30  LinBupt  阅读(220)  评论(0编辑  收藏  举报