以下例子皆需要php5.4以上版本。
CodeIgniter这个框架的规则比较少,另外核心类都心CI_开头,很方便扩展。CodeIgniter的扩展有很多种方法,有替换核心类的,有继承扩展核心类,及使用钩子的,和__autoload加载的。
先说怎么扩展smarty的例子。
本人作为一个程序员,觉得php原生的模版是比smarty更强大的,只要在格式上用<?php if (..): ?><?php endif; ?>,另外view用的函数必须是php原生的或公共的,不要让view越界做M和C的事,就可以了。不过用smarty写不太复杂的代码模版是不错的选择,至少可以直接写<?php 什么的, 还少打字。
当然,你可以把smarty的两个文件夹直接放ci的根目录,这样最好配,但味道不好。
我是这样配的:
首先把smarty的libs文件夹(核心库)拷到 application/third_party/文件夹, 把libs改名成smarty。Smarty.class.php文件改名成 Smarty.php(实际上没必要)。把demo文件夹拷到application/, 改名成smarty。
配上这个函数就OK了。
function get_smarty($conf = [])
{
$smarty_path = APPPATH.'smarty/';
require_once(APPPATH.'third_party/smarty/Smarty.php');
$smarty = new Smarty;
foreach (array_merge([
'compile_check' => 'true',
// 'debugging' => 'true',
'caching' => 'true',
'cache_lifetime' => 120,
// 'force_compile' => 'true',
// 'left_delimiter' => '<{',
// 'right_delimiter' => '}>',
'template_dir' => "{$smarty_path}templates",
'compile_dir' => "{$smarty_path}templates_c",
'cache_dir' => "{$smarty_path}cache",
'config_dir' => "{$smarty_path}configs",
],
$conf) as $k => $v)
{
$smarty->$k = $v;
}
return $smarty;
}
/* 结束 */
至于这个函数是放MY_Controller作static 函数,还是写在专门扩展third_party的静态类中,或是helpers的MY_。。。里直接用函数,这看个人喜好了。
/* __autoload开始 */
CI的__autoload有一些要注意的事项,比如CI的类一般以CI_或MY_开头。__autoload我是这样写的。
if ( ! function_exists('__autoload'))
{
function __autoload($class)
{
/* 这样写是错误的
if ( ! isset($prefix)){
static $prefix;
$prefix = config_item('subclass_prefix');
}; */
static $prefix;
($prefix === NULL) && $prefix = config_item('subclass_prefix');
if (strpos($class, $prefix) === 0)
{
require(APPPATH . "core/{$class}.php");
}
else
{
strpos($class, 'CI_') !== 0 && require(APPPATH . "third_party/{$class}.php");
}
}
};
/* 结束 */
if (strpos($class, $prefix) === 0)
{
require(APPPATH . "core/{$class}.php");
}
这句CI有内部类,会自己加载。。所以可以改成strpos($class, $prefix) === 0 && strpos。。。
require(APPPATH . "third_party/{$class}.php")
这句是要依赖于PHP的命名空间的。命名空间有很多\符号,这个在win环境下require没有问题。linux的还没有试过,你如果想保险,就用strtr($str, '\\', '/')或str_replace('\\', '/', $str)替换下, strtr效率高些,但区别不大;另外可能还要注意文件的大小写。
所以直接这样写是最简的。
function __autoload($class)
{
(strpos($class, '\\') !== FALSE) && require(APPPATH . 'third_party/'.strtr($class, '\\', '/').'.php');
}
至于复杂的,可以看Yii这个框架。这个里面的YiiBase.php文件中的YiiBase类有autoload函数是用于自动载入的,深思熟虑的代码,且效率不错。import和getPathOfAlias的写法也类似。
下面看例子我用了doitphp的类库,不过为了方便改了名字,另外改了点代码。
如果你仍然想用csv.class.php这样的名字, 可以写
elseif (strpos($class, 'doitphp') !== 0)
{
strpos($class, 'CI_') !== 0 && require(APPPATH . "third_party/{$class}.php");
}
else
{
require(APPPATH . "third_party/{$class}.class.php");
}
如果像doitphp这样的文件夹多了,不想写太多if,可以把doitphp们放到third_party/dot_class文件夹。
至于重构问题,用IDE应该可以把命名空间和文件名字替换下。
也可以直接用php批量改文件名后缀及str_replace('doitphp\', 'dot_class\doitphp\', 文件读的值)。
也可以这样写:不过我觉得strpos应该比file_exists快些。
$path = APPPATH . "third_party/{$class}";
if (file_exists("{$path}.php"))
{
require("{$path}.php");
}
elseif (file_exists("{$path}.class.php"))
{
require("{$path}.class.php");
};
application/third_partly/doitphp/libraries/csv.php :
<?php
namespace doitphp\libraries;
use doitphp\core as core;
/**
* CSV操作类
*
* CSV文件的读取及生成
* @author tommy <streen003@gmail.com>
* @copyright Copyright (c) 2010 Tommycode Studio
* @link http://www.doitphp.com
* @license New BSD License.{@link http://www.opensource.org/licenses/bsd-license.php}
* @version $Id: csv.class.php 1.0 2011-12-24 18:08:57Z tommy $
* @package libraries
* @since 1.0
*/
(defined('BASEPATH')) OR exit();
class csv extends core\Base {
protected static $_csv_conf = ['delim' => ",",
'newline' => "\n",
'enclosure' => '"',];
protected $_conf = ['class csv extends core\\Base'];
private $_private_conf = ['class csv _private_conf'];
/**
* 将CSV文件转化为数组
*
* @access public
* @param string $fileName csv文件名(路径)
* @param string $delimiter 单元分割符(逗号或制表符)
* @return array
*/
public static function readCsv($fileName, $delimiter = ",") {
//参数分析
if (!$fileName) {
return false;
}
setlocale(LC_ALL, 'en_US.UTF-8');
//读取csv文件内容
$handle = fopen($fileName, 'r');
$outputArray = array();
$row = 0;
while ($data = fgetcsv($handle, 1000, $delimiter)) {
$num = count($data);
for ($i = 0; $i < $num; $i ++) {
$outputArray[$row][$i] = $data[$i];
}
$row++;
}
fclose($handle);
return $outputArray;
}
/**
* 生成csv文件
*
* @access public
* @param string $fileName 所要生成的文件名
* @param array $data csv数据内容, 注:本参数为二维数组
* @return void
*/
public static function makeCsv($fileName, $data, $csv_conf = []) {
//参数分析
if (!$fileName || !$data || !is_array($data)) {
return false;
}
if (stripos($fileName, '.csv') === false) {
$fileName .= '.csv';
}
// 'delim' => ",",
// 'newline' => "\n",
// 'enclosure' => '"',
extract(array_merge(self::$_csv_conf, $csv_conf));
//分析$data内容
$content = '';
foreach ($data as $lines) {
if ($lines && is_array($lines)) {
foreach ($lines as $key=>$value) {
if (is_string($value)) {
$lines[$key] = $enclosure . str_replace($enclosure, $enclosure.$enclosure, $value) . $enclosure;
}
}
$content .= implode($delim, $lines) . $newline;
}
}
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Expires:0');
header('Pragma:public');
header("Cache-Control: public");
header("Content-type:text/csv");
header("Content-Disposition:attachment;filename=" . $fileName);
echo $content;
}
}
/* application/third_partly/doitphp/libraries/csv.php */
/* 结束 */
Base.php 也可以放 libraries,这样写csv extends Base就行。如果写csv extends doitphp\core\Base,那么实际引用的路径就是application/third_partly/doitphp/libraries/doitphp/core/了, 所以有必要用use语句。
感觉下面的Base.php类没有什么用,csv类没有什么protected属性要改,虽然csv还可以有子类,protected属性子类又可以用。
子类继承了父类的__get和__set方法,却也不能作用于自己子类的static属性。所以有必要把子类的属性设成private或static的,就算父类有这些方法,也不会影响封装。
再说下Yii,它的CComponent类中的__get和__set方法写得——还是用深思熟虚这句话吧。
application/third_partly/doitphp/core/Base.php
<?php
namespace doitphp\core;
/**
* Base.class.php
*
* DoitPHP框架基类,用于被其它类继承。
* @author tommy <streen003@gmail.com>
* @copyright Copyright (c) 2010 Tommy Software Studio
* @link http://www.doitphp.com
* @license New BSD License.{@link http://www.opensource.org/licenses/bsd-license.php}
* @version $Id: Base.class.php 1.3 2010-11-13 21:12:01Z tommy $
* @package core
* @since 1.0
*/
(defined('BASEPATH')) OR exit();
abstract class Base {
private static $_base_conf = ['class Base'];
/**
* 自动变量设置
*
* 程序运行时自动完成类中作用域为protected及private的变量的赋值 。
*
* @access public
* @param string $name 属性名
* @param string $value 属性值
* @return void
*/
public function __set($name, $value) {
if (property_exists($this, $name)) {
$this->$name = $value;
}
}
/**
* 自动变量获取
*
* 程序运行时自动完成类中作用域为protected及private的变量的获取。
*
* @access public
* @param string $name 属性名
* @return mixed
*/
public function __get($name) {
return isset($this->$name) ? $this->$name : false;
}
public function get($name) {
if (isset($this->$name)) {
return $this->$name;
} elseif (isset(self::${$name})) {
return self::${$name};
}
}
public function static_get($name) {
if (isset(self::${$name})) {
return self::${$name};
}
}
/**
* 函数: __call()
*
* 用于处理类外调用本类不存在的方法时的信息提示
*
* @access public
* @param string $method 方法名称
* @param string $args 参数名称
* @return string
*/
public function __call($method, array $args) {
echo 'Method:' . $method . '() is not exists in Class:' . get_class($this) . '!<br/>The args is:<br/>';
foreach ($args as $value) {
echo $value, '<br/>';
}
}
/**
* 输出类的实例化对象
*
* 直接调用函数,输出内容。
*
* @access public
* @return string
*/
public function __toString() {
return (string) 'This is ' . get_class($this) . ' Class!';
}
}
/* application/third_partly/doitphp/core/Base.php */
/* 结束 */
// 下面是控制器的代码
// 我可以直接这样写doitphp\libraries\csv::makeCsv(。。。实际上是为了省字
$csv = new doitphp\libraries\csv();
// var_dump($csv->get('_base_conf') /* 父类方法可以得到自己的静态属性 */
// , $csv->_conf /* 父类方法可以得到子类的非静态属性 */
// , $csv->get('_csv_conf') /* 父类方法无法得到子类静态属性 */
// , $csv->static_get('_csv_conf') /* 父类静态方法也无法得到子类静态属性 */
// , $csv->_private_conf /* 父类方法无法得到子类私有属性 */);
$csv::makeCsv('test', [['1','1',], ['2','2',], ['3','3','3',],]);
/* __autoload结束 */
在CI中BASEPATH是绝对路径,但APPPATH这个不是绝对路径,有一点效率问题,所以需要改CI根目录的index.php。
把
if (is_dir($application_folder))
{
define('APPPATH', $application_folder.'/');
}
改成
if (is_dir($application_folder))
{
define('APPPATH', dirname($_SERVER['SCRIPT_FILENAME'])."/{$application_folder}/");
}
当然,你也可以用realpath这个函数。