PHP 实现单例模式的两种方式

第一种方式是由类维护一个静态属性,该属性是对象实例的引用,示例代码如下:

class Singleton {
        private static $_instance = null;
        public static function getInstance() {
                is_null(self::$_instance) && self::$_instance = new self();
                return self::$_instance;
        }
        private function __construct() { // 构造函数 private,防止类在外部被 new 出来
                ;
        }
}

 

第二种方式是由静态方法里面的一个静态变量返回对象实例的引用,示例代码如下:

class Singleton {
        public static function getInstance() {
                static $_instance = null;
                is_null($_instance) && $_instance = new self();
                return $_instance;
        }
        private function __construct() { // 构造函数 private,防止类在外部被 new 出来
                ;
        }
}

 

这两种实现有什么区别呢?效果是一样的吧~

今天才知道原来 PHP 5.3.0 之前版本是不支持延迟绑定的,赶紧把前两天写的类改过来,因为公司线上环境还是用的 5.2 系列版本。关于 PHP 延迟绑定(官网称呼其为:后期静态绑定)请看官网说明:http://www.php.net/manual/zh/language.oop5.late-static-bindings.php

一个更改 hosts 的 PHP 脚本

有这样一个需求,我有多个网址希望在不同的时候对应不同的 ip,如果一个个配 hosts,这工作显得有些繁琐。写了如下脚本来批量更改。

<?php

define('HOST_FILE', 'C:\Windows\System32\drivers\etc\hosts');

$hm = new HostManage(HOST_FILE);

$env = $argv[1];
if (empty($env)) {
        $hm->delAllGroup();
} else {
        $hm->addGroup($env);
}

class HostManage {

        // hosts 文件路径
        protected $file;
        // hosts 记录数组
        protected $hosts = array();
        // 配置文件路径,默认为 __FILE__ . '.ini';
        protected $configFile;
        // 从 ini 配置文件读取出来的配置数组
        protected $config = array();
        // 配置文件里面需要配置的域名
        protected $domain = array();
        // 配置文件获取的 ip 数据
        protected $ip = array();

        public function __construct($file, $config_file = null) {
                $this->file = $file;
                if ($config_file) {
                    $this->configFile = $config_file;
                } else {
                    $this->configFile = __FILE__ . '.ini';
                }
                $this->initHosts()
                        ->initCfg();
        }

        public function __destruct() {
                $this->write();
        }

        public function initHosts() {
                $lines = file($this->file);
                foreach ($lines as $line) {
                        $line = trim($line);
                        if (empty($line) || $line[0] == '#') {
                                continue;
                        }
                        $item = preg_split('/\s+/', $line);
                        $this->hosts[$item[1]] = $item[0];
                }
                return $this;
        }

        public function initCfg() {
                if (! file_exists($this->configFile)) {
                        $this->config = array();
                } else {
                        $this->config = (parse_ini_file($this->configFile, true));
                }
                $this->domain = array_keys($this->config['domain']);
                $this->ip = $this->config['ip'];
                return $this;
        }

        /**
         * 删除配置文件里域的 hosts 
         */
        public function delAllGroup() {
                foreach ($this->domain as $domain) {
                        $this->delRecord($domain);
                }
        }

        /**
         * 将域配置为指定 ip
         * @param type $env
         * @return \HostManage
         */
        public function addGroup($env) {
                if (! isset($this->ip[$env])) {
                        return $this;
                }
                foreach ($this->domain as $domain) {
                        $this->addRecord($domain, $this->ip[$env]);
                }
                return $this;
        }

        /**
         * 添加一条 host 记录
         * @param type $ip
         * @param type $domain
         */
        function addRecord($domain, $ip) {
                $this->hosts[$domain] = $ip;
                return $this;
        }

        /**
         * 删除一条 host 记录
         * @param type $domain
         */
        function delRecord($domain) {
                unset($this->hosts[$domain]);
                return $this;
        }

        /**
         * 写入 host 文件
         */
        public function write() {
                $str = '';
                foreach ($this->hosts as $domain => $ip) {
                        $str .= $ip . "\t" . $domain . PHP_EOL;
                }
                file_put_contents($this->file, $str);
                return $this;
        }

}

示例配置文件如下:

# 域名
[domain]
a.example.com=1 # 请无视这个 =1,因为使用了 parse_ini_file 这个函数来解析,如果后面不带值,就获取不到这条记录了
b.example.com=1
c.example.com=1

# ip 记录
[ip]
local=127.0.0.1
dev=192.168.1.100

使用方法:

php hosts.php local # 域名将指向本机 127.0.0.1
php hosts.php dev # 域名将指向开发机 192.168.1.100
php hosts.php # 删除域名的 hosts 配置

写完后,发现,这明明就是只需要一次查找替换就能完成的工作嘛

小心 foreach 中使用引用,否则可能数据出错

有实例代码如下:

// 有一个产品分为标准版和高级版
$products = [
	'standard' => [
		'price' => 100, // 原价
		'discount' => 0.8, // 折扣
	],
	'advanced' => [
		'price' => 200,
		'discount' => 0.7,
	],
]; 

// 算出节省的金额
foreach ($products as &$product) {
	$product['save'] = $product['price'] * (1 - $product['discount']);
}

// 输出产品价格信息
foreach ($products as $product) {
	print_r($product);
}

 

运行,输出的结果却是:

$ php test_foreach.php
Array
(
    [price] => 100
    [discount] => 0.8
    [save] => 20
)
Array
(
    [price] => 100
    [discount] => 0.8
    [save] => 20
)

 

不知道看官您看出问题没有,找这个问题花了我老长时间,foreach 里面对值使用使用后,loop 结束,值还存在,所以在 foreach 循环结束后应手动 unset 该变量。

改正后的 PHP 代码为:

$products = [
	'standard' => [
		'price' => 100,
		'discount' => 0.8,
	],
	'advanced' => [
		'price' => 200,
		'discount' => 0.7,
	],
]; 

foreach ($products as &$product) {
	$product['save'] = $product['price'] * (1 - $product['discount']);
}
unset($product); // ******** <--注意这里!!! **********

foreach ($products as $product) {
	print_r($product);
}

 

php.net 上关于 foreach 里面使用引用的警告说明如下:

Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().

PHP in_array 函数的一个问题

有如下代码:

var_dump(in_array('0', array('abc', 'def')));
var_dump(in_array(0, array('abc', 'def')));
var_dump(in_array('1', array('abc', 'def')));
var_dump(in_array(1, array('abc', 'def')));

会输出:

boolean false
boolean true
boolean false
boolean false

第 2 个输出与我们预想的不一样,为什么会出现这种情况呢?

in_array 函数在进行比较的时候,会先进行类型转换,字符串和数字进行比较,会将字符串转换为数字,而字符串被强制为数字 0,因此 in_array(0, array[‘abc’, ‘def’]); 为 TRUE

为了避免这种意外,可以在 in_array 函数添加第三个参数 in_array(0, array[‘abc’, ‘def], TRUE);,那么在比较的时候除了比较值还会比较类型。

storytlr 登录出现问题,删除 apc 后正常

昨天开始,安装在服务器上的 storytlr 出现问题,登录的时候,页面不给任何反馈,查看 apache 错误日志也没有错误产生,查看程序自带的错误记录,也没能看出任何问题。但是我本地也安装来一个 storytlr 正常运行,没有出现这个问题,代码都是一样的,按说不应该啊。于是我对比来一下本机和服务器 phpinfo() 的输出,发现服务器多加载一个 apc 扩展,抱着试一试的态度,我将服务器的 php-apc 扩展删除了,重启 apache2 服务,正常,搞定!不知道为什么会这样,我对 storytlr 代码不熟,对它使用的 zend framework 也没用过,还真有点没底呢,不过现在问题解决了就好。

为什么会出现这个问题呢?

CodeIgniter 控制器方法不能和控制器类名同名

我写了这样一段代码:

class Login extends CI_Controller {

    public function index()	{
        $this->load->library('form_validation');
        /* ... other code ... */
        $this->load->view('login_form');
    }

    public function login() {
        $this->load->helper('url');
        /* ... other code ... */
    }
}

 

运行,无论怎样都有错,提示 $this 的 load, session 等属性不存在。找了半天才知道是存在与控制器类名相同的方法引起的。

但是为什么会是这样呢?有时间研究一下。

php 实现计数排序

算法还是很重要嘀~
直接贴代码:

<?php
/*
 * count_sort.php 计数排序
 * cli 模式运行 php count_sort.php 3 4 5 6 3 2 4 56 4 3 2 8 1
 */
$a = $argv;
unset($a[0]);
$n = count($a);
// 找出数组中最大的数
$k = 0;
for ($i = 1; $i <= $n; $i++) {
	if ($k < $a[$i])
		$k = $a[$i];
}
// 初始化数组 $c
$c[0]= 0;
for ($i = 1; $i <= $k; $i++)
	$c[$i] = 0;

for ($i = 1; $i <= $n; $i++) {
	$c[$a[$i]]++;
}// 此时 $c[$i] 包含等于 $i 的元素个数

for ($i = 1; $i <= $k; $i++) {
	$c[$i] = $c[$i-1] + $c[$i];
}// 此时 $c[$i] 包含小于或等于 $i 的元素个数

for ($i = $n; $i >= 1; $i--) {
	$b[$c[$a[$i]]] = $a[$i];
	$c[$a[$i]]--;
}
// 输出已排好序的数组
for ($i = 1; $i <= $n; $i++)
	echo $b[$i] . ' ';
echo "\n";

 

今天又重新梳理了一遍,不能根据这个代码看出计数排序是稳定排序:

<?php
/** 
 * 计数排序
 */
$nums = [23,12,23,34,23,456,23,12,23,45,23,12,1234];

$n = count($nums);

// 找出数组中最大的数
$max = $nums[0];
for ($i = 1; $i < $n; $i++) {
        if ($max < $nums[$i]) {
                $max = $nums[$i];
        }
}

// 初始化用来计数的数组
for ($i = 0; $i <= $max; $i++) {
        $cnt[$i] = 0;
}

// 下面的 for loop 处理完毕后,$cnt[$i] 表示待排序数组中,值等于 $i 的元素个数
for ($i = 0; $i < $n; $i++) {
        $cnt[$nums[$i]]++;
}

// 下面的 for loop 处理完毕后,$cnt[$i] 表示待排序数组中,值小于等于 $i 的元素个数
for ($i = 1; $i <= $max; $i++) {
        $cnt[$i] = $cnt[$i-1] + $cnt[$i];
}

// 这一步比较难理解
for ($i = $n-1; $i >= 0; $i--) {
      $result[$cnt[$nums[$i]]] = $nums[$i];
      $cnt[$nums[$i]]--; // 前一个数找到位置后,那么和它值相同的数位置往前一步
}

for ($i = 1; $i < $n; $i++) {
        echo $result[$i], ' ';
}
echo PHP_EOL;

 

PHP 上传文件时没有错误提示,但是 $_POST 数组却为空

这是因为 post_max_size 的值小于 upload_max_filesize 的原因引起的。

默认 post_max_size=8M ,upload_max_filesize=2M 。当我将最大上传大小改为 10M 后,我再尝试上传大于 10M 的文件就出现如题的问题。

解决方法:

在 php.ini 设置,将 post_max_size 的值设置为大于 upload_max_filesize 的值。