浅析php单态设计模式之单例模式的理解
1.单态设计模式含义:
单态模式的主要作用是保证在面向对象编程设计中,一个类只能有一个实例对象存在。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统全局地提供这个实例。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。
2.单台模式的三个关键点:
① 需要一个保存类的唯一实例的静态成员变量;
②构造函数和克隆函数必须声明为私有的,防止外部程序new类从而失去单例模式的意义;
③必须提供一个访问这个实例的公共的静态方法(通常为getInstance方法),从而返回唯一实例的一个引用 。
代码如下 | 复制代码 |
<?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"); //访问对象中的成员 ?> |
单例模式,就是保持一个对象只存在一个实例。并且为该唯一实例提供一个全局访问点(一般是一个静态的getInstance方法)。单例模式应用场景非常广泛,例如:
数据库操作对象
日志写入对象
全局配置解析对象等
这些场景的共同特征是从业务逻辑上来看运行期间改对象却是
只需要一个实例
不断new多个实例会增加不必要的资源消耗
全局调用便利
。
下面分别说明这三个方面:
1. 业务上只需要一个实例
以数据库连接对象为例,加入有一个购物网站,有一个MySQL数据库
127.0.0.1:3306
,那么在一个进程中无论我们需要进行多少次针对改数据库的操作,都只需要连接数据库一次,使用相同的数据库连接句柄(MySQL Connection Resource),从业务需求上来看就只需要一个实例。
相反,同样以购物网站为例,存在许多商品,这些商品都不一样(id,name,price..),这个时候需要显示一个商品列表,加入我们建立一个
Product
作为数据映射对象,那么从业务需求上来说,一个实例就无法满足业务需求,因为每个商品都不一样。
2. 不断new操作增加不必要的资源消耗
我们一般会在类的构造方法(new操作肯定会调用)中进行一些业务操作,例如数据库连接对象可能会在构造方法中尝试读取数据库配置并进行数据库连接(如mysqli::__construct())、日志写入对象会判断日志写入目录是否存在并写入(不存在可能尝试创建改目录)、全局配置解析对象可能需要定位配置文件的保存目录并进行文件扫描等。
这些业务都会消耗相当的资源,如果在一个进程中我们值需要做一次,将会非常有利于我们提高应用的运行效率。
3. 全局调用便利
因为单例模式的一大特点就是通过静态方法获取对象实例,那么就意味着访问对象的方法时不需要先new一个对象的实例,如果改对象需要很多地方使用,则提高了调用的便利性。
通过一个日志操作类来举例:
代码如下 | 复制代码 |
class Logger{ //首先,需要一个私有的静态变量来存储产生的对象实例 private static $instance; //业务变量,保存日志写入路径 private $logDir; //构造方法,注意必须也是私有的,不允许被外部实例化(即在外部被new) private function __construct(){ //调试输出,测试对象被new的次数 echo "new Logger instance rn"; $logDir = sys_get_temp_dir(). DIRECTORY_SEPARATOR . "logs"; if(!is_dir($logDir) || !file_exists($logDir)){ @mkdir($logDir); } $this->logDir = $logDir; } //类唯一实例的全局访问点,用于判断并返回对象实例,供外部调用 public static function getInstance(){ if(is_null(self::$instance)){ $class = __CLASS__; //获取本对象的类型,也可以用new self()方式 self::$instance = new $class(); } return self::$instance; } //重载__clone()方法,不允许对象对克隆 public function __clone(){ throw new Exception("Singleton Class Can Not Be Cloned"); } //具体的业务方法,实际可以有很多方法 public function logError($message){ $logFile = $this->logDir . DIRECTORY_SEPARATOR . "error.log"; error_log($message, 3, $logFile); } } //日志调用 $logger = Logger::getInstance(); $logger->logError("An error occured"); $logger->logError("Another error occured"); //或者这样调用 Logger::getInstance()->logError("Still have error"); Logger::getInstance()->logError("I should fix it"); |
在单例模式中可能遇到一种比较特殊的情况,比如数据库连接对象,对于大型应用来说,很可能需要连接多台数据库,那么不同的数据库公用一个对象可能会产生问题,比如连接的分配、获取
insert_id
,
last_error
等。这个问题也比较好解决,就是把我们的$instance变量变成一个关联数组,通过给getInstance方法传入不同的参数获取不同的"单例对象"(引号的含义是:严格来说类可能被new多次,但是这个new也是在我们的控制之内的,而不是在类外部):
代码如下 | 复制代码 |
class MysqlServer{ //注意,变成复数了哦^_^ 当然只是为了标识而已 private static $instances = array(); //业务变量,保持当前实例的mysqli对象 private $conn; //显著特征:私有的构造方法,避免在类外部被实例化 private function __construct($host, $username, $password, $dbname, $port){ $this->conn = new mysqli($host, $username, $password, $dbname, $port); } //类唯一实例的全局访问点 public static function getInstance($host='localhost', $username='root', $password='123456', $dbname='mydb', $port='3306'){ $key = "{$host}:{$port}:{$username}:{$dbname}"; if (empty(self::$instances[$key])){ //这里也可以用 new self(); 的方式 $class = __CLASS__; self::$instances[$key] = new $class($host, $username, $password, $dbname, $port); } return self::$instances[$key]; } //重载__clone方法,不允许对象实例被克隆 public function __clone(){ throw new Exception("Singleton Class Can Not Be Cloned"); } //查询业务方法,后面省略其它业务方法 public function query($sql){ return $this->conn->query($sql); } //尽早释放资源 public function __destruct(){ $this->conn->close(); } } |
问题1:单例类能否拥有子类,因为单例类的构造方法是私有的,因此无法被继承,如果要继承则需要将构造方法改为protected或public,这就违背了单例模式的本意。因此,如果你想给单例类加子类,那就需要回头想想是否错用了模式,或者结构设计上有问题。
问题2:单例滥用,单例模式相对来说比较好理解和实现,因此一旦认识到单例模式的好处,很可能什么类都想写成单例,因此在使用次模式之前一定要考虑上述3种情况,看是否真的有必要使用。
SQL注入攻击的概念及危害我相信大家都有所了解,如果不了解的请自己度娘,下面我们来学习一下在php中如何有效的防止问题描述:
如果用户输入的数据在未经处理的情况下插入到一条SQL查询语句,那么应用将很可能遭受到SQL注入攻击,正如下面的例子:
代码如下 | 复制代码 |
$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('" . $unsafe_variable . "')"); |
因为用户的输入可能是这样的:
代码如下 | 复制代码 |
value'); DROP TABLE table;-- |
那么SQL查询将变成如下:
代码如下 | 复制代码 |
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--') |
应该采取哪些有效的方法来防止SQL注入?
最佳回答(来自Theo):
使用预处理语句和参数化查询。预处理语句和参数分别发送到数据库服务器进行解析,参数将会被当作普通字符处理。这种方式使得攻击者无法注入恶意的SQL。 你有两种选择来实现该方法:
1、使用PDO:
代码如下 | 复制代码 |
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute(array('name' => $name)); foreach ($stmt as $row) { // do something with $row } |
2、使用mysqli:
代码如下 | 复制代码 |
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // do something with $row } |
PDO
注意,在默认情况使用PDO并没有让MySQL数据库执行真正的预处理语句(原因见下文)。为了解决这个问题,你应该禁止PDO模拟预处理语句。一个正确使用PDO创建数据库连接的例子如下:
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
在上面的例子中,报错模式(ATTR_ERRMODE)并不是必须的,但建议加上它。这样,当发生致命错误(Fatal Error)时,脚本就不会停止运行,而是给了程序员一个捕获PDOExceptions的机会,以便对错误进行妥善处理。 然而,第一个setAttribute()调用是必须的,它禁止PDO模拟预处理语句,而使用真正的预处理语句,即有MySQL执行预处理语句。这能确保语句和参数在发送给MySQL之前没有被PHP处理过,这将使得攻击者无法注入恶意SQL。了解原因,可参考这篇博文:PDO防注入原理分析以及使用PDO的注意事项。 注意在老版本的PHP(<5.3.6),你无法通过在PDO的构造器的DSN上设置字符集,参考:silently ignored the charset parameter。
解析
当你将SQL语句发送给数据库服务器进行预处理和解析时发生了什么?通过指定占位符(一个?或者一个上面例子中命名的 :name),告诉数据库引擎你想在哪里进行过滤。当你调用execute的时候,预处理语句将会与你指定的参数值结合。 关键点就在这里:参数的值是和经过解析的SQL语句结合到一起,而不是SQL字符串。SQL注入是通过触发脚本在构造SQL语句时包含恶意的字符串。所以,通过将SQL语句和参数分开,你防止了SQL注入的风险。任何你发送的参数的值都将被当作普通字符串,而不会被数据库服务器解析。回到上面的例子,如果$name变量的值为 ’Sarah’; DELETE FROM employees ,那么实际的查询将是在 employees 中查找 name 字段值为 ’Sarah’; DELETE FROM employees 的记录。 另一个使用预处理语句的好处是:如果你在同一次数据库连接会话中执行同样的语句许多次,它将只被解析一次,这可以提升一点执行速度。 如果你想问插入该如何做,请看下面这个例子(使用PDO):
代码如下 | 复制代码 |
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute(array('column' => $unsafeValue)); |
开发什么的最喜欢了。写程序可以提高实力又可以收藏一些代码!的确很是不错。由于某些原因需要获取数据库最大的id值。所以出现了这段php 获取数据库最大的id代码了。这里面的max(id) 这里面的id 就是要获取最大的id了。如果是别的字段请填写为其他字段
方法一
$fh = mysql_query("select MAX(id) from log");
$c_echo = mysql_fetch_array($fh);
echo $c_echo;
方法二
<?php
//执行插入数据库的语句
//……
$getID=mysql_insert_id();//$getID即为最后一条记录的ID
?>
PHP 函数 mysql_insert_id() 是返回在最后一次执行了 INSERT 查询后,由 AUTO_INCREMENT 定义的字段的值。
方法三
msyql_query("select last_insert_id()");
last_insert_id() 是mysql 一个函数 也是 对当前链接起效
此用法 解决了 mysql_insert_id () 中遇到的 bigint 型问题
使用PHP描述冒泡排序和快速排序算法,对象可以是一个数组。
使用PHP描述顺序查找和二分查找(也叫做折半查找)算法,顺序查找必须考虑效率,对象可以是一个有序数组。
写一个二维数组排序算法函数,能够具有通用性,可以调用php内置函数
1. 使用PHP描述冒泡排序和快速排序算法,对象可以是一个数组
代码如下 | 复制代码 |
function bubble_sort($array)
|
2. 使用PHP描述顺序查找和二分查找(也叫做折半查找)算法,顺序查找必须考虑效率,对象可以是一个有序数组
代码如下 | 复制代码 |
function bin_sch($array, $low, $high, $k){ if ($low <= $high){ $mid = intval(($low+$high)/2); if ($array[$mid] == $k){ return $mid; }elseif ($k < $array[$mid]){ return bin_sch($array, $low, $mid-1, $k); }else{ return bin_sch($array, $mid+1, $high, $k); } } return -1; }function seq_sch($array, $n, $k){ $array[$n] = $k; for($i=0; $i<$n; $i++){ if($array[$i]==$k){ break; } } if ($i<$n){ return $i; }else{ return -1; } } |
3. 写一个二维数组排序算法函数,能够具有通用性,可以调用php内置函数
代码如下 | 复制代码 |
function array_sort($arr, $keys, $order=0) { |
1、简介
当PHP脚本有输出时,输出控制函数可以用这些来控制输出。这在多种不同情况中非常有用,尤其是用来在脚本开始输出 数据后,发送http头信息到浏览器。输出控制函数不影响由 header()或setcookie()发送的文件头信息,仅影响像echo这样的函数和PHP代码块间的数据。
2、作用
1)、如官方介绍说,为了避免在输出文件头信息如header()和setcookie()之前出现输出而发生的错误。可以使用输出缓存函数。如下代码
代码如下 | 复制代码 |
<?php ob_start(); //打开缓存控制函数 echo 'hello'; header('Location: http://www.111cn.net/'); ?> |
2)、将输出内容放入缓存区,如需进行其他操作,如将输出写入缓存文件,可将缓冲区内容写入缓存文件,然后输出。
代码如下 | 复制代码 |
ob_start(); echo 'hello'; $file = ob_get_contents(); ob_end_clean(); write_cache($file); //将缓冲区内容写入缓存函数,实现细节省略 |
3)、获取某些无返回值内置函数的输出内容,如phpinnfo();
代码如下 | 复制代码 |
<?php ob_start(); phpinfo(); $file = ob_get_contents(); //phpinfo()输出内容 ob_end_clean(); ?> |
4)、在使用框架的时候,我们会发现框架中会将输出缓冲然后进行变量替换。最后输出。
除了这些内容,我们还可以利用输出缓存进行错误处理,具体看代码。我们可以将错误信息获取,以更加友好的方式输出。
代码如下 | 复制代码 |
<?php function display_error() { if($errors = error_get_last()) { return var_export($errors , TRUE); } return FALSE; } $dis_error = 'display_error'; //$a = 3; ob_start( $dis_error ); echo $a; |
附:ob_start()官方解释:
bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )
此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。
内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中。 想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。另外, 使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容。
output_callback
可选参数 output_callback 函数可以被指定。 此函数把一个字符串当作参数并返回一个字符串。 当输出缓冲区被( ob_flush(), ob_clean() 或者相似的函数)冲刷(送出)或者被清洗的时候;或者在请求结束之际输出缓冲区内容被冲刷到浏览器的时候该函数将会被调用。 当调用 output_callback 时,它将收到输出缓冲区的内容作为参数 并预期返回一个新的输出缓冲区作为结果,这个新返回的输出缓冲区内容将被送到浏览器。 如果这个 output_callback 不是一个可以调用的函数,此函数 会返回 FALSE 。
如果回调函数有两个参数,第二个参数会由一个位域补充,该位域由 PHP_OUTPUT_HANDLER_START, PHP_OUTPUT_HANDLER_CONT 和 PHP_OUTPUT_HANDLER_END 组成。
如果 output_callback 返回 FALSE ,其原来的输入 内容被直接送到浏览器。
这个参数 output_callback 可以通过直接给一个 NULL 值而避开。
ob_end_clean(), ob_end_flush(), ob_clean(), ob_flush() and ob_start() 不能从一个回调函数中调用。 如果从回调函数中调用了它们,产生的行为是不明确的。 如果想要删除缓冲区的内容,从回调函数中返回一个”" (空字符串)。 更不能从一个回调函数中使用像print_r($expression, true) 或highlight_file($filename, true) 一样的输出缓冲函数。
相关文章
- 今天小编在这里就来给各位photoshop的这一款软件的使用者们来说一说设计一幅大鱼海棠动画片海报制作的实例教程,各位想知道具体制作步骤的使用者们,那么各位就快来看看...2016-09-14
- ps软件是一款非常不错的图片处理软件,有着非常不错的使用效果。这次文章要给大家介绍的是ps怎么制作倒影,一起来看看设计倒影的方法。 用ps怎么做倒影最终效果̳...2017-07-06
- 这篇文章主要为大家介绍了JavaScript设计模式中的装饰者模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21
- 神马是“解释器模式”?先翻开《GOF》看看Definition:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。在开篇之前还是要科普几个概念: 抽象语法树: 解释器模式并未解释如...2014-06-07
- 这篇文章主要介绍了C语言程序设计第五版谭浩强课后答案(第二章答案),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2021-04-02
- 今天小编在这里就来给Photoshop的这一款软件的使用者们来说下计商务名片的5种常见思路,各位想知道的使用者,那么下面就快来跟着小编一起看一看吧。 给各位Photosho...2016-09-14
- 最近在网上看到了新版的360安全卫士,感觉界面还不错,于是用WPF制作了一个,时间有限,一些具体的控件没有制作,用图片代替了。感兴趣的朋友一起跟着小编学习WPF实现类似360安全卫士界面的程序源码分享...2020-06-25
- 这篇文章主要介绍了JavaScript设计模式之职责链模式,对设计模式感兴趣的同学,可以参考下...2021-04-25
- 今天小编在这里就来给各位photoshop的这一款软件的使用者们来说下安卓和苹果的界面设计之尺寸规范,各位想知道的使用者们,那么下面就快来跟着小编一起看看吧。 给...2016-09-14
- 今天小编在这里就来给各位photoshop的这一款软件的使用者们来说一说设计重影效果具体的制作教程,各位想知道具体制作方法的软件使用者们,那么大家就来看下小编带来的教...2016-09-14
Illustrator结合photoshop设计可爱的卡通女厨师头像制作教程
今天小编在这里就来给Illustrator的这一款软件的使用者们来说一说结合photoshop设计可爱的卡通女厨师头像的制作教程,各位想知道具体制作步骤的使用者们,那么下面就快来...2016-09-14- PS怎么设计T恤?很多人都想要在T恤上有自己喜欢的图案,那么自己设计T恤的图案是方法之一,本次为大家带来了详细的ps设计T恤教程,有兴趣的同学快来看看吧。 1、打开PS,新...2016-12-31
- 这篇文章主要为大家介绍了JavaScript设计模式中的状态模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-12
- 今天小编在这里就来给photoshop的这一款软件的使用者们来详细的说说设计一张节约用水环保主题海报的制作教程,各位想知道具体制作方法的使用者们,那么下面就快来跟着小...2016-09-14
- 这篇文章主要介绍了JavaScript设计模式之命令模式,对设计模式感兴趣的同学,可以参考下...2021-04-25
- 这篇文章主要为大家介绍了JavaScript设计模式中的单例模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21
- 这篇文章主要为大家介绍了JavaScript设计模式中的状态模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-14
- c#实现51单片机频率计的代码分享,大家参考使用吧...2020-06-25
- 我们建站的时候,开始是每次请求数据库都要重新连接的、这样显然不合理、然后自己封装了一个数据库操作类、DBTools.php、要解决一个连接多次使用的话、最好的办法是使...2016-11-25
- 这篇文章主要为大家介绍了JavaScript设计模式中的责任链模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21