MySQL的sql_mode解析与设置

解析

这个sql_mode,简而言之就是:它定义了你MySQL应该支持的sql语法,对数据的校验等等。。

一、如何查看当前数据库使用的sql_mode:

mysql> select @@sql_mode;

如下是我的数据库当前的模式:

二、sql_mode值的含义:

ONLY_FULL_GROUP_BY:

对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么将认为这个SQL是不合法的,因为列不在GROUP BY从句中

STRICT_TRANS_TABLES:

在该模式下,如果一个值不能插入到一个事务表中,则中断当前的操作,对非事务表不做任何限制

NO_ZERO_IN_DATE:

在严格模式,不接受月或日部分为0的日期。如果使用IGNORE选项,我们为类似的日期插入’0000-00-00’。在非严格模式,可以接受该日期,但会生成警告。

NO_ZERO_DATE:

在严格模式,不要将 ‘0000-00-00’做为合法日期。你仍然可以用IGNORE选项插入零日期。在非严格模式,可以接受该日期,但会生成警告

ERROR_FOR_DIVISION_BY_ZERO:

在严格模式,在INSERT或UPDATE过程中,如果被零除(或MOD(X,0)),则产生错误(否则为警告)。如果未给出该模式,被零除时MySQL返回NULL。如果用到INSERT IGNORE或UPDATE IGNORE中,MySQL生成被零除警告,但操作结果为NULL。

NO_AUTO_CREATE_USER

防止GRANT自动创建新用户,除非还指定了密码。

NO_ENGINE_SUBSTITUTION:

如果需要的存储引擎被禁用或未编译,那么抛出错误。不设置此值时,用默认的存储引擎替代,并抛出一个异常

另外还有一些,这里仅对我本地当前值做解释分析。。。。。

三、据说是MySQL5.0以上版本支持三种sql_mode模式:ANSI、TRADITIONAL和STRICT_TRANS_TABLES。

1、ANSI模式:宽松模式,更改语法和行为,使其更符合标准SQL。对插入数据进行校验,如果不符合定义类型或长度,对数据类型调整或截断保存,报warning警告。对于本文开头中提到的错误,可以先把sql_mode设置为ANSI模式,这样便可以插入数据,而对于除数为0的结果的字段值,数据库将会用NULL值代替。

将当前数据库模式设置为ANSI模式:

mysql> set @@sql_mode=ANSI;

2、TRADITIONAL模式:严格模式,当向mysql数据库插入数据时,进行数据的严格校验,保证错误数据不能插入,报error错误,而不仅仅是警告。用于事物时,会进行事物的回滚。 注释:一旦发现错误立即放弃INSERT/UPDATE。如果你使用非事务存储引擎,这种方式不是你想要的,因为出现错误前进行的数据更改不会“滚动”,结果是更新“只进行了一部分”。

将当前数据库模式设置为TRADITIONAL模式:

mysql> set @@sql_mode=TRADITIONAL;

3、STRICT_TRANS_TABLES模式:严格模式,进行数据的严格校验,错误数据不能插入,报error错误。如果不能将给定的值插入到事务表中,则放弃该语句。对于非事务表,如果值出现在单行语句或多行语句的第1行,则放弃该语句。

将当前数据库模式设置为STRICT_TRANS_TABLES模式:

mysql> set @@sql_mode=STRICT_TRANS_TABLES;

没有最好与最坏的模式,只有最合适的模式。需要根据自己的实际情况去选择那个最适合的模式!!!

另外说一点,这里的更改数据库模式都是session级别的,一次性,关了再开就不算数了!!!

也可以通过配置文件设置:vim /etc/my.cnf
在my.cnf(my.ini)添加如下配置:
[mysqld]
sql_mode=’你想要的模式’

经典的 Fork 炸弹解析

Jaromil 在 2002 年设计了最为精简的一个Linux Fork炸弹,整个代码只有13个字符,在 shell 中运行后几秒后系统就会宕机:

 :(){:|:&};:

这样看起来不是很好理解,我们可以更改下格式:

:()
{
	:|:&
};
:

更好理解一点

bomb()
{
	bomb|bomb&
};
bomb

因为shell中函数可以省略function关键字,所以上面的十三个字符是功能是定义一个函数与调用这个函数,函数的名称为:,主要的核心代码是:|:&,可以看出这是一个函数本身的递归调用,通过&实现在后台开启新进程运行,通过管道实现进程呈几何形式增长,最后再通过:来调用函数引爆炸弹。因此,几秒钟系统就会因为处理不过来太多的进程而死机,解决的唯一办法就是重启。

Bomb一下

秉着不作不死的心态,我们也来运行一下,于是我将矛头指向云主机,,我使用了国内的一个2G内存的云主机,首先在本地开启两个终端,在一个终端连接云主机后运行炸弹,秒后再尝试用另外一个终端登录,效果可以看下面Gif图:

看,运行一段时间后直接报出了-bash: fork: Cannot allocate memory,说明内存不足了。并且我在二号终端上尝试连接也没有任何反应。因为是虚拟的云主机,所以我只能通过主机服务商的后台来给主机断电重启。然后才能重新登录:

炸弹危害

Fork炸弹带来的后果就是耗尽服务器资源,使服务器不能正常的对外提供服务,也就是常说的DoS(Denial of Service)。与传统1v1、通过不断向服务器发送请求造成服务器崩溃不同,Fork炸弹有种坐山观虎斗,不费一兵一卒斩敌人于马下的感觉。更吓人的是这个函数是不需要root权限就可以运行的。看到网上有帖子说某些人将个性签名改为Fork炸弹,结果果真有好奇之人中枪,试想如果中枪的人是在公司服务器上运行的话,oh,!

 

预防方式

当然,Fork炸弹没有那么可怕,用其它语言也可以分分钟写出来一个,例如,python版:

import os
 	while True: 
 	os.fork()

Fork炸弹的本质无非就是靠创建进程来抢占系统资源,在Linux中,我们可以通过ulimit命令来限制用户的某些行为,运行ulimit -a可以查看我们能做哪些限制:

ubuntu@10-10-57-151:~$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7782
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7782
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

可以看到,-u参数可以限制用户创建进程数,因此,我们可以使用ulimit -u 20来允许用户最多创建20个进程。这样就可以预防bomb炸弹。但这样是不彻底的,关闭终端后这个命令就失效了。我们可以通过修改/etc/security/limits.conf文件来进行更深层次的预防,在文件里添加如下一行(ubuntu需更换为你的用户名):

ubuntu - nproc 20

这样,退出后重新登录,就会发现最大进程数已经更改为20了,

这个时候我们再次运行炸弹就不会报内存不足了,而是提示-bash: fork: retry: No child processes,说明Linux限制了炸弹创建进程。

参考

http://en.wikipedia.org/wiki/Fork_bomb

 

原文出处: saymagic

centos redis安装配置

本文详细介绍Redis单机单实例安装与配置,服务及开机自启动。

以下配置基于CentOS release 6.5 Final, redis版本3.0.2 [redis版本号中间位是偶数的是稳定版,奇数的为非稳定版]

一.安装redis

1)     下载redis安装包

可去官网http://redis.io ,也可通过wget命令,

wget http://download.redis.io/redis-stable.tar.gz

2)     解压

tar –zxvf redis-stable.tar.gz

3)     编译、安装

cd redis-stable

make

如果提示gcc command不识别,请自行安装gcc;

如果提示couldn’t execute tcl : no such file or dicrectory,请自行安装tcl;

如果提示

请执行make distclean,然后再make

Make成功之后,会在src目录下多出一些文件,如下

可手动拷贝redis-server、redis-cli、redis-check-aof、redis-check-dump等至/usr/local/bin目录下,也可执行make install,此处执行make install

可查看,/usr/local/bin下已有这些文件。

注意:若此时执行redis-server –v (查看版本命令),若提示redis-server command not found,则需要将/usr/local/bin目录加到环境变量,如何添加,此处不做详细介绍,可查看修改/etc/profile,(查看环境变量命令:echo $PATH)

正常如下

至此,redis安装完成,接着配置。

二.修改配置文件.conf

1)     创建配置文件目录,dump file 目录,进程pid目录,log目录等

配置文件一般放在/etc/下,创建redis目录

cd /etc/

mkdir redis

ll 查看创建的redis目录

~

dump file、进程pid、log目录等,一般放在/var/目录下,

cd /var/

mkdir redis

cd redis

mkdir data log run

至此,目录创建完毕

2)     修改配置文件,配置参数

首先拷贝解压包下的redis.conf文件至/etc/redis

查看/etc/redis/redis.conf

cd /etc/redis/

ll

打开redis.conf文件

修改端口(默认6379)

修改pid目录为新建目录

修改dump目录为新建目录

修改log存储目录为新建目录

3)     持久化

默认rdb,可选择是否开启aof,若开启,修改配置文件appendonly

4)     启动redis,查看各目录下文件

查看进程

redis已启动

查看dump, log, pid等

发现只有日志,没有dump和pid信息,是因为当前redis服务仍然是console模式运行的,且没有数据存储操作

停止redis服务,修改配置文件使得redis在background运行

改成yes,保存,重启redis服务

查看pid信息,如下

查看dump信息

若配置了aof持久化方式,data目录下还会有aof的相关文件

5)     客户端连接redis

默认端口6379

6)     至此,redis基础配置完毕,若有其他相关配置调整,可查找文档再修改

三.服务及开机自启动

1)     创建redis启动脚本

拷贝解压包下utils下redis启动脚本至/etc/init.d/

cp redis_init_script /etc/init.d/

修改脚本名称(也可不修改)为redis

查看ll

修改脚本pid及conf路径为实际路径

生产环境下,配置时,配置文件、pid等最好加上端口标识,以便区分,如

 

保存

退出

至此,在/etc/init.d/目录下,已经可以通过service redis start/stop 命令启动和关闭redis

 

若在其他目录下,不能够使用这2个命令,请继续配置2),添加权限

2)     给启动脚本添加权限

chmod +x /etc/init.d/redis

实际命令,根据目录的不同,会不一样

相应的删除权限是

chmod –x /etc/init.d/redis

如果需要在开机的时候,redis服务自动启动,可继续3)

3)     设置自启动

chkconfig redis on

如果运行报错,提示

是因为没有在启动脚本里加入redis启动优先级信息,可添加如下

再次执行chkconfig redis on,成功

 

至此,自启动配置完毕

原文链接:http://blog.csdn.net/ludonqin/article/details/47211109

apache如何解决跨域资源访问

首先编辑httpd.conf

找到这行

#LoadModule headers_module modules/mod_headers.so

把#注释符去掉

LoadModule headers_module modules/mod_headers.so

目的是开启apache头信息自定义模块

 

然后在独立资源域名的虚拟主机添加一行

Header set Access-Control-Allow-Origin *

意思是对这个域名的资源进行访问时,添加一个头信息

 

重启apache

再访问,OK!

使用 OpCache 提升 PHP 5.5+ 程序性能

PHP 5.5 以后内建了 OpCache , OpCache 的加速原理是把编译后的 bytecode 存储在内存里面, 避免重复编译 PHP 所造成的资源浪费.

修改 php.ini 文件

sudo vim /usr/local/php/etc/php.ini

在文件最后面加入:

; 开关打开
opcache.enable=1

; 可用内存, 酌情而定, 单位 megabytes
opcache.memory_consumption=256

; 对多缓存文件限制, 命中率不到 100% 的话, 可以试着提高这个值
opcache.max_accelerated_files=5000

; Opcache 会在一定时间内去检查文件的修改时间, 这里设置检查的时间周期, 默认为 2, 定位为秒
opcache.revalidate_freq=240

重启服务

sudo service php-fpm restart
sudo service nginx restart

 

在 PHP 中养成 7 个面向对象的好习惯

在 PHP 编程早期,PHP 代码在本质上是限于面向过程的。过程代码 的特征在于使用过程构建应用程序块。过程通过允许过程之间的调用提供某种程度的重用。
但是,没有面向对象的语言构造,程序员仍然可以把 OO 特性引入到 PHP 代码中。这样做有点困难并且会使代码难于阅读,因为它是混合范例(含有伪 OO 设计的过程语言)。使用 PHP 代码中的 OO 构造 — 例如能够定义和使用类、能够构建使用继承的类之间的关系以及能够定义接口 — 可以更轻松地构建符合优秀 OO 实践的代码。
虽然没有过多模块化的纯过程设计运行得很好,但是 OO 设计的优点表现在维护上。由于典型应用程序的大部分生命周期都花费在维护上,因此代码维护是应用程序生命周期的重要部分。并且在开发过程中代码维护很容易被遗忘。如果在应用程序开发和部署方面存在竞争,那么长期可维护性可能被放在比较次要的地位。
模块化— 优秀 OO 设计的主要特性之一 — 可以帮助完成这样的维护。模块化将帮助封装更改,这样可以随着时间的推移更轻松地扩展和修改应用程序。
总的来说,虽然构建 OO 软件的习惯不止 7 个,但是遵循这里的 7 个习惯可以使代码符合基本 OO 设计标准。它们将为您提供更牢固的基础,在此基础之上建立更多 OO 习惯并构建可轻松维护与扩展的软件。这些习惯针对模块化的几个主要特性。

7 个优秀 PHP OO 习惯包括:

1、保持谦虚。
2、做个好邻居。
3、避免看到美杜莎。
4、利用最弱的链接。
5、您是橡皮;我是胶水。
6、限制传播。
7、考虑使用模式

保持谦虚
保持谦虚指避免在类实现和函数实现中暴露自己。隐藏您的信息是一项基本习惯。如果不能养成隐藏实现细节的习惯,那么将很难养成任何其他习惯。信息隐藏也称为封装。

直接公开公共字段是一个坏习惯的原因有很多,最重要的原因是让您在实现更改中没有应有的选择。使用 OO 概念隔离更改,而封装在确保所作更改在本质上不是病毒性(viral)更改方面扮演不可或缺的角色。病毒性 更改是开始时很小的更改 — 如将保存三个元素的数组更改为一个只包含两个元素的数组。突然,您发现需要更改越来越多的代码以适应本应十分微不足道的更改。

开始隐藏信息的一种简单方法是保持字段私有并且用公共访问方法公开这些字段,就像家中的窗户一样。并没有让整面墙都朝外部开放,而只打开一两扇窗户(我将在 “好习惯:使用公共访问方法” 中介绍访问方法的更多信息)。

除了允许您的实现隐藏在更改之后外,使用公共访问方法而非直接公开字段将允许您在基本实现的基础上进行构建,方法为覆盖访问方法的实现以执行略微不同于父方法的行为。它还允许您构建一个抽象实现,从而使实际实现委托给覆盖基本实现的类。

坏习惯:公开公共字段
在清单 1 的坏代码示例中,Person 对象的字段被直接公开为公共字段而非使用访问方法。虽然此行为十分诱人,尤其对于轻量级数据对象来说更是如此,但是它将对您提出限制。

清单 1. 公开公共字段的坏习惯

<?php
class Person {
    public $prefix;
    public $givenName;
    public $familyName;
    public $suffix;
}

$person = new Person();
$person->prefix = "Mr.";
$person->givenName = "John";

echo($person->prefix);
echo($person->givenName);

如果对象有任何更改,则使用该对象的所有代码也都需要更改。例如,如果某人的教名、姓氏和其他名字被封装到 PersonName 对象中,则需要修改所有代码以适应更改。

好习惯:使用公共访问方法
通过使用优秀的 OO 习惯(参见清单 2),同一个对象现在拥有私有字段而非公共字段,并且通过称为访问方法 的 get 和 set 公共方法谨慎地向外界公开私有字段。这些访问方法现在提供了一种从 PHP 类中获取信息的公共方法,这样在实现发生更改时,更改使用类的所有代码的需求很可能变小。

清单 2. 使用公共访问方法的好习惯

<?php
class Person {
    private $prefix;
    private $givenName;
    private $familyName;
    private $suffix;
    
    public function setPrefix($prefix) {
        $this->prefix = $prefix;
    }
    
    public function getPrefix() {
        return $this->prefix;
    }
    
    public function setGivenName($gn) {
        $this->givenName = $gn;
    }
    
    public function getGivenName() {
        return $this->givenName;
    }
    
    public function setFamilyName($fn) {
        $this->familyName = $fn;
    }
    
    public function getFamilyName() {
        return $this->familyName;
    }
    
    public function setSuffix($suffix) {
        $this->suffix = $suffix;
    }
    
    public function getSuffix() {
        return $suffix;
    }
    
}

$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");

echo($person->getPrefix());
echo($person->getGivenName());

乍看之下,这段代码可能会完成大量工作,并且实际上可能更多是在前端的工作。但是,通常,使用优秀的 OO 习惯从长远来看十分划算,因为将极大地巩固未来更改。

在清单 3 中所示的代码版本中,我已经更改了内部实现以使用名称部件的关联数组。比较理想的情况是,我希望拥有错误处理并且更仔细地检查元素是否存在,但是本例的目的在于展示使用我的类的代码无需更改的程度 — 代码并没有察觉到类发生更改。记住采用 OO 习惯的原因是要谨慎封装更改,这样代码将更具有可扩展性并且更容易维护。

清单 3. 使用不同内部实现的另一个示例

<?php
class Person {
    private $personName = array();
    
    public function setPrefix($prefix) {
        $this->personName['prefix'] = $prefix;
    }
    
    public function getPrefix() {
        return $this->personName['prefix'];
    }
    
    public function setGivenName($gn) {
        $this->personName['givenName'] = $gn;
    }
    
    public function getGivenName() {
        return $this->personName['givenName'];
    }

    /* etc... */
}

/*
 * Even though the internal implementation changed, the code here stays exactly
 * the same. The change has been encapsulated only to the Person class.
 */
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");

echo($person->getPrefix());
echo($person->getGivenName());

做个好邻居
在构建类时,它应当正确地处理自己的错误。如果该类不知道如何处理错误,则应当以其调用者理解的格式封装这些错误。此外,避免返回空对象或者状态无效的对象。许多时候,只需通过检验参数并抛出特定异常说明提供参数无效的原因就可以实现这一点。在您养成这个习惯时,它可以帮您 — 和维护代码或使用对象的人员 — 节省很多时间。

坏习惯:不处理错误
考虑清单 4 中所示的示例,该示例将接受一些参数并返回填充了一些值的 Person 对象。但是,在 parsePersonName() 方法中,没有验证提供的 $val 变量是否为空、是否是零长度字符串或者字符串是否使用无法解析的格式。parsePersonName() 方法不返回 Person 对象,但是返回 null。使用这种方法的管理员或程序员可能会觉得很麻烦 — 至少他们现在需要开始设置断点并调试 PHP 脚本。

清单 4. 不抛出或处理错误的坏习惯

<?php
class PersonUtils{
    public static function parsePersonName($format, $val) {
        if (strpos(",", $val) > 0) {
            $person = new Person();
            $parts = split(",", $val); // Assume the value is last, first
            $person->setGivenName($parts[1]);
            $person->setFamilyName($parts[0]);
        }
        return $person;
    }
}

清单 4 中的 parsePersonName() 方法可以修改为在 if 条件外部初始化 Person 对象,确保总是获得有效的 Person 对象。但是,您得到的是没有 set 属性的 Person,这仍然没有很好地改善您的困境。

好习惯:每个模块都处理自己的错误
不要让调用方凭空猜测,而是对参数进行预先验证。如果未设置的变量无法生成有效的结果,请检查变量并抛出 InvalidArgumentException。如果字符串不能为空或者必须为特定格式,请检查格式并抛出异常。清单 5 解释了如何在演示一些基本验证的 parsePerson() 方法中创建异常以及一些新条件。

清单 5. 抛出错误的好习惯

<?php
class InvalidPersonNameFormatException extends LogicException {}

class PersonUtils{
    public static function parsePersonName($format, $val) {
        if (! $format) {
            throw new InvalidPersonNameFormatException("Invalid PersonName format.");
        }

        if ((! isset($val)) || strlen($val) == 0) {
            throw new InvalidArgumentException("Must supply a non-null value to parse.");
        }
    }
}

最终目的是希望人们能够使用您的类,而不必了解其中的工作原理。如果他们使用的方法不正确或者不是按照期望的方法使用,也不需要猜测不能工作的原因。作为一个好邻居,您需要知道对您的类进行重用的人并没有特异功能,因此您需要解决猜测的问题。

避免看到美杜莎
在我最初了解 OO 概念时,我十分怀疑接口是否真正有帮助。我的同事给我打了个比方,说不使用接口就好像看到美杜莎的头。在希腊神话中,美杜莎是长着蛇发的女怪。凡是看了她一眼的人都会变成石头。杀死美杜莎的珀尔休斯通过在盾上观察她的影子,避免了变成石头而得以与她对抗。

接口就是对付美杜莎的镜子。当您使用一个特定的具体实现时,代码也必须随着实现代码的更改而更改。直接使用实现将限制您的选择,因为您已经在本质上把类变成了 “石头”。

坏习惯:不使用接口
清单 6 显示了从数据库中装入 Person 对象的示例。它将获取人员的姓名并返回数据库中匹配的 Person 对象。

清单 6. 不使用接口的坏习惯

<?php
class DBPersonProvider {
    public function getPerson($givenName, $familyName) {
        /* go to the database, get the person... */
        $person = new Person();
        $person->setPrefix("Mr.");
        $person->setGivenName("John");
        return $person;
    }
}

/* I need to get person data... */
$provider = new DBPersonProvider();
$person = $provider->getPerson("John", "Doe");

echo($person->getPrefix());
echo($person->getGivenName());

在环境发生更改之前,从数据库中装入 Person 的代码都可以正常运行。例如,从数据库装入 Person 可能适用于第一个版本的应用程序,但是对于第二个版本,可能需要添加从 Web 服务装入人员的功能。其实,该类已经变成 “石头”,因为它在直接使用实现类并且现在能做的更改十分有限。

好习惯:使用接口
清单 7 显示了一个代码示例,在实现了加载用户的新方法后并没有进行更改。该示例显示了一个名为 PersonProvider 的接口,该接口将声明单个方法。如果任何代码使用 PersonProvider,代码都禁止直接使用实现类。相反,它就像是一个实际对象一样使用 PersonProvider。

清单 7. 使用接口的好习惯

<?php
interface PersonProvider {
    public function getPerson($givenName, $familyName);
}

class DBPersonProvider implements PersonProvider  {
    public function getPerson($givenName, $familyName) {
        /* pretend to go to the database, get the person... */
        $person = new Person();
        $person->setPrefix("Mr.");
        $person->setGivenName("John");
        return $person;
    }
}

class PersonProviderFactory {
    public static function createProvider($type) {
        if ($type == 'database') {
            return new DBPersonProvider();
        } else {
            return new NullProvider();
        }
    }
}

$config = 'database';
/* I need to get person data... */
$provider = PersonProviderFactory::createProvider($config);
$person = $provider->getPerson("John", "Doe");

echo($person->getPrefix());
echo($person->getGivenName());

在使用接口时,尝试避免直接引用实现类。相反,使用对象外部的内容可以提供正确的实现。如果您的类将装入基于某些逻辑的实现,它仍然需要获取所有实现类的定义,并且那样做也无法取得任何效果。

您可以使用 Factory 模式来创建实现接口的实现类的实例。根据约定,factory 方法将以 create 为开头并返回接口。它可以为您的 factory获取必要的参数以计算出应当返回哪个实现类。

在清单 7 中,createProvider() 方法只是获取 $type。如果 $type 被设为 database,工厂将返回 DBPersonProvider 的实例。从数据库中装入人员的任何新实现都不要求在使用工厂和接口的类中进行任何更改。DBPersonProvider 将实现 PersonProvider 接口并且拥有 getPerson() 方法的实际实现。

利用最弱的链接
将模块松散耦合 在一起是件好事情;它是允许您封装更改的属性之一。另外两个习惯 — “保持谨慎” 和 “避免看到美杜莎” — 可帮助您构建松散耦合的模块。要实现松散耦合的类,可通过养成降低类依赖关系的习惯实现。

坏习惯:紧密耦合
在清单 8 中,降低依赖关系并不是必须降低使用对象的客户机的依赖关系。相反,该示例将演示如何降低与正确类的依赖关系并最小化这种依赖关系。

清单 8. Address 中紧密耦合的坏习惯

<?php
require_once "./AddressFormatters.php";

class Address {
    private $addressLine1;
    private $addressLine2;
    private $city;
    private $state; // or province...
    private $postalCode;
    private $country;

    public function setAddressLine1($line1) {
        $this->addressLine1 = $line1;
    }

    /* accessors, etc... */

    public function getCountry() {
        return $this->country;
    }

    public function format($type) {
        if ($type == "inline") {
            $formatter = new InlineAddressFormatter();
        } else if ($type == "multiline") {
            $formatter = new MultilineAddressFormatter();
        } else {
            $formatter = new NullAddressFormatter();
        }
        return $formatter->format(
            $this->getAddressLine1(), 
            $this->getAddressLine2(), 
            $this->getCity(), 
            $this->getState(), 
            $this->getPostalCode(), 
            $this->getCountry()
        );
    }
}

$addr = new Address();
$addr->setAddressLine1("123 Any St.");
$addr->setAddressLine2("Ste 200");
$addr->setCity("Anytown");
$addr->setState("AY");
$addr->setPostalCode("55555-0000");
$addr->setCountry("US");

echo($addr->format("multiline"));
echo("\n");

echo($addr->format("inline"));
echo("\n");

在 Address 对象上调用 format() 方法的代码可能看上去很棒 — 这段代码所做的是使用 Address 类,调用 format() 并完成。相反,Address 类就没那么幸运。它需要了解用于正确格式化的各种格式化方法,这可能使 Address 对象无法被其他人很好地重用,尤其是在其他人没有兴趣在 format() 方法中使用格式化方法类的情况下。虽然使用 Address 的代码没有许多依赖关系,但是 Address 类却有大量代码,而它可能只是一个简单的数据对象。

Address 类与知道如何格式化 Address 对象的实现类紧密耦合。

好习惯:在对象之间松散耦合
在构建优秀的 OO 设计时,必须考虑称为关注点分离(Separation of Concerns,SoC)的概念。SoC 指尝试通过真正关注的内容分离对象,从而降低耦合度。在最初的 Address 类中,它必须关注如何进行格式化。这可能不是优秀的设计。然而,Address 类应当考虑 Address 的各部分,而某种格式化方法应当关注如何正确格式化地址。

在清单 9 中,格式化地址的代码被移到接口、实现类和工厂中 — 养成 “使用接口” 的习惯。现在,AddressFormatUtils 类负责创建格式化方法并格式化 Address。任何其他对象现在都可以使用 Address 而不必担心要求获得格式化方法的定义。

清单 9. 在对象之间松散耦合的好习惯

<?php

interface AddressFormatter {
    public function format($addressLine1, $addressLine2, $city, $state, $postalCode, $country);
}

class MultiLineAddressFormatter implements AddressFormatter {
    public function format($addressLine1, $addressLine2, $city, $state, 
        $postalCode, $country)
    {
        return sprintf("%s\n%s\n%s, %s %s\n%s", 
            $addressLine1, $addressLine2, $city, $state, $postalCode, $country);
    }
}

class InlineAddressFormatter implements AddressFormatter {
    public function format($addressLine1, $addressLine2, $city, $state, $postalCode, $country){
        return sprintf("%s %s, %s, %s %s %s", $addressLine1, $addressLine2, $city, $state, $postalCode, $country);
    }
}

class AddressFormatUtils {
    public static function formatAddress($type, $address) {
        $formatter = AddressFormatUtils::createAddressFormatter($type);
        
        return $formatter->format(
			$address->getAddressLine1(), 
            $address->getAddressLine2(), 
            $address->getCity(), 
			$address->getState(), 
            $address->getPostalCode(), 
            $address->getCountry()
		);
    }
    
    private static function createAddressFormatter($type) {
        if ($type == "inline") {
            $formatter = new InlineAddressFormatter();
        } else if ($type == "multiline") {
            $formatter = new MultilineAddressFormatter();
        } else {
            $formatter = new NullAddressFormatter();
        }
        return $formatter;
    }
}

$addr = new Address();
$addr->setAddressLine1("123 Any St.");
$addr->setAddressLine2("Ste 200");
$addr->setCity("Anytown");
$addr->setState("AY");
$addr->setPostalCode("55555-0000");
$addr->setCountry("US");

echo(AddressFormatUtils::formatAddress("multiline", $addr));
echo("\n");

echo(AddressFormatUtils::formatAddress("inline", $addr));
echo("\n");

当然,缺点是只要使用模式,通常就意味着工件(类、文件)的数量会增加。但是,通过减少每个类中的维护可以弥补这个缺点,甚至在获得正确的可重用性时反而可以减少工件量。

 

您是橡皮;我是胶水
具有高度内聚力的 OO 设计被集中并组织到相关模块中。了解 “关注点” 对于决定如何紧密地联系函数和类十分重要。

坏习惯:降低内聚力
当设计的内聚力较低 时,它就不能良好地组织类和方法。意大利面条式代码(spaghetti code)一词通常用于描述捆绑在一起并且具有低内聚力的类和方法。清单 10 提供了意大利面条式代码的示例。相对通用的 Utils 类将使用许多不同对象并且有许多依赖关系。它执行很多操作,因而很难实现重用。

清单 10. 降低内聚力的坏习惯

<?php
class Utils {
    public static function formatAddress($formatType, $address1, $address2, $city, $state) {
        return "some address string";
    }
    
    public static function formatPersonName($formatType, $givenName, $familyName) {
        return "some person name";
    }
    
    public static function parseAddress($formatType, $val) {
        // real implementation would set values, etc...
        return new Address();
    }
    
    public static function parseTelephoneNumber($formatType, $val) {
        // real implementation would set values, etc...
        return new TelephoneNumber();
    }
}

好习惯:利用高内聚力
高内聚力 指将相互关联的类和方法分组在一起。如果方法和类都具有高度的内聚力,则可以轻松地分解整个组而不影响设计。具有高内聚力的设计将提供降低耦合的机会。清单 11 显示了被较好组织到类中的两个方法。AddressUtils 类将包含用于处理 Address 类的方法,显示了与地址相关的方法之间的高度内聚力。同样地,PersonUtils 将包含专门处理 Person 对象的方法。这两个拥有高度内聚力方法的新类的耦合性都很低,因为可以完全独立地使用。

清单 11. 高内聚力的好习惯

<?php
class AddressUtils{
    public static function formatAddress($formatType, $address1, $address2, $city, $state) {
        return "some address string";
    }
    
    public static function parseAddress($formatType, $val) {
        // real implementation would set values, etc...
        return new Address();
    } 
}

class PersonUtils {
    public static function formatPersonName($formatType, $givenName, $familyName) {
        return "some person name";
    }
    
    public static function parsePersonName($formatType, $val) {
        // real implementation would set values, etc...
        return new PersonName();
    }
}

限制传播
我经常对我所在的软件团队(我在其中担任技术主管或架构师)的成员提起,OO 语言最大的敌人是复制和粘贴操作。当在缺少预先 OO 设计的情况下使用时,没有任何操作会像在类之间复制代码那样具有破坏性。无论何时,如果想将代码从一个类复制到下一个类中,请停下来并考虑如何使用类层次结构利用类似功能或相同功能。在大多数情况下,使用优秀设计后,您将会发现完全没有必要复制代码。

坏习惯:不使用类层次结构
清单 12 显示了部分类的简单示例。它们从重复的字段和方法开始 — 从长远来看,不利于应用程序作出更改。如果 Person 类中有缺陷,则 Employee 类中也很可能有一个缺陷,因为看上去似乎实现是在两个类之间复制的。

清单 12. 不使用层次结构的坏习惯

<?php
class Person {
    private $givenName;
    private $familyName;
}

class Employee {
    private $givenName;
    private $familyName;
}

继承 是一个很难入手的习惯,因为构建正确继承模型的分析通常需要花费大量时间。反过来,使用 Ctrl+C 组合键和 Ctrl+V 组合键构建新实现只需几秒钟。但是省下的这部分时间通常会在维护阶段迅速抵销掉,因为应用程序实际上将花费大量进行维护。

好习惯:利用继承
在清单 13 中,新 Employee 类将扩展 Person 类。它现在将继承所有通用方法并且不重新实现这些方法。此外,清单 13 显示了抽象方法的用法,演示如何将基本功能放入基类中以及如何阻止实现类使用特定函数。

清单 13. 利用继承的好习惯

<?php
abstract class Person {
    private $givenName;
    private $familyName;
    
    public function setGivenName($gn) {
        $this->givenName = $gn;
    }
    
    public function getGivenName() {
        return $this->givenName;
    }
    
    public function setFamilyName($fn) {
        $this->familyName = $fn;
    }
    
    public function getFamilyName() {
        return $this->familyName;
    }
     
    public function sayHello() {
        echo("Hello, I am ");
        $this->introduceSelf();
    }
    
    abstract public function introduceSelf();
    
}

class Employee extends Person {
    private $role;
    
    public function setRole($r) {
        $this->role = $r; 
    }
    
    public function getRole() {
        return $this->role;
    }
    
    public function introduceSelf() {
        echo($this->getRole() . " " . $this->getGivenName() . " " . $this->getFamilyName());
    }
}

考虑使用模式
设计模式指对象和方法的常见交互,并且时间证明它可以解决某些问题。当您考虑使用设计模式时,您就需要了解类之间如何进行交互。它是构建类及其交互操作的简单方法,无需重蹈他人的覆辙,并从经过证明的设计中获益。

坏习惯:一次考虑一个对象
实际上没有适当的代码示例可以演示如何考虑使用模式(尽管有丰富的优秀示例可以显示模式实现)。但是,一般而言,您知道在满足以下条件时一次只能考虑一个对象:

1、不会提前设计对象模型。
2、开始编写单一方法的实现,而无需去掉大部分模型。
3、在交谈中不使用设计模式名而宁愿谈论实现。

好习惯:同时添加模式中形成的对象
一般而言,当您在执行以下操作时就是在考虑使用模式:

1、提前构建类及其交互操作。
2、根据模式套用类。
3、使用模式名,如 Factory、Singleton 和 Facade。
4、去掉大部分模型,然后开始添加实现。

结束语
在 PHP 中养成良好的 OO 习惯将帮助您构建更稳定、更易于维护和更易于扩展的应用程序。记住:

1、保持谨慎。
2、做个好邻居。
3、避免看到美杜莎。
4、利用最弱的链接。
5、您是橡皮,我是胶水。
6、限制传播。
7、考虑使用模式。
当您养成并应用这些习惯后,您很可能会惊讶地发现应用程序在质量上的飞跃。

 

shell判断文件,目录是否存在或者具有权限

#!/bin/sh 
 
myPath="/var/log/httpd/"
myFile="/var/log/httpd/access.log"
 
#这里的-x 参数判断$myPath是否存在并且是否具有可执行权限
if [ ! -x "$myPath" ]
     then
        mkdir "$myPath"
fi
 
#这里的-d 参数判断$myPath是否存在
if [ ! -d "$myPath" ]
     then
        mkdir "$myPath"
fi
 
#这里的-f参数判断$myFile是否存在
if [ ! -f "$myFile" ]
    then
        touch "$myFile"
fi
 
#其他参数还有-n,-n是判断一个变量是否是否有值
if [ ! -n "$myVar" ]
    then
        echo "$myVar is empty"
        exit 0
fi
 
#两个变量判断是否相等
if [ "$var1" = "$var2" ]
    then
        echo '$var1 eq $var2'
    else
        echo '$var1 not eq $var2'
fi