3.4、PHP面向对象常见关键字和魔术方法

16 篇文章 0 订阅
15 篇文章 0 订阅

1、final
final—用于类、方法前,不能用来标识成员属性。
final类—不可被继承。
final方法—子类中不可覆盖该方法。
eg1:

<?php
final class test{
	
}
//由于test类被final修饰,所以无法被继承
class test1 extends test{
	public function a(){
		echo 123;
	}
}

$a = new test1();
$a->a();//程序会报错Fatal error: Class test1 may not inherit from final class (test)...

eg2:

<?php
class test{
	public final function a(){
		echo 123;
	} 
}

class test1 extends test{
	//由于a方法在类test中被final修饰,所以无法在子类中被重写
	public function a(){
		parent::a();
		echo 222;
	}
}
$a = new test1();
$a->a();//报错,Fatal error: Cannot override final method test::a()...

2、static
首先解释下静态成员。
      一个类在被实例化多个对象时,一般的成员,在多个对象之间是相互独立的,但是静态成员不一样,多个对象之间,静态成员总是唯一存在的,且共享,甚至是没有实例化对象,也总是存在的。可以理解成静态成员是属于类的。
如下图,存储视图:
这里写图片描述
另外用static修饰的方法中,不能访问非静态成员,因为不能使用$this
静态成员的访问:
本类中访问:self::静态成员方法名();
                   self::静态成员属性名;
在类的外部:类名::静态成员方法名();
                   类名::静态成员属性名;

eg:

<?php
class test{
	static $a = 0;
	public function __construct(){
		self::$a++;
	}
}
new test();
new test();
new test();
print_r(test::$a);//输出3

3、单态设计模式
单态设计模式经常用在数据库连接和日志处理等情景。
单态模式又叫单例模式,其特点如下:
①一个类只能有一个实例对象存在,且无法在外部实例化,而是会向单例类内部存储的实例返回一个引用。
②节省内存,有些场景提高效率
关键因素:
①需要一个保存类的唯一实例的静态成员变量;
②构造函数和克隆函数须声明为私有的,防止外部实例化,而失去单态设计模式意义
③须提供一个公共的静态方法(通常为getInstance方法),在类内部实例化本类,从而返回唯一实例的一个引用 。

示例说明:在一个脚本中,执行多条sql时,我们不能就实例化一个数据库操作类对象,实例化一次就要连接数据库一次,这样做效率十分底下,而且耗资源。单态模式很好的解决了该问题,执行一次脚本,执行sql语句时,只会实例化一次操作数据库类对象。
eg:

<?php
class DB {
    private static $obj = null;           //声明一个私有的,静态的成员属性$obj
    private function __construct() {       //私有构造方法,只能在类的内部实例化对象
        echo "连接数据库成功<br>";
    }
    public static function getInstance() {  // 通过此静态方法才能获取本类的对象
        if(is_null(self::$obj))  //如果本类中的$obj为空,说明还没有被实例化过
            self::$obj = new self();  //实例化本类对象
            return self::$obj;  //返回本类的对象
    }
    public function query($sql) {  //执行SQL语句完成对数据库的操作
        echo $sql;
    }
}
    $db = DB::getInstance();        //只能使用静态方法getInstance()去获取DB类的对象
    $db -> query("select * from user");       //访问对象中的成员

4、const
const和define一样都是定义常量的,但是之间有一些使用上的区别
区别:
①使用const使得代码简单易读,const本身就是一个语言结构,而define是一个函数。另外const在编译时要比define快很多。
②const用于类成员变量的定义,一经定义,不可修改。Define不可以用于类成员变量的定义,可用于全局常量。
③const可在类中使用,define不能。
④const区分常量大小写,define可在参数中设置是否区分大小写
const使用注意事项
①常量前面没有美元符号($)
②使用 const 关键字定义常量必须处于最顶端的作用区域,因为用此方法是在编译时定义的。这就意味着不能在函数内,循环内以及 if 语句之内用 const 来定义常量。
eg1:

<?php
define("CONSTANT", 1 << 5);
const WAKAKA = 1;
var_dump(get_defined_constants(true));//返回所有常量的关联数组,键是常量名,值是常量值

eg2:

<?php
class test{
	const WAKAKA = 1;
	public function aaa(){
		echo self::WAKAKA;//在类中访问方式
	}
}
$a = new test();
$a->aaa();
echo test::WAKAKA;//类外部访问const定义的常量,类名::常量名

**5、instanceof **
instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例,也可用于确定一个变量是不是实现了某个接口的对象的实例。详细可参考官网:http://php.net/manual/zh/language.operators.type.php
eg:

<?php
class MyClass
{
}

class NotMyClass
{
}
$a = new MyClass;

var_dump($a instanceof MyClass);//输出true
var_dump($a instanceof NotMyClass);//输出false

6、克隆对象
克隆对象在https://blog.csdn.net/qq_21386275/article/details/79552106中有提到过,这里只介绍其魔术方法__clone,在使用clone关键字克隆对象时,会调用魔术方法__clone。下面用实例来示例该魔术方法的用处。
eg:

<?php
class test{
	private $name;
	private $age;
	
	function __construct($name="小白",$age=10){
		$this->name = $name;
		$this->age = $age;
	}
	
	function __clone(){
		$this->name = "这是".$this->name."副本";
	}
	
	function say(){
		echo "我的名字:".$this->name."<br/>";
		echo "我的年纪:".$this->age."<br/>";
	}
}

$a = new test();
$a->say();//输出 我的名字:小白 我的年纪:10
$b = clone $a;//重新初始化
$b->say();//输出:我的名字:这是小白副本 我的年纪:10

7、__toString()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
Warning
不能在 __toString() 方法中抛出异常。这么做会导致致命错误。

<?php
class Person{  
        private $name;  
        private $age;  
        public  $sex;  
        public function __toString(){  
            //这里也可以使用get_class函数获取类名  
            //$classname = get_class($this);  
            $classname = __CLASS__;  
            $method = __METHOD__ ;  
            return  "当前调用{$classname}类的{$method}方法,用户名:{$this->name},名字:{$this->age}";  
        }  
          
        public function __construct($name,$age){  
              
            $this->name = $name;  
            $this->age = $age;  
        }  
        private function __testMethod(){  
            echo "testMethod";  
        }   
    }  
    $a = new Person("小明","18");  
    $arry_methods = get_class_methods($a);  
    $arry_vars = get_class_vars(get_class($a));//参数是类名
	
    var_dump($arry_methods);//私有的方法 没有显示  
    var_dump($arry_vars);//私有的变量,没有显示 
    echo $a;//会调用 __toString方法
	
    //默认类对象可以调用var_dump函数 返回类的属性和属性值的数据  
    var_dump($a); 

8、__call()和__callStatic() 魔术方法
当我们调用对象中不存在的方法时,会退出程序,并出现系统报错,但当在类中添加了魔术方法__call,此时会自动调用__call方法。
__call魔术方法有两个参数:调用的不存在的方法名以及其参数
参数1,以字符串形式接收调用不存在的方法名。
参数2,以数组的形式接收调用不存在的方法的参数。
eg:

<?php
class Test 
{
    public function __call($name, $arguments) 
    {
        // 注意: $name 的值区分大小写
        echo "方法名 '$name' ";
		var_dump($arguments)."<br/>";
        
    }

    /**  PHP 5.3.0之后版本  */
    public static function __callStatic($name, $arguments) 
    {
        // 注意: $name 的值区分大小写
        echo "方法名 '$name' ";
		var_dump($arguments)."<br/>";
    }
}

$obj = new Test;
$obj->runTest(1,"two");

Test::runTest(1,"two");  // PHP 5.3.0之后版本

这里附加一个框架中很重要的概念:“连贯操作”
下示例中的__call并不是连贯操作的什么关键方法,只是利用了__call方法完成了一个比较好的示例,利用__call方法,省去了写field、where、limit等方法
eg:

<?php
    class Db{  
        //此属性定义要实现连贯操作的方法名  
        public $sql = array(  
            "field" => "",  
            "where" => "",  
            "limit" => "",   
        );  
  
        /** 
         * 连贯操作时,调用field() where() limit() 方法组合成sql语句 
         */  
        function __call($name,$args){  
            //把请求不存在的方法名,统一转为小写  
            $name=strtolower($name);
            if(isset($this->sql[$name])){  
                $this->sql[$name]=$args[0];  
            }else{  
                echo '调用类'.get_class($this).'中的'.$name.'()方法不存在';  
            }  
            //返回对象,从而可以继续调用本对象中的方法,形成连贯操作  
            return $this;//连贯操作核心
        }  
  
        /** 
         * 此方法终结了连贯操作,置于连贯操作的最后面
         */  
        function select(){
            $sql="SELECT {$this->sql['field']} FROM test {$this->sql['where']} {$this->sql['limit']}";  
            echo $sql;  
        }  
    }  
  
    $obj=new db();  
  
    $obj->field('name,sex,address')->where('where name="123"')->limit('limit 1')->select(); 

9、serialize()和unserialize()
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。

为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。

上诉都是官网的解释,把上面的话说的通俗一点,个人理解如下:
serialize()在序列化对象时,会保存对象的所有变量值,但不会保存对象中的方法,会保存类的名字。
unserialize()在反序列化时,个人理解是实际上还是会重新new一下这个类,但是会初始化重新new的对象中的变量值,就是serialize()保存的对象中的变量值。
eg:

//test01.php中的代码
<?php
class A {
	public $one = 1;

	public function show_one() {
		echo $this->one;
	}
	
	public function test(){
		echo 123;
	}
}

//test02.php中的代码,先执行该文件,再执行test03.php文件
<?php
include("test01.php");
$a = new A;
$s = serialize($a);
// 把变量$s保存起来以便文件test03.php能够读到,同级目录会看到store文件
file_put_contents('store', $s);

//test03.php中的代码
<?php
// 要正确了解序列化,必须包含下面一个文件
include("test01.php");
$s = file_get_contents('store');
$a = unserialize($s);
// 现在可以使用对象$a里面的函数 show_one()
$a->show_one();

__sleep和__wakeup魔术方法
__sleep 方法在一个对象被序列化的时候调用,__wakeup方法在一个对象被反序列化的时候调用。
__sleep不需要接受任何参数,但是需要返回一个数组,包含需要串行化的属性。
eg:

<?php 
class Test{   
    public $test1 = 'test1';    
    public $test2 = 'test2';
	public $test3 = 'test3';
    
    public function __wakeup(){
       echo '123<br />';    
    }
    
    public function __sleep(){ 
       echo '456<br />';    
       return array('test1', 'test2');//数组中的成员属性test1、test2将会被串行化,test3将会被忽略
    }
}
    
$obj = new Test();  
    
// 序列化对象调用__sleep    
    
$serialized = serialize($obj);  
    
// 输出序列化后的字符串    
    
var_dump($serialized);
    
// 重建对象调用__wakeup
    
$obj2 = unserialize($serialized);

unserialize漏洞
unserialize在php官方有报告其漏洞,主要是正则不严谨引起的,当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行。
关于反序列化(unserialize)漏洞详情,可以参考:https://paper.seebug.org/39/
漏洞修复版本:升级SugarCRM到v6.5.24 升级php5到5.6.25及以上 升级php7到7.0.10及以上。
另外,当其变量是由用户决定时,可能会引起注入漏洞,详情可参考:https://blog.csdn.net/qq_32400847/article/details/53873275

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值