月度归档:2015年06月

开发常用工具(备用)

版本控制
1 Git for Windows(原命名为msysgit, MS系统下的GIT)
说明:Windows下Git管理软件不二的选择
下载:https://git-for-windows.github.io/

这个工具使得在MS系统以类linux的操作方式来使用Git。这个工具是如何做到的?实际上它集成了一个叫MinGW的工具(http://www.mingw.org/),这个工具全名Minimalist GNU for Windows, 从名称就可以知道,它是GNU软件在Windows下运行的工具,自然Git就可以跑在它上面了,MinGW的Shell是MSYS,它是搭配MinGW来使用的,安装了Git for Windows后看到的Git for Bash,实际就是这么一个东西。

2 TortoiseGit
说明:Git客户端,Windows下最流行的客户端
下载:http://download.tortoisegit.org
3 Gitlab
说明:Git代码托管
下载:https://about.gitlab.com/
使用:http://blog.ifeeline.com/1628.html

文件比较工具:
1 WinMerge
首先是免费的,支持文件和目录比较。
2 Beyond Compare(http://www.scootersoftware.com/ 标准版30美金)专业级的文件和文件夹比较,支持包括二进制在内多种格式文件比较,跟WinMerge相比,它提供了一个右击菜单,可以开速加入文件。
http://blog.ifeeline.com/1880.html

数据库客户端(http://blog.ifeeline.com/1588.html)
1 Navicat
2 phpMyAdmin

虚拟机
VMware Workstation

IDE
Zend Studio

编辑器
EditPlus
Dreamweaver

Shell & FTP
Xshell
WinScp
FileZilla Client
http://blog.ifeeline.com/1865.html

开发环境
Wamp
http://blog.ifeeline.com/1464.html

Yaf中自动装载

Yaf中提供了Yaf_Loader来装载资源。实现单利模式。一般是由Yaf_Application负责初始化的。初始化的过程就是调用Yaf_Loader的getInstance()方法:

public static Yaf_Loader Yaf_Loader::getInstance( string  $local_library_directory = NULL , string  $global_library_directory = NULL );

默认传递两个路径信息,Yaf_Application初始化时把$local_library_directory赋值为配置文件中的ap.library,$global_library_directory赋值为PHP配置中的ap.library。换句话说就是这个Loader就搜索这两个目录,如果有第三方的类需要自动装载,放入这两个目录即可。

Yaf_Loader提供import方法,可以导入任意位置的文件(类似include)。
Yaf_Loader提供registerLocalNamespace方法注册本地名空间,意思就是在本地类库目录加载对应名空间(不去全局类库目录)。在其它的自动装载的实现方案中,注册命名空间可以让Loader快速定位到代码位置(比如$loader->set(“Zend”,”/library”))。Yaf_Loader无法指定一个命名空间对应一个路径。

Yaf_Loader自然使用了PHP中的spl_autoload实现。在Yaf扩展可用配置中(写入PHP的配置),有配置项yaf.use_spl_autoload,默认值为0(关闭),文档说明:开启的情况下, Yaf在加载不成功的情况下, 会继续让PHP的自动加载函数加载, 从性能考虑, 除非特殊情况, 否则保持这个选项关闭,意思就是说,如果关闭,后续注册到spl_autoload的自动装载函数将不生效(被剥夺了),如果要使用第三方的自动装载方案,务必把0改为1。

Yaf_Loader把类转载限定到两个目录,大部分情况下,都能满足需要,不过如果使用的第三方类库比较多,要管理它们的版本,就需要引入包管理器。

Yaf框架还需要映射models,controllers(分模块也一样),内部映射细节应该是根据类名来获取文件名然后import,在models中的类也可以添加命名空间,但是最终的类名必须添加Model后缀(控制器是Controller),实例化一个Model是一个显式的可控的操作,但是实例化一个控制器是框架自动完成的操作,所以控制器添加命名空间是不妥的,至少在Yaf中不妥。

Yaf中可以使用反斜杠方式使用Yaf的类,不过需要在PHP配置中添加:

yaf.use_namespace = 1

这样,Yaf_Application就可以以Yaf\Application来用,不过要注意的是,最后以Abtract结尾的类,比如Yaf_Bootstrap_Abstract,只能改为Yaf\Bootstrap_Abstract,最后的下划线不能换掉,否则就是语法错误,就把Bootstrap_Abstract整体看成一个类名就好了。当然,如果要用到这些类,比如用到Yaf\Application,先use一下,然后接下来就可以直接使用Application了。

一份实例:

<?php
use Yaf\Application;
use Yaf\Bootstrap_Abstract as BootstrapAbstract;
use Yaf\Dispatcher;
use Yaf\Route\Regex;
use Zend\Db\Adapter\Adapter;

class Bootstrap extends BootstrapAbstract {
	private $_config;

	public function _init(Dispatcher $dispatcher) {
		$this->_config = Application::app()->getConfig();
		Registry::set('config', $this->_config);

		///
		if($this->_config->application->showError){
			error_reporting (-1);
			ini_set('display_errors','On');
		}
		
// 		$library = ROOT_PATH.'/library';
// 		$actions = APPLICATION_PATH.'/actions';
// 		$models  = APPLICATION_PATH.'/models';
// 		//get_include_path()
// 		$includePath = array(
// 			$library,
// 			$actions,
// 			$models
// 		);
// 		set_include_path(implode(PATH_SEPARATOR, $includePath));

// 		if(file_exists(ROOT_PATH.'/vendor/autoload.php')){
// 			$loader = include ROOT_PATH.'/vendor/autoload.php';
// 		}else{
// 			exit("Composer Autoload Failed. ");
// 		}
// 		$loader->setUseIncludePath(true);
// 		$loader->setPsr4("Zend\\",$library.'/zf2_psr4');
// 		Registry::set('loader', $loader);
		
		$dispatcher->autoRender(FALSE);
		
		Registry::set('adapter',function(){
			$mysqlMasterConfig = $this->_config->mysql->master->toArray();
			$adapter = new Adapter($mysqlMasterConfig);
			return $adapter;
		});
	}
	
	public function _initRoutes() {
		//Dispatcher::getInstance()->getRouter()->addRoute("xxx", new Regex(,,));
	}
		
	public function _initPlugin(Dispatcher $dispatcher) {
// 		$authPlugin = new AuthPlugin();
// 		$dispatcher->registerPlugin($authPlugin);
	}
	
	public function _initLayout() {
// 		$layout = new Zend_Layout();
	
// 		$layoutPath = APPLICATION_PATH . '/layouts';
// 		if(isset($this->_config->application->layoutPath)) {
// 			$layoutPath = $this->_config->application->layoutPath;
// 		}
	
// 		$layout->setLayoutPath($layoutPath);
	
// 		$layout->getView()->headLink()->appendStylesheet('/css/jquery-ui.min.css');
// 		$layout->getView()->headScript()->appendFile('/js/jquery.min.js');
// 		$layout->getView()->headScript()->appendFile('/js/jquery-ui.min.js');
	
// 		Yaf_Registry::set('layout', $layout);
	}
}

PHP 注册表类

<?php
// 注册表
class Registry{
	public static $instance = array();

	public static function set($name,$callBack){
		if(!isset(self::$instance[$name])){
			self::$instance[$name] = $callBack;
		}
	}

	public static function get($name){
		if(isset(self::$instance[$name])){
			if(is_callable(self::$instance[$name])){
				$func = self::$instance[$name];
				self::$instance[$name] = $func();
			}
			return self::$instance[$name];
		}
		return false;
	}
	
	public static function has($name){
		if(isset(self::$instance[$name])){
			return true;
		}
		return false;
	}
	
	public static function del($name){
		if(self::has($name)){
			unset(self::$instance[$name]);
		}
	}
}

这个注册表跟一般的注册表实现没有太多不一样,主要的差异在于在get一个变量时,如果是回调函数,则先执行这个回调函数,然后把执行后的结果返回。这样改进的目的在于希望一个资源在被使用时才初始化它。比如框架中可能用到多种数据库(MySQL MongoDB),不需要每次请求中都首先初始化这些链接,可能有些链接根本用不到某个数据库链接,那么资源在使用到时才是初始化就比较有意义。例子:

Registry::set('adapter',function(){
	$mysqlMasterConfig = $this->_config->mysql->master->toArray();
	$adapter = new Adapter($mysqlMasterConfig);
	return $adapter;
});

//在需要使用的地方:
$adapter = Registry::get('adapter');

这个好处在于一旦初始化,后续如果再次调用,使用的都是同一份实例。特别是可能要使用多种数据源时,而又可能不会同时都使用时(一个或多个),就特别有用。

Zend Framework 2.x 之 Zend\Db

zend-db

Driver是数据库驱动抽象,负责和具体的数据库建立链接,如果要获取这个链接,就需要通过Adapter的Driver。Platform是针对不同平台的抽象,比如不同平台用不同的字符把字段圈起来。

Adapter需要确定Driver 和 Platform,对不同驱动和平台,Adapter提供了一个通用接口,这个就是适配器设计模式了。

$adapter = new Zend\Db\Adapter\Adapter(array(
		'driver' => 'pdo',
		'dsn' => 'mysql:dbname=test;hostname=localhost',
		'username' => 'root',
		'password' => '',
		'driver_options' => array(
				PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
		),
));

$adapter = new Zend\Db\Adapter\Adapter(array(
    'hostname' => 'localhost',
    'database' => 'test',
    'username' => 'root',
    'password' => '',
    
    'driver' => 'mysqli',
    'charset' =>'utf8'
    
    //'driver' => 'pdo_mysql',
    //'driver_options' => array(
    //    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
    //),
));
//当不使用PDO时,字符集的指定使用charset,有时候我们有理由相信mysqli驱动会比PDO好,因为它抽象多了一层。注意:不管是否使用PDO,driver_options都可以使用,不过需要指定针对具体驱动的选项。

这是一段范本代码。看看手册中的说法:
“It is important to know that by using this style of adapter creation, the Adapter will attempt to create any dependencies that were not explicitly provided. A Driver object will be created from the configuration array provided in the constructor. A Platform object will be created based off the type of Driver class that was instantiated. And lastly, a default ResultSet object is created and utilized. Any of these objects can be injected, to do this, see the next section.”

如果使用这种风格创建适配器,适配器试图去创建所有的没有明确提供的依赖(依赖具体的驱动,平台)。一个Driver对象将根据适配器构造函数中提供的数组参数来创建。一个Platform对象将根据Driver被实例化时的类的类型来创建。最后,一个默认的ResultSet对象被创建并可用。所有这些对象都是可注入的。

use Zend\Db\Adapter\Platform\PlatformInterface;
use Zend\Db\ResultSet\ResultSet;

class Zend\Db\Adapter\Adapter {
    public function __construct($driver, PlatformInterface $platform = null, ResultSet $queryResultSetPrototype = null)
}

玛尼,意思就是你可以通过构造函数去指定具体的驱动,平台,结果集。(第一参数可以是配置数组 或 Driver对象)

适配器提供一个相对原始的query()方法,可以预处理SQL语句:

$result = $adapter->query('SELECT * FROM `site` WHERE `id` = ?', array(5));

内部工作流程:
1 创建一个新的Statement对象
2 如果需要,预处理一个数组到ParameterContainer
3 把ParameterContainer注入到Statement对象
4 执行Statement对象,处理结果对象
5 检查结果对象确定提供的sql是查询或or a result set producing statement
6 如果是结果的语句,克隆ResultSet的prototype,inject Result as datasource
7 否则返回Result

也可直接执行SQL语句,参考:

$r = $adapter->query('ALTER TABLE `site` ADD `test` int NULL', Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE);

一般执行最多的是SELECT INSERT UPDATE DELETE语句,以下是一个范本:

$sttm = $adapter->query('SELECT * FROM `site`');
$resultSet = $sttm ->execute();
foreach($resultSet as $rr){
	print_r($rr);
}

最好可以去看看query()方法的实现:

    public function query($sql, $parametersOrQueryMode = self::QUERY_MODE_PREPARE)
    {
        if (is_string($parametersOrQueryMode) && in_array($parametersOrQueryMode, array(self::QUERY_MODE_PREPARE, self::QUERY_MODE_EXECUTE))) {
            $mode = $parametersOrQueryMode;
            $parameters = null;
        } elseif (is_array($parametersOrQueryMode)) {
            $mode = self::QUERY_MODE_PREPARE;
            $parameters = $parametersOrQueryMode;
        } else {
            throw new Exception\InvalidArgumentException('Parameter 2 to this method must be a flag, an array, or ParameterContainer');
        }

        if ($mode == self::QUERY_MODE_PREPARE) {
            $this->lastPreparedStatement = null;
            $this->lastPreparedStatement = $this->driver->createStatement($sql);
            $this->lastPreparedStatement->prepare();
            if (is_array($parameters) || $parameters instanceof ParameterContainer) {
                $this->lastPreparedStatement->setParameterContainer((is_array($parameters)) ? new ParameterContainer($parameters) : $parameters);
                $result = $this->lastPreparedStatement->execute();
            } else {
                return $this->lastPreparedStatement;
            }
        } else {
            $result = $this->driver->getConnection()->execute($sql);
        }

        if ($result instanceof Driver\ResultInterface && $result->isQueryResult()) {
            $resultSet = clone $this->queryResultSetPrototype;
            $resultSet->initialize($result);
            return $resultSet;
        }

        return $result;
    }

第二参数默认是预处理(self::QUERY_MODE_PREPARE)。首先如果是字符串(预处理 或 直接执行),参数就是null,如果是一个数组,那么就是预处理,参数就是这个数组。

如果是预处理,就调用驱动的createStatement($this->driver->createStatement($sql)),然后执行statement的prepare方法,如果有参数提供,就调用setParameterContainer方法,然后执行statement的execute方法,如果没有参数提供就直接把这个statement返回。

如果不是预处理,直接调用:$result = $this->driver->getConnection()->execute($sql);

最后判断是否是查询,是查询就返回结果集。否则就是一个结果对象。

总结,三种可能,statement对象,结果对象,结果集对象。首先根据是否是预处理,如果是但是没有绑定参数,直接返回statement对象,如果需要绑定参数,则把执行结果用结果对象保存起来,如果是执行语句,也把结果用结果对象保存,最后根据sql是否是查询,确定是返回结果集还是结果对象。

事实:如果返回结果集,必定是查询。对于查询,这里只是再次封装一遍而已。 实际不用再次封装也是可以使用的。以下是一些例子,对比返回的类型,可以更好理解:

// 不指定第二参数,为预处理模式,没有需要的参数绑定,直接返回Statement
// Zend\Db\Adapter\Driver\Pdo\Statement
$stmt = $adapter->query('SELECT * FROM `site`');
// Zend\Db\Adapter\Driver\Pdo\Result
$result= $stmt->execute();
foreach($result as $rr){
	//print_r($rr);
}

// 第二参数为一个数组,为预处理模式,绑定的参数为指定的数组,内部执行Statement的execute
// 返回结果或结果集
// Zend\Db\ResultSet\ResultSet
$resultSet = $adapter->query('SELECT * FROM `site` WHERE `id` > ?', array(1));
foreach($resultSet as $rr){
	//print_r($rr);
}

// 第二参数直接指定为执行模式,返回结果或结果集
// 可以看做和上面用法等同
// Zend\Db\ResultSet\ResultSet
$resultSet = $adapter->query('SELECT * FROM `site`', Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE);
foreach($resultSet as $rr){
	//print_r($rr);
}

// 直接执行模式,返回结果(一般INSERT UPDATE这些返回一个结果,查询返回结果集)
// Zend\Db\Adapter\Driver\Pdo\Result
$result = $adapter->query("INSERT INTO `site`(`platform_id`,`name`) values('2','中国')", Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE);
// 返回插入的ID
echo $result->getGeneratedValue();


// Zend\Db\Adapter\Driver\Pdo\Result
$result = $adapter->query("UPDATE `site` SET `name` = '中国' where id=12", Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE);
// 更新受影响的行数
echo $result->getAffectedRows();

这里需要说明的是,getAffectedRows()是Zend\Db\Adapter\Driver\Pdo\Result的方法,返回的是受影响的行数,而Zend\Db\ResultSet\ResultSet并没有这个方法,Zend\Db\ResultSet\ResultSet的toArray()可以把结果集转换成一个数组输出。

Zend\Db\ResultSet\ResultSet是一个更抽象的东西,封装了Zend\Db\ResultSet\Result。

在理解了以上内容的前提下,我们可以不使用query(),而直接使用statement。

// Zend\Db\ResultSet\Result
$statement = $adapter->createStatement($sql, $optionalParameters);
$result = $statement->execute();

注意看上面的例子中的$adapter->createStatement(),这个方法内部实际调用$this->driver->createStatement(),driver中提供了三个对象:

Zend\Db\Adapter\Driver\ConnectionInterface
Zend\Db\Adapter\Driver\StatementInterface
Zend\Db\Adapter\Driver\ResultInterface

Statement 和 Result上面已经讨论过了(通过adapter中间接调用,adapter中的query()会调用到driver的execute)。以下是driver中必须知道的方法:

$cnn = $adapter->getDriver()->getConnection();
$cnn->beginTransaction(); 	//开始事务
$cnn->commit();			//提交事务
$cnn->rollback();		//回滚事务
$cnn->isConnected()		//是否已链接
$cnn->getLastGeneratedValue()   //最后插入的ID

适配器在自动构建SQL语句时,需要它的另一个对象支持,Zend\Db\Adapter\Platform。直接看例子:

$platform = $adapter->getPlatform();
// or
$platform = $adapter->platform; // magic property access


/** @var $adapter Zend\Db\Adapter\Adapter */
/** @var $platform Zend\Db\Adapter\Platform\Sql92 */
$platform = $adapter->getPlatform();

// "first_name"
echo $platform->quoteIdentifier('first_name');

// "
echo $platform->getQuoteIdentifierSymbol();

// "schema"."mytable"
echo $platform->quoteIdentifierChain(array('schema','mytable')));

// '
echo $platform->getQuoteValueSymbol();

// 'myvalue'
echo $platform->quoteValue('myvalue');

// 'value', 'Foo O\\'Bar'
echo $platform->quoteValueList(array('value',"Foo O'Bar")));

// .
echo $platform->getIdentifierSeparator();

// "foo" as "bar"
echo $platform->quoteIdentifierInFragment('foo as bar');

// additionally, with some safe words:
// ("foo"."bar" = "boo"."baz")
echo $platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', array('(', ')', '='));

Zend\Db\Sql\Sql提供了比较强大的,面向对象的SQL书写方法。

use Zend\Db\Sql\Sql;
$sql = new Sql($adapter);
$select = $sql->select(); // @return Zend\Db\Sql\Select
$insert = $sql->insert(); // @return Zend\Db\Sql\Insert
$update = $sql->update(); // @return Zend\Db\Sql\Update
$delete = $sql->delete(); // @return Zend\Db\Sql\Delete

Sql对象接收$adapter,第二参数可以是表名。具体例子:

###返回一个Statement,然后调用它的execute方法返回结果集
use Zend\Db\Sql\Sql;
$sql = new Sql($adapter);
$select = $sql->select();
$select->from('foo');
$select->where(array('id' => 2));

$statement = $sql->prepareStatementForSqlObject($select);
$results = $statement->execute();

###换成查询字符串,传递到适配器的query()方法
use Zend\Db\Sql\Sql;
$sql = new Sql($adapter);
$select = $sql->select();
$select->from('foo');
$select->where(array('id' => 2));

$selectString = $sql->getSqlStringForSqlObject($select);
$results = $adapter->query($selectString, $adapter::QUERY_MODE_EXECUTE);


### 指定第二参数,不需要指定表。
use Zend\Db\Sql\Sql;
$sql = new Sql($adapter, 'foo');
$select = $sql->select();
$select->where(array('id' => 2)); // $select already has the from('foo') applied

这部分的内容,如果熟悉SQL,很容易就可以掌握,实际就是以面向对象的方法构建可以适配多平台的SQl。

可以使用以上例子那样使用Sql对象,不过大多配合TableGateway来使用。

可以直接实例化一个TableGateway对象,比如:

$tg = new Zend\Db\TableGateway\TableGateway(“test”,$adapter);

第一参数为表名,第二参数为适配器对象。

大部分情况下都不这样做,一般使用自己的类继承一下Zend\Db\TableGateway\TableGateway类,或者写一个公共类,然后所有类都从这个公共类继承。比如,我们可以把表和类对应起来,一一建立对应的TableGateway类,安装最佳实践,应该建立一个公共类,就算当前没有可以公共的方法(未来可能有),也是如此。每个表建立对应的TableGateway,我们就可以在类中添加复杂的业务逻辑(关注数据交互)。
tablegateway

构造函数在TableGateway中提供:

public function __construct($table,AdapterInterface $adapter){
	$this->table = $table;
	$this->adapter = $adapter;

	$this->sql = ($sql)?:new Sql($this->adapter,$this->table);

	$this->initialize();
}

可做如下封装:

// 常规封装
class MyTableGateway extends Zend\Db\TableGateway\TableGateway{
	protected $table = ‘my_table_gateway’;

	public function __construct($adapter){
		parent::__construct($this->table, $adapter);
	}
}

// 这个注入方式简单粗暴
class MyTableGateway extends Zend\Db\TableGateway\TableGateway{
	protected $table = ‘my_table_gateway’;

	public function __construct(){
		global $adapter;
		if($adapter instanceof Zend\Db\Adapter\Adapter){
			parent::__construct($this->table, $adapter);
		}else{
			//抛异常
		}
	}
}

// 或者使用
Registry::set(‘adapter’,$adapter);
class MyTableGateway extends Zend\Db\TableGateway\TableGateway{
	protected $table = ‘my_table_gateway’;

	public function __construct(){
		$adapter = Registry::get(‘adapter’);
		if($adapter instanceof Zend\Db\Adapter\Adapter){
			parent::__construct($this->table, $adapter);
		}else{
			//抛异常
		}
	}
}

// 更好的依赖注入
class Registry{
	public static $instance = array();
	
	public static function set($name,$callBack){
		if(!isset(self::$instance[$name])){
			self::$instance[$name] = $callBack;
		}
	}
	
	public static function get($name){
		if(isset(self::$instance[$name])){
			if(is_callable(self::$instance[$name])){
				$func = self::$instance[$name];
				self::$instance[$name] = $func();
			}
			return self::$instance[$name];
		}
		return false;
	}
}

Registry::set('adapter',function(){
	$adapter = new Zend\Db\Adapter\Adapter(array(
			'driver' => 'pdo',
			'dsn' => 'mysql:dbname=test;hostname=localhost',
			'username' => 'root',
			'password' => '',
			'driver_options' => array(
					PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
			),
	));
	return $adapter;
});

class MyTableGateway extends Zend\Db\TableGateway\TableGateway{
	protected $table = ‘my_table_gateway’;

	public function __construct(){
		$adapter = Registry::get(‘adapter’);
		if($adapter instanceof Zend\Db\Adapter\Adapter){
			parent::__construct($this->table, $adapter);
		}else{
			//抛异常
		}
	}
}

//最总改进范本
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Sql\Sql;

class MyTableGateway extends Zend\Db\TableGateway\TableGateway{
	protected $table = ‘my_table_gateway’;

		public function __construct(AdapterInterface $adapter = null, $features = null, ResultSetInterface $resultSetPrototype = null, Sql $sql = null){
			if($adapter instanceof Adapter){
				parent::__construct($this->table, $adapter, $features, $resultSetPrototype, $sql);
			}else{
				$adapter = Registry::get('adapter');
				if($adapter instanceof Adapter){
					parent::__construct($this->table, $adapter);
				}else{
					throw new Exception("Need an Zend\Db\Adapter object.");
				}
			}
}
}

最后一种实现依赖注入的方法是现代框架常用的方式,我这里的实现是简化了而已。把资源注册到一个容器,然后在用到时初始化资源。在具体的类中,并不直接依赖外部的类对象,所谓的解耦的一种手段。数据库适配器初始化延迟到了具体需要使用这个资源的时候,有时候程序的运行,不需要实例化数据库适配器,那么就不要初始化它(初始化数据库资源是昂贵的)。

———————————————————————————–

$adapter = new Zend\Db\Adapter\Adapter(array(
    'hostname' => 'localhost',
    'database' => 'test',
    'username' => 'root',
    'password' => '',
    
    'driver' => 'mysqli',
    'charset' =>'utf8'
    
    //'driver' => 'pdo_mysql',
    //'driver_options' => array(
    //    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
    //),
));
$connection = $adapter->getDriver()->getConnection();
// connection还是具体数据库链接的抽象,它使用一个叫resource的名称代表最底层的数据库链接对象
// 通过getResource()获取这个数据库对象,然后可以调用具体的方法
$resource = $connection->getResource();
// 调用mysqli的具体方法
$resource->real_escape_string($);

以上程序为了能escape字符串而进行了不兼容调用,在Zend Framework新版本中,这个Bug已经修复,Zend\Db\Adapter\Platform\Mysql的quoteValue改为如下:

    public function quoteValue($value)
    {
        if ($this->resource instanceof DriverInterface) {
            $this->resource = $this->resource->getConnection()->getResource();
        }
        if ($this->resource instanceof \mysqli) {
            return '\'' . $this->resource->real_escape_string($value) . '\'';
        }
        if ($this->resource instanceof \PDO) {
            return $this->resource->quote($value);
        }
        return parent::quoteValue($value);
    }

Javascript碎片笔记

JavaScript只有一个单一的数字类型。它内部被表示为64位的浮点数。值NaN是一个数值,它表示一个不能产生正常结果的运算结果。NaN不等于任何值,包括它自己。可以用函数isNaN(number)检测NaN。

值Infinity表示所有大于1.79xxxe+308的值。

转义字符允许把那些正常情况下不被允许的字符插入到字符串中,比如反斜线、引号和控制符。\u约定允许指定用数字表示的字符码位。

"A" === "\u0041"

字符串有一个length属性。

字符串是不可变的。一旦字符串被创建,就永远无法改变它。通过+运算符去链接其它的字符串会得到一个新字符串。

被当做假:
false
null
undefined
空字符串’ ‘
数字 0
数字 NaN

除此,所有值都被当做真,包括true 字符串”false”以及所有对象。

for (myvar in obj) {
	if (obj.hasOwnProperty(myvar)) {
		...
	}
}

通常须通过检查object.hasOwnProperty(var)来确定这个属性名就是该对象的成员,还是从其原型链里找到的。

return语句如果没有指定返回表达式,其返回值是undefined。JavaScript不允许在return(break)关键字和表达式之间换行。

JavaScript的简单类型包括数字、字符串、布尔值(true和false)、null值和undefined值。其它所有值都是对象。数字、字符串和布尔值类似对象,因为它们有方法,但它们是不可变的。

对象是属性的容器,其中每个属性都有名字和值。属性的名字可以是包括空字符串在内的任意字符串。属性值可以是除undefined值之外的任何值。(不能没有名字 和 不能没有值)

var sum = function(){
	var i, sum = 0;
	for(i = 0; i< arguments.length;i++){
		sum += arguments[i];
	}
}

arguments并不是一个真正的数组。arguments有一个length属性,但它缺少所有的数组方法。

一个函数总是返回一个值。如果没有指定返回值,则返回undefined。

Function.prototype.method = function(name,func){
	this.prototype[name]  = func;
	return this;
}
//改进
Function.prototype.method = function(name,func){
	if(!this.prototype[name]){
		this.prototype[name]  = func;
	}
	return this;
}

Number.method('integer',function(){
	return Math[this<0?'ceiling':'floor'](this);
});

String.method('trim',function(){
	return this.replace(/^\s+|\s+$/g,'');
}

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)。

判断变量为数组:

var is_array = function (value) {
	return value &&
		typeof value === 'object' &&
		value.constructor === Array;
};

//以下是最可靠的方法
var is_array = function(value){
	return value &&
		typeof value === 'object' &&
		typeof value.length === 'number' &&
		typeof value.splice === 'function' &&
		!(value.propertyIsEnumerable('length'));
}

——————-
注意:现在大部分浏览器都应该支持Array.isArray()方法,为了兼容性,可用jQuery中提供的$.isArray()。

Javascript 数组常用函数 (去重 是否有重复值 交集 并集)

自己写了几个函数,可能不是很高效,但至少是可用的…

Array.prototype.unique = function(){ 
	var uniq = [],hash = {};  
    for(var i = 0; i < this.length; i++){  
        if(!hash[this[i]]){  
        	uniq.push(this[i]);
        	hash[this[i]] = true; 
        }  
    }  
    return uniq; 
};

Array.prototype.isUnique = function(){ 
	var uniq = true, hash = {};
	
    for(var i = 0; i < this.length; i++){  
        if(!hash[this[i]]){
        	hash[this[i]] = true; 
        }else{
        	uniq = false;
        	break;
        }
    }  
    return uniq; 
};

Array.prototype.has = function(search){
	for(var i = 0; i < this.length; i++){
		if(this[i] == search){
			return true;
	  	}
	}
    return false;
};

Array.prototype.intersect = function(arr){
	if(arr.constructor == Array){
		var unique = this.unique();

		var hash = {};  
	    for(var i = 0; i < arr.length; i++){
	    	if(!hash[arr[i]]){
	        	hash[arr[i]] = true; 
	        }
	    }
	    var inter = [];
	    for(var i=0; i<unique.length; i++){
			if(hash[unique[i]]){
				inter.push(unique[i]);
			}
		}
		return inter;
	}else{
		return [];
	}
};

Array.prototype.merge = function(arr){
	if(arr.constructor == Array){
		for(var i = 0; i < arr.length; i++){
			this.push(arr[i]);
		}
		return this.unique();
	}else{
		return this;
	}
};

jQuery客户端翻页测试

这段程序也是常常使用到的范例。

<?php
header('Content-Type:application/json; charset=utf-8');

$page = !empty($_REQUEST['page'])?(int)$_REQUEST['page']:1;
$pageSize = !empty($_REQUEST['pageSize'])?(int)$_REQUEST['pageSize']:20;

// 修正页大小
if($pageSize < 10){ $pageSize = 10; }
if($pageSize > 200){ $pageSize = 20; }

// 总记录
//$total = mt_rand(1000,10000);
$total = 26100;

// 总页数
$pageTotal = ceil($total/$pageSize);

// 修正页数
if(($page < 1) || ($page > $pageTotal)){
	$page = 1;
}

// 开始 与 结束
$from = (($page-1)*$pageSize+1);
$to   = $from + $pageSize - 1;

$data = array();
for($i=$from; $i<=$to; $i++){
	$data[] = array(
			'id' => $i,
			'word' => 'word_'.$i,
			'logo' => 'logo_'.$i,
			'site' => 'site_'.$i
	);
}

echo json_encode(array(
		'total' => $total,
		'pageSize' => $pageSize,
		'page' => $page,
		'data' => $data
));

HTML页面:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
ul,li{
	margin:0px;
	padding:0px;
	list-style:none;	
}
li{ float:left; padding:5px 10px;}
table{ border-left:1px solid #ccc; border-top:1px solid #ccc;}
table td,table th{ padding:5px; border-right:1px solid #ccc; border-bottom:1px solid #ccc;}
</style>
<script type="text/javascript" src="jquery.js"></script>
<script>
var displayPageNum = 10;
$(function(){
	$(document).on('click', '.page',function(){
		var page = $(this).attr("page");
		pager(page);
		return false;
	});
	
	function pager(page){
		$.get("data.php",{"page":page,"pageSize":20},function(d){
			var total = d.total;
			var page =  d.page;
			var pageSize = d.pageSize;
			var data = d.data;
			
			var table = "<table><tr><th>id</th><th>word</th><th>logo</th><th>site</th></tr>";
	
			$.each(data,function(i,v){
				table += "<tr>";
				table += "<td>"+v.id+"</td>";
				table += "<td>"+v.word+"</td>";
				table += "<td>"+v.logo+"</td>";
				table += "<td>"+v.site+"</td>";
				table += "</tr>";	
			});
			
			table += "</table>";
			$("#dtable").html(table);
			
			var pageTotal = Math.ceil(total / pageSize);
			
			var pageList = '<ul>';
			if(pageTotal > 0){
				pageList += '<li><a href="#" class="page" page="1">首页</a></li>';	
			}
			if((pageTotal > 0) && (page > 1)){
				pageList += '<li><a href="#" class="page" page="'+(page-1)+'">上一页</a></li>';	
			}
			//////////////////////////
			if(page >= displayPageNum){
				var to = page + displayPageNum/2;
				if(to > pageTotal){
					to = pageTotal; 
				}
				from = to - displayPageNum + 1;
				if(from < 1){ 
					from = 1;   
				}
			}else{
				var from = page - displayPageNum/2;
				if(from < 1){
					from = 1;
				}
				to = from + displayPageNum -1;
				if(to > pageTotal){ 
					to = pageTotal;   
				}
			}
			for(var i=from; i<=to; i++){
				if(i == page){
					pageList += '<li><b>第'+i+'页</b></li>';
				}else{
					pageList += '<li><a href="#" class="page" page="'+i+'">第'+i+'页</a></li>';
				}
			}
			//////////////////////////
			
			if((pageTotal > 0) && (page < pageTotal)){
				pageList += '<li><a href="#" class="page" page="'+(page+1)+'">下一页</a></li>';
			}
			if(pageTotal > 0){
				pageList += '<li><a href="#" class="page" page="'+pageTotal+'">最后页</a></li>';	
			}
			pageList += '</ul>';
			
			$("#pageList").html(pageList);
			
		},'json');
	}
	
	pager(1);
});

</script>
</head>
<body>
<div id="dtable"></div>
<div id="pageList"></div>
</body>
</html>

参考地址:htp://blog.ifeeline.com/jquery/dtable/index.php

目录联动选择最佳实践范例

简单来说就是选择一级目录时显示二级目录,选择二级目录时显示三级目录,这样需求如果使用Ajax来实现,思路很清晰。不过我这里没有使用这个方法,而是一次性读取整个目录,在客户端实现联动。由于这样的程序很常见,所有这里做一个范例保存参考,未来需要时可以避免重新造轮子。

控制器代码(如果有需要重用,可以封装成一段业务逻辑代码):

//控制器
	public function view_test(){
		// 记录客户端选择的目录,类似1-14-85 1-14-,如果-后为空说明没有选择目录
		$selectedCates = isset($_REQUEST['selectedCates'])?trim($_REQUEST['selectedCates']):'';
		
		// 读取目录列表,每个类目按照格式:目录包含子类方式组织(叶子类目去掉)
		$erpCates = A('erpCates')->getSubCates();
		$erpCatesArr = json_decode($erpCates,true);
		
		// 组织传递到视图的类目选中(或初始)数据,是否选中
		$cateList = array();
		if(empty($selectedCates)){
			foreach($erpCatesArr[0] as $idx=>$vle){
				$cateList[0][$vle['id']] = array('id'=>$vle['id'],'name'=>$vle['name'],'selected'=>0); 
			}
		}else{
			$cids = explode('-',$selectedCates);
			$pid = 0;
			$count = count($cids); 
			for($i = 0; $i < $count; $i++){
				
				foreach($erpCatesArr[$pid] as $idx=>$vle){
					$selected = 0;
					if($vle['id'] == $cids[$i]){ $selected = 1; }
					$cateList[$pid][$vle['id']] = array('id'=>$vle['id'],'name'=>$vle['name'],'selected'=>$selected);
				}
				$pid = $cids[$i];
			}
		}
		// 类目选择初始
		$this->smarty->assign('cateList',$cateList);
		// 全部类目数据
		$this->smarty->assign('erpCates',$erpCates);
		$this->smarty->display('title_test.html');
	}
#############################################################################
// 或者封装成一段业务逻辑代码
	public function act_initCates($selectedCates){
		// 读取目录列表,每个类目按照格式:目录包含子类方式组织
		$erpCates = A('erpCates')->getSubCates();
		$erpCatesArr = json_decode($erpCates,true);
		
		// 组织传递到视图的类目选中(或初始)数据,寻找了那个类目,是否选中
		$cateList = array();
		if(empty($selectedCates)){
			foreach($erpCatesArr[0] as $idx=>$vle){
				$cateList[0][$vle['id']] = array('id'=>$vle['id'],'name'=>$vle['name'],'selected'=>0); 
			}
		}else{
			$cids = explode('-',$selectedCates);
			$pid = 0;
			$count = count($cids); 
			for($i = 0; $i < $count; $i++){
				
				foreach($erpCatesArr[$pid] as $idx=>$vle){
					$selected = 0;
					if($vle['id'] == $cids[$i]){ $selected = 1; }
					$cateList[$pid][$vle['id']] = array('id'=>$vle['id'],'name'=>$vle['name'],'selected'=>$selected);
				}
				$pid = $cids[$i];
			}
		}
                return $cateList;
	}
//在控制器中如下使用
	public function view_test(){
		// 记录客户端选择的目录,类似1-14-85 1-14-,如果-后为空说明没有选择目录
		$selectedCates = isset($_REQUEST['selectedCates'])?trim($_REQUEST['selectedCates']):'';
                $cateList = A('title')->act_initCates($selectedCates);

                // 读取目录列表,每个类目按照格式:目录包含子类方式组织
		$erpCates = A('erpCates')->getSubCates();

		// 类目选择初始
		$this->smarty->assign('cateList',$cateList);
		// 全部类目数据
		$this->smarty->assign('erpCates',$erpCates);
		$this->smarty->display('title_test.html');
        }

这段代码是实现了当选择提交返回之后,选择的信息不丢失(选择信息由$cateList带回来)。接下来就是一段客户端的实现(JS)。

<script type="text/javascript">
var mod = "{$mod}";
var act = "{$act}";
// 类目数据,目录包含子类目,去掉叶子目录
var erpCatess = {$erpCates}
{literal}
$(function(){
        // 每个目录被选中时,都执行先清除其后的元素,再附加当前选中类目的子类目
	$(document).on('change','.erpCate',function(){
		var catePath 	= $(this).val();
		catePath 		= catePath.split('-');
		var cid 		= catePath.pop();
		selectHtml(cid,$(this));
	});
	// 提交表单时,把选中的类目组成成1-14-80 1-4-这种格式赋值给隐藏表单域
	$("#searchForm").submit(function(){
		var cids = [];
		$("#catesCon select").each(function(){
			cids.push($(this).val());
		});
		$("#selectedCates").val(cids.join('-'));
	});
})
// 这段代码实际并没有很强重用性,可以需用分离
function selectHtml(pid,e){
	var selectHtml 	= '';
	if(pid == ''){
		e.nextAll('.erpCate').remove();
		return false;
	}
	erpCates = erpCatess[pid];
	if(!erpCates){
		e.nextAll('.erpCate').remove();
		return false;
	}
	for(var i=0;i<erpCates.length;i++){
		selectHtml 	+= "<option value='"+erpCates[i].id+"'>"+erpCates[i].name+"</option>"; 
	}
	$('.erpCate').attr('name','');
	e.nextAll('.erpCate').remove();
	e.after('<select class="erpCate"><option value="">请选择</option>'+selectHtml+'</select>');
}
{/literal}
</script>

HTML代码部分:

<form id="searchForm" name="" method="post" action="">
	<input type="hidden" value="{$mod}" name="mod" />
	<input type="hidden" value="{$act}" name="act" />
	<input type="hidden" value="" name="selectedCates" id="selectedCates" />
	<table>
		<tr>
			<td id="catesCon">
			{foreach $cateList as $iid=>$cca}
			<select name="select_{$iid}" class="erpCate">
			<option value="">请选择</option>
			{foreach $cca as $cidx=>$cvle}
			<option value="{$cvle['id']}" {if $cvle['selected'] == 1}selected="selected"{/if}>{$cvle['name']}</option>
			{/foreach}
			</select>
			{/foreach}
			</td>
			<td><input type="submit" value="搜索" /></td>
		</tr>
	</table>
</form>

在选中数据回显上,服务端根据提交的数据组织数据(如果没有选中就是默认的数据),然后在客户端循环输出这个数据即可,这个实现方式是一最佳实践。

永久链接:http://blog.ifeeline.com/1747.html

jQuery Ajax请求同步 异步…

jQuery.ajax()
Perform an asynchronous HTTP (Ajax) request.(执行一个异步的HTTP请求)
一般我们认为Ajax请求就是异步请求的别名,jQuery中所有的Ajax请求的方法都是异步的($.get() $.post()这些实际是$.ajax()方法的封装,$.ajax()方法默认就是异步的)。

$(function(){
	$("#searchForm").submit(function(){
		var success = true;
		
		$.get('index.php?mod=title&act=admin', {}, function(d){
			if(d.success == 'nook'){
				success = false;
			}
		},'json');
		
		if(!success){
			return false;	
		}
	});
});

这段代码并没有办法实现预期。因为$.get()方法是异步执行的,它没有返回结果之前,就可以执行它之后的代码。当我遇到这个问题的时候,才意识到,自己平时使用Ajax的方法时只在意去获取数据,而几乎忽略了它是否是异步执行。

到达到以上的效果,需要使用$.ajax()函数,把里面的参数async设置为false(即同步运行代码),以下是一段典型的,可作为模子的代码:

$.ajax({ 
	url:'/xx/xxx/xxxx/',
	async:false,
	type:"POST",
	data:form.serializeArray(),
	dataType:"json",
	beforeSend:loading,
	complete:closeLoading,
	success:function(data){
		if(data.success == "OK"){
			$("#dialog").dialog('close');
		}else{
			$("#errInfo").html(data.error);
		}
	},
	error:function(){
		alert("Error occurred");
	}
});

注意dataType参数,当指定时,jQuery会按照指定的类型解析,比如指定为json,jQuery会把返回的字符串自动使用parseJSON()进行解析,如果没有指定,jQuery会自动判断(根据HTTP头信息)然后做相应处理。如果要自己处理,可以把dataType指定为text,然后自己针对这个字符串进行处理,比如调用parseJSON()。一般,在返回JSON前,发送一个JSON头(header(‘Content-Type:application/json; charset=utf-8’);),然后在客户端指定dataType为json,这样就不需要自己处理了。

如果要把数据转换成JSON字符串,可以参考:http://blog.ifeeline.com/913.html。

jQuery插件 – zTree

jQuery插件zTree, 项目地址:http://www.ztree.me/v3/main.php#_zTreeInfo, 国人开发,中文文档。使用上相对容易。累世这类插件的使用,往往会有很多配置信息,所以需要花一些时间理清楚配置参数的作用,然后学习基本的API。通过例子学习是一个捷径。

以下是一个例子,在这个例子中,我实现了根据勾选的叶子节点,获取整个路径的信息,因为我实际中需要保存这个路径:

<?php 
/*
[
  {
     "id":"1",
     "parentid":"0",
     "name":"服装及配饰"
  },
  {}
]
*/
include 'inc.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script src="jquery.min.js"></script>
<script src="js/jquery.ztree.all-3.5.min.js"></script>
<link rel="stylesheet" href="css/zTreeStyle/zTreeStyle.css" type="text/css" media="all" />

<script>
treeJson = <?php echo $tree?>;

var setting = {
	view:{  
		// 双击展开
		dblClickExpand: true,
		// 是否显示对齐线 
		showLine: false,  
		// 是否显示图标
		showIcon:false
	},  
	data: {  
		simpleData: {
			// 是否启用简单数据模式(这个方式只要把每个目录按照如下格式读出放入数组即可)
			enable: true,
			// 目录ID的标识符
			idKey: "id",
			// 符目录ID标识符
			pIdKey: "parentid",
			// 根目录ID值
			rootPId: 0
		}  
	},
	check: {
		// 是否启用选框
		enable: true,
		// 设置自动关联勾选时是否触发 beforeCheck / onCheck 事件回调函数
		autoCheckTrigger: true,
		chkStyle: "checkbox"
	}
};


$(function(){
	// 第一个参数是一个容器(jQuery包装),第二参数是配置信息,第三参数是Json数据
	// 注意:容器需要应用一个叫ztree的CSS类,也可以修改,不过对应CSS样式要替换(一般不需要改动)
	zTree = $.fn.zTree.init($("#erpTree"), setting, treeJson);	
	
	
	$("#trigger").click(function(){
		var set = $(this);
		if(set.attr('clear') == 'false'){
			set.attr('clear','true');
			// 清楚所有勾选
			zTree.checkAllNodes(false);
			return;
		}else if(set.attr('clear') == 'true'){
			set.attr('clear','false');
		}
		
		// 节点ID,不需要区分子节点 和 父节点
		var nodes = [82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,403,404,405,406,445,476,510,511];
		for(i in nodes){
			// 获取节点对象
			var node = zTree.getNodeByParam('id',nodes[i]);
			// 选中节点 第一参数必须是zTree内部节点对象,第二参数表示是否选中,第三参数表示是否联动操作(子节点选中,父节点也选上)
			zTree.checkNode(node, true, true);
			// 更新节点信息
			//zTree.updateNode(node);
		}
	});
	
	$("#getNodes").click(function(){
		// 获取所有选中的节点对象
		var checkedNodes = zTree.getCheckedNodes(true);
		// 选中节点ID与对象对照表
		var cates = {};
		for(var ii in checkedNodes){
			cates[checkedNodes[ii]['id']] =  checkedNodes[ii];
		}
			
		// 子节点路径对应
		var catePath = {};
		for(i in checkedNodes){
			// 子节点 回溯 获取路径
			if(!checkedNodes[i]['isParent']){
				var id = checkedNodes[i]['id'], name = checkedNodes[i]['name'];
				var ids = [], names = [];
				ids.push(id); names.push(name);
				pid = checkedNodes[i]['parentid'];
				for(var j=0; j<30; j++){
					if(pid != 0){
						node = cates[pid];
						ids.push(node['id']);
						names.push(node['name']);
						pid = node['parentid'];
					}else{
						break;
					}
				}
				catePath[id] = [ids.reverse().join('-'),names.reverse().join('-')];
			}
		}
		
		var outStr = '';
		$.each(catePath,function(){
			//console.log(this.join('|'));
			outStr += this.join('|')+"<br />";
		});
		
		$("#log").html(outStr);
	});
});

</script>
</head>
<body>
<table>
	<tr>
    	<td style="vertical-align:top;width:350px;">
        <div id="erpTree" class="ztree"></div>
        <button id="trigger" clear="true">设置节点</button> -- <button id="getNodes">获取节点</button>
        </td>
        <td style="vertical-align:top">
        <div id="log"></div>
        </td>
    </tr>
</table>
</body>
</html>

Demo地址:http://blog.ifeeline.com/jquery/zTree_v3/index.php