[框架核心]kohana框架controller/action/view原理,在很多时候反射也是唯一的选择:为什么我们会选择使用反射?找不到action和对应方法时调用404。直接访问Url里的一个controler下用action直接输出调用forward时在调核心request类里的函数返回$this是什么的理解。错误报告,文件缓存等实现, 自制session,web端打开写SQL日志及分析,单例模式,观察者模式,加载外部modules方法及入口文件,model+service+controller中间加上server层的原因,为何getCache得在services目录里的base.php和library目录下的model.php里有。
[root@my htdocs]# php -v
PHP 5.3.27 (cli) (built: Aug 12 2013 12:22:48)
升级后:
[root@iZ25z0ugwgtZ ~]# php -v
PHP 5.6.18 (cli) (built: Feb 13 2016 13:05:48)
升级后,出现:
ErrorException [ 2 ]: fopen(Unknown): failed to open stream: No such file or directory ~ SYS_PATH/core.php [ 865 ]
===================================================================
回退回5.3版本,如下:
[ZendGuardLoader-php-5.3-linux-glibc23-x86_64.tar.gz] found
ZendGuardLoader module install successfully!
Gracefully shutting down php-fpm . done
Starting php-fpm done
####################Congratulations########################
PHP install dir: /usr/local/php
eAccelerator Control Panel url: http://101.200.189.210/control.php
eAccelerator user: admin
eAccelerator password: eAccelerator
——————————————————————————————————————————————————————————
第一较大步骤:
学习这个Kohana这个框架的真正在于它就像雨燕一样的灵活,所以,先了解一下这个是怎么通过路由实现了其多域名共用一套完整代码的松耦合,且以mvc的高内聚的框架思想是首页步骤,如下:
只要三个步骤实现一套代码两套域名,还可实现看似一套代码且可独立部署,且还实现了域名的分离,这也是Kohana这个框架的灵活之处,更多请看:
http://jackxiang.com/post/7122/
http://jackxiang.com/post/5977/
____________________________________在controller里建立interface目录____________________________________
步骤一:mkdir /data/htdocs/boosh.com/application/controllers/internal (注意controller调用里面的类名前缀和路由保持一致。)
问题:框架controller层层调用情况,以新加一个接口域名internal.boosh.com,但代码还得放一块,而这个域名不一致,怎么办?
解决办法,先看框架controller层层调用情况:
./application/controllers/internal/base.php:abstract class internal_BaseController extends InitController //类名和文件夹名一样第一个字大写是规范,而和前面建立的目录对应的前缀表明这个类是在这个目录下的,这块的目录internal定后,其下面的类名必须以目录名且首字母大写作为前缀,这是因为request.php里定死了的,如下代码:
$prefix .= ucfirst($directory) . '_';//形成Internal_
$class = new ReflectionClass(ucfirst($prefix) . ucfirst($this->controller) . 'Controller');//这个controller也就是文件名如/internal/user.php里也就是User,class Internal_UserController extends Internal_BaseController ,user里的U大写即可。
——————————————————————————————————————————————————
./application/controllers/global.php:class InitController extends Controller
./libraries/controller.php abstract class Controller
再次对这个
./application/controllers/internal/base.php:abstract class internal_BaseController extends InitController //类名和文件夹名一样?这个是和路由的php代码设置及nginx的转写规则有关。
require_once dirname(dirname(__FILE__)) . '/global.php';
继承自:
./application/controllers/global.php:class InitController extends Controller
继承自:
./libraries/controller.php abstract class Controller
这个名称都可随便写,如base.php,但里面的类前缀部分也得变得有关联更符合要求,这块不规范像这样:
如:./application/controllers/internal/my.php
require_once 'base.php';
class Internal_MyController extends Internal2_BaseController
//class Internal_MyController extends Internal_BaseController
./application/controllers/internal/base.php也一样得有关联:
require_once dirname(dirname(__FILE__)) . '/global.php';//前面上一层目录的global.php
//abstract class Internal_BaseController extends InitController
abstract class Internal2_BaseController extends InitController //加个2后,在my.php里也加个2继承下,也没关系运行没有问题,只是不规范罢了。
为何直接写没有自己写那个new 这个controller类就运行了呢?是因为两个地方:
1)\libraries\request.php里的execute对目录进行分析,对controller类进行反射类实例化:
(1)internal目录进行处理:
if ($this->directory) {
// Add the directory name to the class prefix
$directory = str_replace (array('\\', '/', '//'), '_', trim($this->directory, '/'));
$directories = explode('_', $directory);
foreach ($directories as $directory) {
$prefix .= ucfirst($directory) . '_';
}
}
(2)对反射类进行处理,找不到文件则到core.php来进行查找定位处理:
try {
// Load the controller using reflection
$class = new ReflectionClass(ucfirst($prefix) . ucfirst($this->controller) . 'Controller');
if ($class->isAbstract()) {
throw new KoException('Cannot create instances of abstract controller: :error', array(':error' => ucfirst($this->controller) . 'Controller'));
}
} catch (Exception $e) {
if ($e instanceof ReflectionException) {
$this->status = 404;
} else {
$this->status = 500;
}
$this->sendHeaders();
exit(0);
}
用了do while串连起这个反射类,如知识点如下:
(3)为何不同的目录可以实现根据nginx里的d参数两个不同的域名呢,是因为下面这块实现的levoo.com/openapi === api.levoo.com:
一套代码通过nginx转写d到不同目录后,最终调用的都是:global.php,如下:
/data/htdocs/levoo.com/application/controllers/manage.php//class ManageController extends FrontController
/data/htdocs/xiyou.cntv.cn/application/controllers/front.php//class FrontController extends InitController
/data/htdocs/xiyou.cntv.cn/application/controllers/global.php//class FrontController extends InitController
/data/htdocs/levoo.com/application/controllers/openapi/manage.php
class Openapi_ManageController extends Openapi_BaseController //它们最终都直接继承了global.php文件
require_once dirname(dirname(__FILE__)) . '/global.php'; //class InitController extends Controller
最后,execute全部代码如下,试图调度控制器/动作。如果要求表示它需要被调度,移动到下一个请求行动。
(2)core.php的autoload和findFile找文件这个查找的core.php是在fontend里的index.php包含bootstrap.php再包含core.php引入的,给反射类的查找文件指名了方向,在bootstrap.php最后一行(Request::instance()->dispatch();)便是自动根据core.php里的__autoload,找到request.php文件进行操作里面的相关函数的一个立即体现(这块的core.php直接引入太关键了),\application\bootstrap.php:
自己重新定了autoload,而不是__autoload:
将__autoload换成loadprint函数。但是loadprint不会像__autoload自动触发,这时spl_autoload_register()就起作用了,它告诉PHP碰到没有定义的类就执行loadprint()。
/data/htdocs/xiyou.cntv.cn/application/bootstrap.php:spl_autoload_register(array('Ko', 'autoload')); //这个是调用静态的方法:
spl_autoload_register() 调用静态方法
摘自:http://blog.csdn.net/panpan639944806/article/details/23192267
更多分析再参考自己写的这篇文章:http://jackxiang.com/post/5877/
步骤二:路由配置如下
/data/htdocs/my.boosh.com/application/bootstrap.php //把internal的路由指向前面建立的目录
Route::set('internal_route', 'internal(/<controller>(/<action>(/<__KO_VARS__>)))', array('__KO_VARS__' => '.+'))
->defaults(array(
'directory' => 'internal',//下面配置的Nginx指向这个目录并作转写即可:d=internal。
'controller' => isset($default_controller) ? $default_controller : 'index',
'action' => isset($default_action) ? $default_action : 'index',
));
这块路由的名称其实不重要,加个2也是可以的:
Route::set('internal2_route', 'internal(/<controller>(/<action>(/<__KO_VARS__>)))', array('__KO_VARS__' => '.+'))
为何配置这个bootstrap.php呢?htdocs的入口查看:
/data/htdocs/my.boosh.com/frontend/index.php
// Bootstrap the application
require_once APP_PATH . 'bootstrap.php';
步骤三:Nginx的配置文件
server
{
listen 80;
server_name i.boosh.com boosh.com;
index index.html index.htm index.php;
root /data/htdocs/my.boosh.com/frontend;///data/htdocs/boosh.com/frontend;被域名www.boosh.com用了,所以,my.boosh.com用作http://boosh.com使用。
location / {
rewrite "^/api/(album|general|partner|rank|search|user|video|stb|navigation|app|mobile|public|oauth|my|activity|manage|oauth2|favorite)\.([a-zA-Z]{4,})$" /index.php?d=internal&c=$1&a=$2&$args last;
rewrite "^/(album|general|partner|rank|search|user|video|stb|navigation|app|mobile|public|oauth|my|activity|manage|oauth2|favorite)/([a-zA-Z]{4,})$" /index.php?d=internal&c=$1&a=$2&$args last; //这一行必须要有,否则就没法通过get的d参数实现路由,也就是说即使后面分离后,前面新加的internal目录不用挪动,是nginx转写时加上了d,对album这些写死在nginx配置里的action进行加上d=internal,进而框架里的bootstrap.php进行了路由设置且支持路由。
前端访问情况:
直接通过api的接口访问:
http://i.boosh.com/album/addalbum (新加的域名访问A接口)
直接通过主站进行加上一个api进行访问:
http://boosh.com/api/album/addalbum(主站直接也可访问这个A接口)
对于接口输出比如ajax的json示例,此时一般都是关闭了模板,直接用自带函数进行输出,如下:
http://levoo.com/activityvideo/getactivityvideo/eid/14544893367247/typeid/1/pagenum/11/pagesize/20/sortby/new/format/jsonp/jsoncallback/cmscallback
=================View与smarty的一个接合问题=======================
前面新的一个interface没有和主站一个目录,而是另一个目录里,如何与smarty搭上边呢?
vi base.php
require_once dirname(dirname(__FILE__)) . '/global.php'; //与上一层目录的global.php进行引用,也就是主站的global.php
这个global.php主要干些啥?
1)获取进来的各种jsonP参数:
$this->varname = trim($this->getRequest()->getParam('varname'));
$this->jsoncallback = trim($this->getRequest()->getParam('jsoncallback'));
2)失败输出json/输出xml。
3)读取写入配置文件。
4)设置语言包位置及路径:css/js/images。
5)是否自动输出模板。
6) 一些共性的函数操作放里面,特别是像登录啥的。
interface/my.php
这就是真正的controller了,里面写这个页面特别的一些输出及模板渲染,经典的controller/action。
总之,就是通过view的工厂类, $this->autoRender(FALSE); 这个是不输出模板不需要smarty。要输出刚好是True,输出用了smarty,主站需要用,它是写在/controller/front.php里的,$this->view = View::factory($this->themesPath['themePath'] . '/' . $this->request->getController() . '/' . $this->request->getAction());,这一行调用了:\libraries\view.php,它引入了:require_once 'smarty/Smarty.class.php';且view.php初始化了smarty这个类,如下:
public function __construct ($file = NULL, array $data = NULL)
{
if (! empty($file)) {
$this->setView($file);
}
$this->_smarty = new Smarty();
if ($data !== NULL) {
$this->_smarty->assign($data);
}
$this->_smarty->template_dir = Ko::config('smarty.template_path');//这个是绝对路径前缀读取的是./smarty.php: 'template_path' => APP_PATH. 'views/',静态的KO类实现读取配置文件,Nginx指向->\frontend\index.php 包含=》\application\bootstrap.php=》它里面引用:require_once SYS_PATH . 'core.php'; =》public static function config ($group)静态函数实现。。
$this->_smarty->cache_dir = Ko::config('smarty.cache_path');
$this->_smarty->compile_dir = Ko::config('smarty.compile_path');
$this->_smarty->config_dir = Ko::config('smarty.configs_path');
并提供了,__set ,__get,render这些基础函数的二次根据这个view类的smarty封装。实现了smarty模板类。
模板问题再细一点:
出现模板找不到:The requested view themes/zh/my/video.html could not be found ~ SYS_PATH/view.php [ 174 ]
(这里的view.php并没有被直接包含,而是框架core.php里autoload和findFile经过在new类时查找到\libraries\目录并引入:public static function autoload ($class),根据文件名及类名引入的, Ko::autoload('My_Class_Name');class_exists找不到则去findFile查找并引入:require_once $path;。)
这就是这些类查找也好,命名也好的规范,对Controller Model Service,影响了其类名的写法必须符合,否则找不到,见libraries/core.php:
controller和Models和server分层在类名上体现是必要的,
无聊咔咔(5**773145) 17:52:56
__autoload
回忆未来-向东-Jàck(372647693) 17:53:21
加上findFile
一般框架都这样,MVC啥的全是这样搞的,有谁有新方法找我。
把找到的文件目录给缓存起来,下次就不用去再找一次了。
再把controller /action的类名加上比如Controller之类,
规范规范,加个smarty,一个PHP的架子就搭建起来了嘛要。
回忆未来-向东-Jàck(372647693) 17:57:30
controller和Models和server分层在类名上体现是必要的,如下:
class EventModel extends Model
class NewsService extends AdminBaseService
class ActController extends FrontController
findFile这个是围绕着环境变量且伴随是否caching进行编写的,主要是这两个核心的全局数组,步骤如下:
private static $_paths = array(APP_PATH , SYS_PATH);// Include paths that are used to find files
private static $_files = array(); // File path cache
1)如果是Cahceing就在self::$_files就是一个一个的文件以文件名为key,值就是文件绝对路径的数组里根据传入的文件名进行查找对应的路径,有则直接返回。
2)没有则对'config' 'i18n' 'messages'进行查找,这三个地方估计没法cache进来,得特殊处理。
3)还找不到,则self::$_paths ,这个是一堆的路径数组,一个一个去把文件名放进去拼成绝对路径,再用is_file去循环所有路径判断这个文件到底在哪儿,直到找到这个文件的绝对路径。
4)对这个文件的路径再放回到第1部里的self::$_files里去,同时返回这个文件的绝对路径,autoload 里的require_once $path;就是引入经过一堆查找到的文件,真心不容易啊。
__上面描述为何能自动找到这个View.php,Smarty被它重新封装并形成factory进行重新对象化,如何使用smarty如下:______
public function video()
{
$themesPath = $this->getLang(); //global.php里写好路径,且是相对路径,所以报错也是相对的,但输入smarty时拼成一个绝对路径,如下:
//[root@iZ25z0ugwgtZ config]# grep -r "views" ./
//./smarty.php: 'template_path' => APP_PATH. 'views/',
$this->themesPath = $themesPath;
$this->view = View::factory($this->themesPath['themePath'] . '/' . $this->request->getController() . '/' . $this->request->getAction());
$this->view->textlinkhotmax = "jackxiang";
$this->view->specialmax = "xiangdong";
$this->setFile("my/video");//如果不写,默认则是http://xxx.com/controller/funciton.html为模板。 https://boosh.com/my/video =>my目录下的video.html。
}
模板路径:/application/views/themes/zh/my/video.html 前面的这个路径是从/application/controllers/global.php 里写死一数组定义的:
[root@iZ25z0ugwgtZ my]# cat ~+/video.html
jack
<{$textlinkhotmax}>
<{$specialmax}>
访问输出:
jack jackxiang xiangdong
下面主要是core.php和request.php两个,core.php主要是autoload和findFile的一个结合,及reques.php是路由Route和反射类ReflectionClass的一个集合,最后是两个文件对新纳入文件的文件命名和类命名的的约束,形成KO的核心的框架。
第二大步,再谈一谈其高内聚合、低耦合的一些设计模式,如下:
在很多时候反射也是唯一的选择。为什么我们会选择使用反射?因为我们没有办法在编译期通过静态绑定的方式来确定我们要调用的对象。例如一个ORM框架,它要面对的是通用的模型,此时无论是方法也好属性也罢都是随应用场景而改变的,这种完全需要动态绑定的场景下自然需要运用反射。还例如插件系统,在完全不知道外部插件究竟是什么东西的情况下,是一定无法在编译期确定的,因此会使用反射进行加载。其实,包同学的反驳文章里也是持这种观点的:
什么是php反射类,顾名思义,可以理解为一个类的映射。
举个例子:
class fuc { //定义一个类
static function ec() {
echo '我是一个类';
}
}
$class=new ReflectionClass('fuc'); //建立 fuc这个类的反射类
echo $class; //输出这反射类
Class [ class A ] { @@ F:\phpweb\myPHP\test.php 23-30 - Constants [0] { } - Static properties [0] { } - Static methods [0] { } - Properties [0] { } - Methods [1] { Method [ public method __construct ] { @@ F:\phpweb\myPHP\test.php 26 - 29 } } }
$fuc=$class->newInstance(); //相当于实例化 fuc 类
$fuc->ec(); //执行 fuc 里的方法ec
/*最后输出:我是一个类*/
其中还有一些更高级的用法
$ec=$class->getmethod('ec'); //获取fuc 类中的ec方法
$fuc=$class->newInstance(); //实例化
$ec->invoke($fuc); //执行ec 方法
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
$class->getmethod('ec')-> invokeArgs($fuc, $params); //有点绕:class是反射类,获取这个反射类里的ec方法,用实例化后的$fuc,并传入相关ec的参数。
对该方法的传入参数及调用的方法实践OK如下:
输出:
---------- 调试PHP ----------
getAPI函数里的apiNO参数:1
Array
(
[apiName] => getVideo
[apiIntro] => getVideo from Server
)
是通过数组进行扩展多个参数的测试:第三个参数
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
$methodsArrAll = $class->getMethods(); //获取所有的方法名
Reflection::export($class);//通过此方法可以看到ReflectionClass也就是fuc类中所有的属性和方法
上面的过程很熟悉吧。其实和调用对象的方法类似
只不过这里是反着来的,方法在前,对象在后
举例: 这样的方法实现在面像对象的框架里有这样的用法,见:http://kohanaframework.org/ request.php
框架核心摘录解析\libraries\request.php,涉及到before/after(写日志)函数的先后,特别注意:
来自:http://blog.csdn.net/21aspnet/article/details/6952432
给redirect函数里加上日志:
找不到action和对应方法时调用404.
/**
* 当调用的action不存在时调用该方法
* Overload
*
* @see application/controllers/InitController::__call()
*/
public function __call($method, $arguments=NULL)
{
$this->redirect(KO::config('url.main_url') . 'error.html');
}
———————————直接访问Url里的一个controler下用action直接输出调用forward时在调核心request类里的函数返回$this是什么的理解。—————————
这里的 $this->request是从controller.php里继承来的:
class ApiController extends InitController,class InitController extends Controller。
public function __construct (Request $request)
{
// Assign the request to the controller
$this->request = $request;
}
而forward函数是request.php里的:
/**
* Forward to another controller/action.
*
* @param string $action
* @param string $controller
* @param string $directory
* @param array $params
* @return void
*/
public function forward($action, $controller = null, $directory = null, array $params = null)
{//方法,文件类名,文件路径(默认是框架路径,这儿可在框架路径下建立自己的路径),参数(是通过__call: public function __call($method, $arguments = null) 传入的)
。。。。。。
这些都是教简单,而return正是在request.php里函数常用到,如下:
/**
* Set parameters
*
* Set one or more parameters. Parameters are set as userland parameters,
* using the keys specified in the array.
*
* @param array $params
* @return Request
*/
public function setParams(array $params)
{
foreach ($params as $key => $value) {
$this->setParam($key, $value);
}
return $this;
}
/**
* Set request directory
*
* @param string
* @return Request
*/
public function setDirectory ($directory)
{
echo "<br>".__FILE__.__LINE__.$directory."<br>";
$this->directory = $directory;
return $this;
}
setParams和setDirectory都是forward里的,如下:
public function forward($action, $controller = null, $directory = null, array $params = null)
{
if (null !== $params && is_array($params)) {
$this->setParams($params);
}
if (null !== $controller) {
$this->setController($controller);
// Directory should only be reset if controller has been specified
if (null !== $directory) {
echo "Here,Jack...";
$this->setDirectory($directory);
}
}
$this->setAction($action)
->setDispatched(false);
}
这里的return this如何理解:
http://zhidao.baidu.com/link?url=QU-yClJDjln0YRwEZr8nO2wBLUIqDx4oPeYL6BykgCZvrclh78YpY9qnsfRaYWZam87QCaMEdmk06ZGd_DKY2q
这是类里面的一个函数,没有形参,函数里最后一句是return $this; 请问这个是返回了什么东西啊?返回了谁的自身?
public function getmodule() {
$this->cache->key = 'sdmodule';
$result;
.....
....//中间很多句子。
....
$this->smarty->assign("modules", $result);
return $this;
}
就是返回这个对象,在PHP类里面$this关键字就是代表这个类内部的引用。如你上面所说的return $this;就是相当于把该对象返回到方法getmodule() 中。
比如:
$abc=new class; //class是指你那个类
echo $abc->getmodule()->cache->key; //输出sdmodule
也就是getmodule() 拥有了该类的所有的成员和方法。
—————————通过forward方法传入和通过Request::instance()->dispatch();的区别(instance获取uri后调__construct进行action,controller,路径path的类变量赋值实现了类似forward的传参)—————————
/**
* Main request singleton instance. If no URI is provided, the URI will
* be automatically detected using PATH_INFO, REQUEST_URI, or PHP_SELF.
*
* @param string URI of the request
* @return Request
*/
public static function instance ($uri = TRUE)
{
static $instance;
if ($instance === NULL) {
.......//各种获取Uri的方法
$instance = new self($uri);//把uri传入类中,下面:
// public function __construct ($uri)//它就是用来对uri进行解析得到forward里的a,c,d。forward只是直接传入罢了,不用在这儿初始化就在于此,直接传入修改了类需要运行的相关变量,除开get参数外,都在这里进行分析并写入类全局变量,通过this作为句柄作操作。
//
}
return $instance;
}
forward和__construct的相同点和不同点:
public function __construct ($uri)
{
Ko::log('debug', __CLASS__ . ' Library loaded');
// Are query strings enabled in the config file?
// If so, we're done since segment based URIs are not used with query strings.
if (Ko::$enable_query_strings && isset($_GET['c'])) {
$this->setController(Security::xss_clean($_GET['c']));
if (isset($_GET['a'])) {
$this->setAction(Security::xss_clean($_GET['a']));
}
if (isset($_GET['d'])) {
$this->setDirectory(Security::xss_clean($_GET['d']));
}
$this->params = array();
return;
}
......对接受到的变量赋值到类变量后立即进行安全检查并过滤......
foreach ($routes as $route) {
if (($params = $route->matches($uri)) !== false) {
$this->directory = Security::xss_clean($params['directory']);
$this->controller = Security::xss_clean($params['controller']);
$this->action = Security::xss_clean($params['action']);
$this->params = $params;//变量从uri里获取到的
return;
}
}
//上面找不到的则进行404处理并输出找不到的头页面处理
}
而在执行时这个this-;>params还会用到:
function excute(){
if ($this->isDispatched()) {
$class->getMethod($action)->invokeArgs($controller, $this->params);//这儿
}
你会问这个POST,GET参数去哪儿了?在request;.php里有:
public function getParam ($key, $default = NULL)
{//对各种变量都作了xss检测
if (isset($this->params[$key])) {
return Security::xss_clean($this->params[$key]);
} elseif (isset($_GET[$key])) {
return Security::xss_clean($_GET[$key]);
} elseif (isset($_POST[$key])) {
return Security::xss_clean($_POST[$key]);
} else {
return $default;
}
}
再就是通过_set,_get进行属性读取,参看:php面向对象_get(),_set()的用法,http://blog.sina.com.cn/s/blog_4565cc770100bv2u.html
http://jackxiang.com/post/2766/ 里面有代码对其实践和论述。
在实际编写代码调用:
public function minialbum(){
$uuid = $this->getRequest()->getParam('uuid');
public function setController ($controller)
{
$this->controller = $controller;
return $this;
}
//而forward是自己写的,所以就不用进行安全的xss检测
public function forward($action, $controller = null, $directory = null, array $params = null)
{
if (null !== $params && is_array($params)) {
$this->setParams($params);
}
if (null !== $controller) {
$this->setController($controller);
// Directory should only be reset if controller has been specified
if (null !== $directory) {
$this->setDirectory($directory);
}
}
$this->setAction($action)
->setDispatched(false);
}
六:错误报告:
$show_debug_errors = false;
在配置文件:/data/htdocs/jackxiang.com/application/bootstrap.php
Ko::init( array(
'base_url' => '/',
'errors' => isset($show_debug_errors) && $show_debug_errors,
后赋值给Library/core.php里的:Ko::$errors,后在/library里的request;.php里面决定是否输出错误:
public function errorPage($msg = null, $data = array())
{
if(Ko::$errors === TRUE) {
throw new KoException($msg, $data);
exit;
} else {
return $this->forward('index', 'error');
}
}
七:文件位置缓存实现:
查找文件路径的缓存:'caching' => TRUE,
core.php
isset($settings['caching']) && self::$caching = (bool) $settings['caching'];
if (self::$caching === TRUE) {
// Use the default cache directory
self::$cache_dir = DATA_PATH . 'cache';
self::$_files = self::cache('Ko::findFile()');
}
self::$_files = self::cache('Ko::findFile()');
public static function cache ($name, $data = NULL, $lifetime = 3600)
{
// Cache file is a hash of the name
$file = sha1($name) . '.txt';
// Cache directories are split by keys to prevent filesystem overload
$dir = self::$cache_dir . DIRECTORY_SEPARATOR . "{$file[0]}{$f
配置文件:
/data/htdocs/jackxiang.com/application/bootstrap.php
Ko::init( array(
'base_url' => '/',
'errors' => isset($show_debug_errors) && $show_debug_errors,
'index_file' => isset($index_file) ? $index_file : 'index.php',
'caching' => TRUE,
'enable_query_strings' => TRUE,
'threshold' => isset($log_threshold) ? $log_threshold : 0,
));
——————————————————————————————————————————
八.自制session:
protected $session;
$this->session = Session::instance();
操作session:
protected function checkLogin ()//检查登录
{
if ($verifycode != $this->session->verifycode || !$this->session->userinfo ) {//从session里取不到
$this->session->userinfo = $this->ssoCheck($verifycode);//从接口里取并存入session
$this->session->verifycode = $verifycode;
......
}
return $this->session->userinfo;
}
protected function ssoCheck($verifycode){
......//查接口返回数据:'passport_url'=> 'http://passport.cntv.cn/ssocheck.jsp',Curl
$userinfo = json_decode($ret,true);
return $userinfo;//返回
}
九:写错误日志函数及错误的统一捕获:
场景:问,最近我做了一个日志系统,PHP在调用时出现因为异常错误导致没有运行到日志函数就退出了,
请问:怎么实现对这个错误的捕获?还问:PHP里的try catch可能并不能捕获里面的所有错误的实际情况。
这个问题其实是想问对PHP的set_error_handler函数,及错误处理函数,它捕获能导致php脚本停止运行的严重错误(这类错误是不能被set_error_handler ()捕获的),
一旦脚本停止运行,customend()函数就会被调用,在customend()函数中通过error_get_last()来判断脚本是正常结束还是发生严重错误而中断,如果是发生严重错误而中断,则运行错误处理程序。try-catch 无法在类的自动加载函数 __autoload() 内生效。try-catch 用于捕获异常,无法捕获错误,例如 trigger_error() 触发的错误,异常和错误是不一样的。
摘自:http://jingyan.baidu.com/article/046a7b3ed5e233f9c37fa969.html
参考:PHP 的异常处理、错误处理:error_reporting,try-catch,trigger_error,set_error_handler,set_exception_handler,register_shutdown_function
http://www.php-note.com/article/detail/779
******web端打开写SQL日志及分析。———show_debug_errors是前端错误打开于否的开关,
而那个threshold是分开关,如果要写Mysql日志则:$log_threshold = 3;小于3的threshold则不打印日志的,
上面两变量均在index.php入口配置,在application下的bootopen.php进行init()函数的赋值。******
如果等于threshold的值等于4则:debug: Request Library loaded ,也就是相当于debug的日志可打印到日志里来了,等于1只报error错误日志,如等于0则全关闭了即使有错。
* Log Thresholds:
* 0 - Disables logging completely
* 1 - Error Messages (including PHP errors)
* 2 - Alert Messages
* 3 - Informational Messages
* 4 - Debug Messages
*/
$log_threshold = 3;
对于这个;show_debug_errors这个值,则应该用Nginx给屏蔽掉,当然上线后关闭是最好的,提高运行效率及稳定性,Nginx配置如下:
实践Ok如下:
如错误页面在这儿:http://my.jackxiang.com/error
vi my.cnf
在server里添加:
error_page 404 = ./error;
error_page 500 502 503 504 = ./error;
#下面也行:
error_page 404 = http://my.jackxiang.com/error.html;
error_page 500 502 503 504 = http://my.jackxiang.com/error.html;
来自自己的参考:http://jackxiang.com/post/6769/
要想写日志得满足三个条件:
1./application/bootopen.php 里的'errors' 是TRUE,这样就注册了register_shutdown_function函数,进而在log.php里写日志
2.如果是后台程序立即触发就写,前台程序是关闭的写的。
register_shutdown_function这个的用法见,好多单例都给整了这个东东在上面:http://jackxiang.com/post/6784/
例1:核心都有用到register_shutdown_function
/libraries/core.php
// Enable the Ko shutdown handler, which catches E_FATAL errors.
register_shutdown_function(array('Ko' , 'shutdown_handler'));
if (self::$errors === TRUE) {
// Enable the Ko shutdown handler, which catches E_FATAL errors.
register_shutdown_function(array('Ko' , 'shutdown_handler'));
// Enable Ko exception handling, adds stack traces and error source.
set_exception_handler(array('Ko' , 'exception_handler'));
// Enable Ko error handling, converts all PHP errors to exceptions.
set_error_handler(array('Ko' , 'error_handler'));
}
例2:web运行完再写日志更是常用:
/libraries/log.php
// Write the logs at shutdown
register_shutdown_function(array(self::$_instance[$group], 'write'));
frontend/index.php //这儿打开的******(这块打开如果action类和方法不存在则会直接报错)
// Define wether show debug message.
$show_debug_errors = true;
/application/bootopen.php
Ko::init( array(
'base_url' => '/',
'errors' => isset($show_debug_errors) && $show_debug_errors,
/libraries/core.php
if (self::$errors === TRUE) {
// Enable the Ko shutdown handler, which catches E_FATAL errors.
register_shutdown_function(array('Ko' , 'shutdown_handler'));
/libraries/log.php
public static function instance($group ='default')
{
if (!isset(self::$_instance[$group]) ||self::$_instance[$group] === NULL) {
// Create a new instance
self::$_instance[$group] = new self($group);
// Write the logs at shutdown
register_shutdown_function(array(self::$_instance[$group], 'write'));
}
return self::$_instance[$group];
}
\libraries\database.php
// Log levels
private static $log_levels = array (
'error' => 1,
'alert' => 2,
'info' => 3,
'debug' => 4,
);
public static function log($type, $message)
{
// Should we log these to file?
if (self::$log_levels[$type] <= self::$threshold) {//type==debug<====4<=2
self::$log->add($type, $message);
}
}
\libraries\database.php
// Log query
Ko::log('info', str_replace(array("\r\n", "\r", "\n"), " ", $sql));
self::$log->add($type, $message);
}
十:上完静态文件,要改config.php里的version,以防止那个CDN了旧的,没法查看。
<?php
defined('SYS_PATH') or die('No direct access allowed.');
return array(
'version' => '2.005',
————————————————————————————
jquery.Jcrop.js?ver=<?php echo $this->_tpl_vars['version']; ?>
直接在框架初始化时赋值比较方便编写些,多层继承的最上层写上:
class Site_PartinController extends FrontBaseController
class FrontBaseController extends FrontController
class FrontController extends InitController
class InitController extends Controller(init.php里进行模板赋值的)
/application/controllers/init.php
/**
* Overload
*/
public function after ()
{
$this->view->version = KO::config('config.version');
模板里:
<script src="<{$staticUrl}>js/My97TimePicker/WdatePicker_time.js?ver=<{$version}>" type="text/javascript"></script>
flashvars["config"] ="<{$flashXmlUrl}>?ver=<{$version}>";// 如上传组件初始化参数的读取加上版本号
十一:单例模式,一些常规操人作,多用单例模式,这种模式不适合用于写PHP的daemon,容易出现内存不够,如不及时释放:
尽管PHP退出后就释放了,不像Java,但好处还是有的:http://blog.csdn.net/jungsagacity/article/details/7618587
http://blog.sina.com.cn/s/blog_6f49a3c30100qgiy.html
\libraries\session.php
public static function instance ($group = 'default')
protected function __construct ($group = 'default')
register_shutdown_function(array($this , 'write_close'));
public function sessionid ()
public function create ()
public function regenerate ()
public function unsetAll()
public function destroy ()
public function write_close ()
public function delete ($keys)
使用方法:
// Init Session
$this->session = Session::instance();
$this->session->set('findemail',$userInfo['email']);
$findemail = $this->session->get('findemail');
$this->session->unsetAll();
\libraries\config.php
public static function instance ($group = 'default')
final private function __construct ($group = 'default')
public function attach (Config_Driver $reader, $first = TRUE)
public function detach (Config_Driver $reader)
public function load ($group)
public function save ($file, $config = NULL)
public function copy ($group)
final private function __clone ()
使用方法:
$config[$group] = Config::instance('messages')->attach(new Config_File('messages'))->load($group);
Config::instance('lang')->attach(new Config_File('i18n'))->load($lang . DIRECTORY_SEPARATOR . $group);
Config::instance('messages')->attach(new Config_File('messages'))->save($file, $data);
更高级别封装readConfig:$this->_pbConfig = $this->readConfig('pbconfig');
controllers/global.php
/**
* 读取数组配置文件信息
* $this->readConfig('config'); ==> data/messages/config.php
* @param string $group 配置文件名,不带扩展名.php
*/
protected function readConfig($group)
{
\libraries\log.php
public static function instance($group ='default')
final private function __construct()
register_shutdown_function(array(self::$_instance[$group], 'write'));
public function attach(Log_Writer $writer, $types = NULL)
public function detach(Log_Writer $writer)
public function add($type, $message)
public function write ()
private function __clone()
使用方法:
$returnlog = Log::instance('videoreturn')->attach(new Log_File(DATA_PATH . 'logs/' . date('Ymd') . '/', 'setCheckVideoreturn-' . date('Ymd') . '.php'));
$logger = Log::instance(strtoupper($prefix))->attach(new Log_File(DATA_PATH . 'logs/' . date('Ymd') . '/', $prefix . '_' . date('Ymd') . '.php'));
$sharelog = Log::instance('share')->attach(new Log_File(DATA_PATH . 'logs/' . date('Ymd') . '/', 'share-' . date('Ymd') . '.php'));
$sharelog->add('succ:',' from vid:'.$vid.' to uuid :'.$videoData['video']['uuid']);
分析该写日志的实现:
$log = Log::instance(__CLASS__)->attach(new Log_File(DATA_PATH . '/logs/' . date('Ymd') . '/mysql-error-' . date('Ymd') . '.log'));
1)文件及目录都是777:
new Log_File:\libraries\log\file.php, 分析文件函数:
(1)目录777:__construct里直接创建目录: if (! mkdir($directory, 0777, TRUE) || ! is_writable($directory)) {
(2)文件777:public function write(){
\libraries\cookie.php
final private function __construct ()
public static function get ($key, $default = NULL)
public static function set ($name, $value, $expiration = NULL)
public static function delete ($name)
使用方法:
Cookie::set('language1',$language);
$lang = Cookie::get('language1');
十二:关于前端代码的路径等配置:前端Js/css静态域名版本号(反CDNl:version)的配置:
/controllers/site/preview.php
关于版本号及前端,每个controller下都有一个preview.php层层去继承InitController在具体的controller下配置相关前端Js输出及CSS输出的路径:
$this->view->version = KO::config('config.version');
public function after()
{
//$this->getwatch();
$this->view->themePath = $this->themePath;
$this->view->publicPath = 'themes/common';
$this->view->cssPath = 'themes/zh';
$this->view->jsPath = 'themes/zh';
$this->view->lang = (array)Ko::lang('common','zh');
$this->view->staticURL = KO::config('url.static_url');
$this->view->mainURL = KO::config('url.main_url');
$this->view->flashURL = KO::config('url.flash_player_url');
$this->view->basePath = $this->getBaseUrl();
$this->view->version = KO::config('config.version');
$this->view->render();
}
十三:smarty模板输出:
最上层的controller下的before还负责smarty模板的指定:
\libraries\controller.php
public function before ()
{
if ($this->auto_render === TRUE) {
// Load the template
$this->template = $this->request->getController() . '/' . $this->request->getAction();
$this->view = View::factory($this->template);
}
}
继承后再重写:
public function before ()
{
// Auto render , use user defined template.
$this->autoRender(TRUE);
parent::before();
———————上面的before,下面的after都是在Library/request.php里进行按before在前,action在中间,after在后面处理的__________
Before处理如下前期问题,参数获取/模板工厂(smarty)等层层继承,层层parent::before();:
//Overload:英文是重载,国人:同名函数,叫 方法重写
class ActController extends FrontController
{
public function before()
{
parent::before();//访问InitController里的after
}
}
class FrontController extends InitController
{
/**
* Overload
*/
public function before()
{
$lang = '';
parent::before();// 初始化模板等...
class InitController extends Controller
{
public function before()
{
// Init Session
$this->session = Session::instance();//获取参数等全局逻辑准备
$this->varname = trim($this->getRequest()->getParam('varname'));
$this->jsoncallback = trim($this->getRequest()->getParam('jsoncallback'));
}
abstract class Controller
public function before ()
{
if ($this->auto_render === TRUE) {
// Load the template
$this->template = $this->request->getController() . '/' . $this->request->getAction();
$this->view = View::factory($this->template);//模板进入工厂
}
}
十四:关于after:
这块输出头也是放after里的:
\controllers\global.php
public function after()
{
// Send headers: Cache-Control & Expire.
$max_age = $this->getRequest()->getParam('max_age', 0);
if ($max_age > 0) {
$this->getRequest()->addHeader('Cache-Control', 'max-age=' . $max_age);
$this->getRequest()->addHeader('Expires', gmdate('D, d M Y H:i:s', time() + $max_age) . ' GMT');
}
else {
$this->getRequest()->addHeader('Cache-Control', 'no-cache');
$this->getRequest()->addHeader('Expires', '0');
$this->getRequest()->addHeader('Pragma', 'No-cache');
}
parent::after();
}
上面的after函数被:\controllers\front.php重写并parent调用之:
/**
* Overload
*/
public function after()
{
......
$this->view->version = KO::config('config.version');
$this->view->pageCode = $this->getPageCode();
$this->autoRender(TRUE);
parent::after();
}
它也是在后面才输出的,调用在Library/request.php:
上面的日志在后面after写的:
libraries\request.php
$class->getMethod('after')->invoke($controller);
摘录自: \libraries\request.php
try {
// Load the controller using reflection
$class = new ReflectionClass(ucfirst($prefix) . ucfirst($this->controller) . 'Controller');
if ($class->isAbstract()) {
$this->errorPage('Cannot create instances of abstract controller: :error',
array(':error' => $prefix . ucfirst($this->controller) . 'Controller'));
}
// Create a new instance of the controller, Interrupted by dispatcher anytime
$controller = $class->newInstance($this);
if ($this->isDispatched()) {
$class->getMethod('before')->invoke($controller);
}
try {
$action = empty($this->action) ? Route::$default_action : $this->action;
if ($this->isDispatched()) {
$class->getMethod($action)->invokeArgs($controller, $this->params);
}
} catch (ReflectionException $e) {
$class->getMethod('__call')->invokeArgs($controller, array($this->getAction() , $this->params));
}
if ($this->isDispatched()) {
$class->getMethod('after')->invoke($controller);
}
}
十五:关于日志(新加daemon日志实现立即写,不用注册的退出时才写:register_shutdown_function):
日志输出在web时是执行完后再输出,而cli模式是立即输出,(关于错误级别$type定义在第九有备注到,如info级别:Ko::log('info', str_replace(array("\r\n", "\r", "\n"), " ", $sql));),如下:
\libraries\log.php
public function add($type, $message)
{
// Create a new message and timestamp it
$this->_messages[] = array (
'time' => date(self::$timestamp),
'type' => $type,
'body' => is_string($message) ? $message : var_export($message, true),
);
if(Ko::$is_cli === TRUE) {
$this->write();
}
return $this;
}
WEB没有立即写,而是后面才写,并没有在request中的after里,而是注册了shutdown函数write:
public static function instance($group ='default')
{
......
// Write the logs at shutdown
register_shutdown_function(array(self::$_instance[$group], 'write'));//这儿调用写
}
return self::$_instance[$group];
}
调用:\controllers\audit.php
$returnlog->add('check start', '-----------------');
$returnlog->add('data post', $post);
这种实现方法在如果用php做daemon时会遇到从不退出就没有调用写导致里面的数据太大出现php内存不够并意外退出问题:
遇到问题,当守护进程时写日志时往往会出现因为框架是web的,在最后退出才写,而daemon是不退出的,导致内存暴增,解决办法如下:
修改为,如果是cli模式立即就写了得了:
十六:使用modules外挂模块方法:
Ko::modules( array(
'weibo' => 'weibo',
'libspace' => 'libspace',
'face' => 'face',
'nusoap' => 'nusoap',
'sdk' => 'sdk',
));
jackxiang.com/modules/ //jackxiang.com是根目录
face/ libspace/ nusoap/ sdk/ weibo/
ls modules/sdk/ //init.php文件名是必须要有的。
common.php init.php justwinitapi.ex.class.php
调用:
./application/models/app.php: $justwinit = new justwinit();
./application/controllers/front/front.php: $justwinit = new justwinit();
/**
* Overload
*/
public function before()
{
$this->checkToken();
}
public function checkToken()
{
$justwinit = new justwinit();
.......
}
十七:框架扩展:在\libraries\core.php里加一个写日志函数,以给daemon程序写当是cli模式时写:
十八:图片缩放之gd,示例:
关于图片上传及缩小,用到上传类libraries//upload.php,缩小gd类libraries/image.php ,
这儿遇到一个问题是上传后缀是大写的JPG时会报错,最后用strtolower($ext)保存为小写就没这个问题,
我从其它博客试了一下确实存在警告,最好还是小写的jpg后缀为好:http://jackxiang.com/post/3022/
缩小为148.148的等比例缩放,其使用方法如下:
十九:关于单例模式里和观察者模式一块用的问题:
1)写法于传统的PHP写法背离,不像C了,在写得少的人看来很是怪异:
$log = Log::instance(__CLASS__)->attach(new Log_File(DATA_PATH . 'logs/' . date('Ymd'), __CLASS__.'-' . date('Ymd') . '.log'));
2)这种单例与观察模式在代码提示时出现无法跳转,因为再智能的编辑器也没法知道前面这个数组是哪个具体对象了:
$writer['object']->write($messages);//这样当于一个写的类句柄通过观察者数组中取出后:Log_File->write($messages);
3)但为了什么耦合,为了少几个句柄,为了提高效率等,还得用,自己作下分析性参考:http://jackxiang.com/post/6825/ 。
二十:观察者模式:
http://www.cnblogs.com/baochuan/archive/2012/02/22/2362668.html
框架里的观察者模式:
/libraries/log.php
片段如下,这里的writer就是一个进来的类对象数组的一个集合(全是对象),这个:
这个对象去分别调用对象类里的方法,和上面的原始摘录Url里的示例一样的,如下用foreach去一个个的调用write方法,$writer['object']->write($messages);:
二十一:关于memcache或ttserver的缓存实现及配置:
配置文件这样写:
config/cache.php
使用时要这样用:
class TagModel extends Model
{
protected $cachePermanent = 'tt_server';// Permanent Cache KEY,来自配置文件的数组key,及config/cache.php tt_server对应。
return (array)$this->getPermanentCache($userid);
return $this->setPermanentCache($userid, $tags);
为什么可以这样用,因为前面的extends Model这个类的libraries/model.php里有:
[root@iZ25z0ugwgtZ libraries]# grep -r "setPermanentCache" ./
./model.php: protected function setPermanentCache($key, $data)
// Cache KEY
protected $cacheConfig = 'default';
protected function getCache($key)
{
return Cache::instance($this->cacheConfig)->get($this->makeCacheKey($key));//静态调用instance方法获取该类的实例
}
这个cache是有各种cache,这个是ttserver,我们来看它是如何实现这个实例的文件,libraries/cache/ttcache.php :
我都说这个直接传入一个string的tt_server,是怎么从前面配置文件里读取到这个数组的,不仅仅是前面的这个instance函数对这各种cache设备
类进行实例化外,还有对这个下面挂的设备本身的相关配置进行初始化,代码如下:
框架分析到此完结。EOF
AddTime:2016-05-28
加载外部modules方法及入口文件:
// Set the current module list
self::$_modules = $modules;
foreach (self::$_modules as $path) {
$init = $path . DIRECTORY_SEPARATOR . 'init.php';
if (is_file($init)) {
require_once $init;
}
}
也就是说会核心框架会自动加载这个init.php
D:\www\xiyou.cntv.cn[新svn代码]\trunk\codes\modules下的OAuth weibo qiniu 里面都有一个init.php,而这前面三个目录是怎么找的呢?
D:\www\xiyou.cntv.cn[新svn代码]\trunk\codes\application\bootstrap.php 里有让modules相对或绝对路径。 relative or absolute path.:
==================================
概述以上分析,从更高层次来分析其入口的调用层次分析:
model独立,model目录里文件均无互相调用(缓存也在这层,也就是数据层),
service加上业务调用model model/open,service之前也可相互调用给application提供数据。
application可以有多个目录,如多了个openapi的目录,这个目录可通过路由来配置指向即可:
./bootstrap.php:Route::set('openapi_route', 'openapi(/<controller>(/<action>(/<__KO_VARS__>)))', array('__KO_VARS__' => '.+'))
这个对像是传入/libraries/request.php,它又和/libraries/core.php接合,接合autoload和反射类到上面这些model、service、controller里找对应,
找不到时用try..catch出来错即可,而这个bootstrap.php,则被./frontend/index.php ./open/index.php包含进来(它里面有错谴责级别设置)后,进行:
// Bootstrap the application
require_once APP_PATH . 'bootopen.php'; ,于是对外可呈现出多个域名,因为在nginx里直接配置:root /data/htdocs/xiyou_alpha/frontend;就会从
fronted/index.php单入口里穿透并进入到各个层里,运行了,在层次上清晰,在代码上可灵活配置,这就是Ko的优势了,MVC加server层次间的调用规定如下:
一)model目录一个目录:open
二)service目录里调model的open里的类用前面加Open_,调open一级的就不用了。
三)controller里有open openapi两个目录,均调用了service里的open目录:
比如:
controller
./openapi/partner.php: $Open_OpenService = new Open_OpenService();
./open/usercenter.php: $open_OpenService = new Open_OpenService();
_________Model_________
.........//主站的model
/model/open/...开放平台
也就是说model最好是简单一点,service文件间可互相调用:
vi services/open/open.php Open_OpenService 原型可被同级的server其它文件调用如下:
vi services/audit.php: $Open_OpenService = new Open_OpenService();
而这个service呢,也调用了model层里的open目录下的modle:
$tokenModel = new Open_TokensModel();
$info = $tokenModel->getTokenInfoByToken($token);
上面两行的位置在哪儿?能通过类前面加上Open知道是model/open/位置:
/application/models/open/tokens.php class Open_TokensModel extends Model
为何getCache得在services目录里的base.php和library目录下的model.php里有:
protected function getCache($key)
{
return Cache::instance($this->cacheConfig)->get($this->makeCacheKey($key));
}
getCurrentPageVideosFromDB在model里不需要cache,但是getCurrentPageVideos就需要cache了:
public function getVideosByUserId($userId)
{
$userVideos = $this->getCache($userId);
if ($userVideos) {
return $userVideos;
}
$userVideos = $this->getVideosByUserIdFromDB($userId);
if ($userVideos && count($userVideos) > 0) {
$this->setCache($userId, $userVideos);
}
return $userVideos;
}
同样,在service里有些变量也需要cache,如cookie变量从cache里读取,为何需要专门函数实现?因为怕cache重复了,结合函数名,查询参数确保唯一。
/**
* 写入cookie - 您最近看过的
*/
public function setCookieHistoryVideoList($uuid)
{
//设置显示“您最近看过的”视频的个数
$MAX_COUNT = 5;
$cookieId = Cookie::get('xiyou_ck_id');
$cookieData = $this->getCache($cookieId);
if(!$cookieData){
$cookieData = array();
}
$videolist = isset($cookieData['history_list']) ? $cookieData['history_list'] :false;
if(!is_array($videolist)){
$videolist = array();
PHP 5.3.27 (cli) (built: Aug 12 2013 12:22:48)
升级后:
[root@iZ25z0ugwgtZ ~]# php -v
PHP 5.6.18 (cli) (built: Feb 13 2016 13:05:48)
升级后,出现:
ErrorException [ 2 ]: fopen(Unknown): failed to open stream: No such file or directory ~ SYS_PATH/core.php [ 865 ]
===================================================================
回退回5.3版本,如下:
[ZendGuardLoader-php-5.3-linux-glibc23-x86_64.tar.gz] found
ZendGuardLoader module install successfully!
Gracefully shutting down php-fpm . done
Starting php-fpm done
####################Congratulations########################
PHP install dir: /usr/local/php
eAccelerator Control Panel url: http://101.200.189.210/control.php
eAccelerator user: admin
eAccelerator password: eAccelerator
——————————————————————————————————————————————————————————
第一较大步骤:
学习这个Kohana这个框架的真正在于它就像雨燕一样的灵活,所以,先了解一下这个是怎么通过路由实现了其多域名共用一套完整代码的松耦合,且以mvc的高内聚的框架思想是首页步骤,如下:
只要三个步骤实现一套代码两套域名,还可实现看似一套代码且可独立部署,且还实现了域名的分离,这也是Kohana这个框架的灵活之处,更多请看:
http://jackxiang.com/post/7122/
http://jackxiang.com/post/5977/
____________________________________在controller里建立interface目录____________________________________
步骤一:mkdir /data/htdocs/boosh.com/application/controllers/internal (注意controller调用里面的类名前缀和路由保持一致。)
问题:框架controller层层调用情况,以新加一个接口域名internal.boosh.com,但代码还得放一块,而这个域名不一致,怎么办?
解决办法,先看框架controller层层调用情况:
./application/controllers/internal/base.php:abstract class internal_BaseController extends InitController //类名和文件夹名一样第一个字大写是规范,而和前面建立的目录对应的前缀表明这个类是在这个目录下的,这块的目录internal定后,其下面的类名必须以目录名且首字母大写作为前缀,这是因为request.php里定死了的,如下代码:
$prefix .= ucfirst($directory) . '_';//形成Internal_
$class = new ReflectionClass(ucfirst($prefix) . ucfirst($this->controller) . 'Controller');//这个controller也就是文件名如/internal/user.php里也就是User,class Internal_UserController extends Internal_BaseController ,user里的U大写即可。
——————————————————————————————————————————————————
./application/controllers/global.php:class InitController extends Controller
./libraries/controller.php abstract class Controller
再次对这个
./application/controllers/internal/base.php:abstract class internal_BaseController extends InitController //类名和文件夹名一样?这个是和路由的php代码设置及nginx的转写规则有关。
require_once dirname(dirname(__FILE__)) . '/global.php';
继承自:
./application/controllers/global.php:class InitController extends Controller
继承自:
./libraries/controller.php abstract class Controller
这个名称都可随便写,如base.php,但里面的类前缀部分也得变得有关联更符合要求,这块不规范像这样:
如:./application/controllers/internal/my.php
require_once 'base.php';
class Internal_MyController extends Internal2_BaseController
//class Internal_MyController extends Internal_BaseController
./application/controllers/internal/base.php也一样得有关联:
require_once dirname(dirname(__FILE__)) . '/global.php';//前面上一层目录的global.php
//abstract class Internal_BaseController extends InitController
abstract class Internal2_BaseController extends InitController //加个2后,在my.php里也加个2继承下,也没关系运行没有问题,只是不规范罢了。
为何直接写没有自己写那个new 这个controller类就运行了呢?是因为两个地方:
1)\libraries\request.php里的execute对目录进行分析,对controller类进行反射类实例化:
(1)internal目录进行处理:
if ($this->directory) {
// Add the directory name to the class prefix
$directory = str_replace (array('\\', '/', '//'), '_', trim($this->directory, '/'));
$directories = explode('_', $directory);
foreach ($directories as $directory) {
$prefix .= ucfirst($directory) . '_';
}
}
(2)对反射类进行处理,找不到文件则到core.php来进行查找定位处理:
try {
// Load the controller using reflection
$class = new ReflectionClass(ucfirst($prefix) . ucfirst($this->controller) . 'Controller');
if ($class->isAbstract()) {
throw new KoException('Cannot create instances of abstract controller: :error', array(':error' => ucfirst($this->controller) . 'Controller'));
}
} catch (Exception $e) {
if ($e instanceof ReflectionException) {
$this->status = 404;
} else {
$this->status = 500;
}
$this->sendHeaders();
exit(0);
}
用了do while串连起这个反射类,如知识点如下:
(3)为何不同的目录可以实现根据nginx里的d参数两个不同的域名呢,是因为下面这块实现的levoo.com/openapi === api.levoo.com:
一套代码通过nginx转写d到不同目录后,最终调用的都是:global.php,如下:
/data/htdocs/levoo.com/application/controllers/manage.php//class ManageController extends FrontController
/data/htdocs/xiyou.cntv.cn/application/controllers/front.php//class FrontController extends InitController
/data/htdocs/xiyou.cntv.cn/application/controllers/global.php//class FrontController extends InitController
/data/htdocs/levoo.com/application/controllers/openapi/manage.php
class Openapi_ManageController extends Openapi_BaseController //它们最终都直接继承了global.php文件
require_once dirname(dirname(__FILE__)) . '/global.php'; //class InitController extends Controller
最后,execute全部代码如下,试图调度控制器/动作。如果要求表示它需要被调度,移动到下一个请求行动。
(2)core.php的autoload和findFile找文件这个查找的core.php是在fontend里的index.php包含bootstrap.php再包含core.php引入的,给反射类的查找文件指名了方向,在bootstrap.php最后一行(Request::instance()->dispatch();)便是自动根据core.php里的__autoload,找到request.php文件进行操作里面的相关函数的一个立即体现(这块的core.php直接引入太关键了),\application\bootstrap.php:
自己重新定了autoload,而不是__autoload:
将__autoload换成loadprint函数。但是loadprint不会像__autoload自动触发,这时spl_autoload_register()就起作用了,它告诉PHP碰到没有定义的类就执行loadprint()。
/data/htdocs/xiyou.cntv.cn/application/bootstrap.php:spl_autoload_register(array('Ko', 'autoload')); //这个是调用静态的方法:
spl_autoload_register() 调用静态方法
摘自:http://blog.csdn.net/panpan639944806/article/details/23192267
更多分析再参考自己写的这篇文章:http://jackxiang.com/post/5877/
步骤二:路由配置如下
/data/htdocs/my.boosh.com/application/bootstrap.php //把internal的路由指向前面建立的目录
Route::set('internal_route', 'internal(/<controller>(/<action>(/<__KO_VARS__>)))', array('__KO_VARS__' => '.+'))
->defaults(array(
'directory' => 'internal',//下面配置的Nginx指向这个目录并作转写即可:d=internal。
'controller' => isset($default_controller) ? $default_controller : 'index',
'action' => isset($default_action) ? $default_action : 'index',
));
这块路由的名称其实不重要,加个2也是可以的:
Route::set('internal2_route', 'internal(/<controller>(/<action>(/<__KO_VARS__>)))', array('__KO_VARS__' => '.+'))
为何配置这个bootstrap.php呢?htdocs的入口查看:
/data/htdocs/my.boosh.com/frontend/index.php
// Bootstrap the application
require_once APP_PATH . 'bootstrap.php';
步骤三:Nginx的配置文件
server
{
listen 80;
server_name i.boosh.com boosh.com;
index index.html index.htm index.php;
root /data/htdocs/my.boosh.com/frontend;///data/htdocs/boosh.com/frontend;被域名www.boosh.com用了,所以,my.boosh.com用作http://boosh.com使用。
location / {
rewrite "^/api/(album|general|partner|rank|search|user|video|stb|navigation|app|mobile|public|oauth|my|activity|manage|oauth2|favorite)\.([a-zA-Z]{4,})$" /index.php?d=internal&c=$1&a=$2&$args last;
rewrite "^/(album|general|partner|rank|search|user|video|stb|navigation|app|mobile|public|oauth|my|activity|manage|oauth2|favorite)/([a-zA-Z]{4,})$" /index.php?d=internal&c=$1&a=$2&$args last; //这一行必须要有,否则就没法通过get的d参数实现路由,也就是说即使后面分离后,前面新加的internal目录不用挪动,是nginx转写时加上了d,对album这些写死在nginx配置里的action进行加上d=internal,进而框架里的bootstrap.php进行了路由设置且支持路由。
前端访问情况:
直接通过api的接口访问:
http://i.boosh.com/album/addalbum (新加的域名访问A接口)
直接通过主站进行加上一个api进行访问:
http://boosh.com/api/album/addalbum(主站直接也可访问这个A接口)
对于接口输出比如ajax的json示例,此时一般都是关闭了模板,直接用自带函数进行输出,如下:
http://levoo.com/activityvideo/getactivityvideo/eid/14544893367247/typeid/1/pagenum/11/pagesize/20/sortby/new/format/jsonp/jsoncallback/cmscallback
=================View与smarty的一个接合问题=======================
前面新的一个interface没有和主站一个目录,而是另一个目录里,如何与smarty搭上边呢?
vi base.php
require_once dirname(dirname(__FILE__)) . '/global.php'; //与上一层目录的global.php进行引用,也就是主站的global.php
这个global.php主要干些啥?
1)获取进来的各种jsonP参数:
$this->varname = trim($this->getRequest()->getParam('varname'));
$this->jsoncallback = trim($this->getRequest()->getParam('jsoncallback'));
2)失败输出json/输出xml。
3)读取写入配置文件。
4)设置语言包位置及路径:css/js/images。
5)是否自动输出模板。
6) 一些共性的函数操作放里面,特别是像登录啥的。
interface/my.php
这就是真正的controller了,里面写这个页面特别的一些输出及模板渲染,经典的controller/action。
总之,就是通过view的工厂类, $this->autoRender(FALSE); 这个是不输出模板不需要smarty。要输出刚好是True,输出用了smarty,主站需要用,它是写在/controller/front.php里的,$this->view = View::factory($this->themesPath['themePath'] . '/' . $this->request->getController() . '/' . $this->request->getAction());,这一行调用了:\libraries\view.php,它引入了:require_once 'smarty/Smarty.class.php';且view.php初始化了smarty这个类,如下:
public function __construct ($file = NULL, array $data = NULL)
{
if (! empty($file)) {
$this->setView($file);
}
$this->_smarty = new Smarty();
if ($data !== NULL) {
$this->_smarty->assign($data);
}
$this->_smarty->template_dir = Ko::config('smarty.template_path');//这个是绝对路径前缀读取的是./smarty.php: 'template_path' => APP_PATH. 'views/',静态的KO类实现读取配置文件,Nginx指向->\frontend\index.php 包含=》\application\bootstrap.php=》它里面引用:require_once SYS_PATH . 'core.php'; =》public static function config ($group)静态函数实现。。
$this->_smarty->cache_dir = Ko::config('smarty.cache_path');
$this->_smarty->compile_dir = Ko::config('smarty.compile_path');
$this->_smarty->config_dir = Ko::config('smarty.configs_path');
并提供了,__set ,__get,render这些基础函数的二次根据这个view类的smarty封装。实现了smarty模板类。
模板问题再细一点:
出现模板找不到:The requested view themes/zh/my/video.html could not be found ~ SYS_PATH/view.php [ 174 ]
(这里的view.php并没有被直接包含,而是框架core.php里autoload和findFile经过在new类时查找到\libraries\目录并引入:public static function autoload ($class),根据文件名及类名引入的, Ko::autoload('My_Class_Name');class_exists找不到则去findFile查找并引入:require_once $path;。)
这就是这些类查找也好,命名也好的规范,对Controller Model Service,影响了其类名的写法必须符合,否则找不到,见libraries/core.php:
controller和Models和server分层在类名上体现是必要的,
无聊咔咔(5**773145) 17:52:56
__autoload
回忆未来-向东-Jàck(372647693) 17:53:21
加上findFile
一般框架都这样,MVC啥的全是这样搞的,有谁有新方法找我。
把找到的文件目录给缓存起来,下次就不用去再找一次了。
再把controller /action的类名加上比如Controller之类,
规范规范,加个smarty,一个PHP的架子就搭建起来了嘛要。
回忆未来-向东-Jàck(372647693) 17:57:30
controller和Models和server分层在类名上体现是必要的,如下:
class EventModel extends Model
class NewsService extends AdminBaseService
class ActController extends FrontController
findFile这个是围绕着环境变量且伴随是否caching进行编写的,主要是这两个核心的全局数组,步骤如下:
private static $_paths = array(APP_PATH , SYS_PATH);// Include paths that are used to find files
private static $_files = array(); // File path cache
1)如果是Cahceing就在self::$_files就是一个一个的文件以文件名为key,值就是文件绝对路径的数组里根据传入的文件名进行查找对应的路径,有则直接返回。
2)没有则对'config' 'i18n' 'messages'进行查找,这三个地方估计没法cache进来,得特殊处理。
3)还找不到,则self::$_paths ,这个是一堆的路径数组,一个一个去把文件名放进去拼成绝对路径,再用is_file去循环所有路径判断这个文件到底在哪儿,直到找到这个文件的绝对路径。
4)对这个文件的路径再放回到第1部里的self::$_files里去,同时返回这个文件的绝对路径,autoload 里的require_once $path;就是引入经过一堆查找到的文件,真心不容易啊。
__上面描述为何能自动找到这个View.php,Smarty被它重新封装并形成factory进行重新对象化,如何使用smarty如下:______
public function video()
{
$themesPath = $this->getLang(); //global.php里写好路径,且是相对路径,所以报错也是相对的,但输入smarty时拼成一个绝对路径,如下:
//[root@iZ25z0ugwgtZ config]# grep -r "views" ./
//./smarty.php: 'template_path' => APP_PATH. 'views/',
$this->themesPath = $themesPath;
$this->view = View::factory($this->themesPath['themePath'] . '/' . $this->request->getController() . '/' . $this->request->getAction());
$this->view->textlinkhotmax = "jackxiang";
$this->view->specialmax = "xiangdong";
$this->setFile("my/video");//如果不写,默认则是http://xxx.com/controller/funciton.html为模板。 https://boosh.com/my/video =>my目录下的video.html。
}
模板路径:/application/views/themes/zh/my/video.html 前面的这个路径是从/application/controllers/global.php 里写死一数组定义的:
[root@iZ25z0ugwgtZ my]# cat ~+/video.html
jack
<{$textlinkhotmax}>
<{$specialmax}>
访问输出:
jack jackxiang xiangdong
下面主要是core.php和request.php两个,core.php主要是autoload和findFile的一个结合,及reques.php是路由Route和反射类ReflectionClass的一个集合,最后是两个文件对新纳入文件的文件命名和类命名的的约束,形成KO的核心的框架。
第二大步,再谈一谈其高内聚合、低耦合的一些设计模式,如下:
在很多时候反射也是唯一的选择。为什么我们会选择使用反射?因为我们没有办法在编译期通过静态绑定的方式来确定我们要调用的对象。例如一个ORM框架,它要面对的是通用的模型,此时无论是方法也好属性也罢都是随应用场景而改变的,这种完全需要动态绑定的场景下自然需要运用反射。还例如插件系统,在完全不知道外部插件究竟是什么东西的情况下,是一定无法在编译期确定的,因此会使用反射进行加载。其实,包同学的反驳文章里也是持这种观点的:
什么是php反射类,顾名思义,可以理解为一个类的映射。
举个例子:
class fuc { //定义一个类
static function ec() {
echo '我是一个类';
}
}
$class=new ReflectionClass('fuc'); //建立 fuc这个类的反射类
echo $class; //输出这反射类
Class [ class A ] { @@ F:\phpweb\myPHP\test.php 23-30 - Constants [0] { } - Static properties [0] { } - Static methods [0] { } - Properties [0] { } - Methods [1] { Method [ public method __construct ] { @@ F:\phpweb\myPHP\test.php 26 - 29 } } }
$fuc=$class->newInstance(); //相当于实例化 fuc 类
$fuc->ec(); //执行 fuc 里的方法ec
/*最后输出:我是一个类*/
其中还有一些更高级的用法
$ec=$class->getmethod('ec'); //获取fuc 类中的ec方法
$fuc=$class->newInstance(); //实例化
$ec->invoke($fuc); //执行ec 方法
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
$class->getmethod('ec')-> invokeArgs($fuc, $params); //有点绕:class是反射类,获取这个反射类里的ec方法,用实例化后的$fuc,并传入相关ec的参数。
对该方法的传入参数及调用的方法实践OK如下:
输出:
---------- 调试PHP ----------
getAPI函数里的apiNO参数:1
Array
(
[apiName] => getVideo
[apiIntro] => getVideo from Server
)
是通过数组进行扩展多个参数的测试:第三个参数
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
$methodsArrAll = $class->getMethods(); //获取所有的方法名
Reflection::export($class);//通过此方法可以看到ReflectionClass也就是fuc类中所有的属性和方法
上面的过程很熟悉吧。其实和调用对象的方法类似
只不过这里是反着来的,方法在前,对象在后
举例: 这样的方法实现在面像对象的框架里有这样的用法,见:http://kohanaframework.org/ request.php
框架核心摘录解析\libraries\request.php,涉及到before/after(写日志)函数的先后,特别注意:
来自:http://blog.csdn.net/21aspnet/article/details/6952432
给redirect函数里加上日志:
找不到action和对应方法时调用404.
/**
* 当调用的action不存在时调用该方法
* Overload
*
* @see application/controllers/InitController::__call()
*/
public function __call($method, $arguments=NULL)
{
$this->redirect(KO::config('url.main_url') . 'error.html');
}
———————————直接访问Url里的一个controler下用action直接输出调用forward时在调核心request类里的函数返回$this是什么的理解。—————————
这里的 $this->request是从controller.php里继承来的:
class ApiController extends InitController,class InitController extends Controller。
public function __construct (Request $request)
{
// Assign the request to the controller
$this->request = $request;
}
而forward函数是request.php里的:
/**
* Forward to another controller/action.
*
* @param string $action
* @param string $controller
* @param string $directory
* @param array $params
* @return void
*/
public function forward($action, $controller = null, $directory = null, array $params = null)
{//方法,文件类名,文件路径(默认是框架路径,这儿可在框架路径下建立自己的路径),参数(是通过__call: public function __call($method, $arguments = null) 传入的)
。。。。。。
这些都是教简单,而return正是在request.php里函数常用到,如下:
/**
* Set parameters
*
* Set one or more parameters. Parameters are set as userland parameters,
* using the keys specified in the array.
*
* @param array $params
* @return Request
*/
public function setParams(array $params)
{
foreach ($params as $key => $value) {
$this->setParam($key, $value);
}
return $this;
}
/**
* Set request directory
*
* @param string
* @return Request
*/
public function setDirectory ($directory)
{
echo "<br>".__FILE__.__LINE__.$directory."<br>";
$this->directory = $directory;
return $this;
}
setParams和setDirectory都是forward里的,如下:
public function forward($action, $controller = null, $directory = null, array $params = null)
{
if (null !== $params && is_array($params)) {
$this->setParams($params);
}
if (null !== $controller) {
$this->setController($controller);
// Directory should only be reset if controller has been specified
if (null !== $directory) {
echo "Here,Jack...";
$this->setDirectory($directory);
}
}
$this->setAction($action)
->setDispatched(false);
}
这里的return this如何理解:
http://zhidao.baidu.com/link?url=QU-yClJDjln0YRwEZr8nO2wBLUIqDx4oPeYL6BykgCZvrclh78YpY9qnsfRaYWZam87QCaMEdmk06ZGd_DKY2q
这是类里面的一个函数,没有形参,函数里最后一句是return $this; 请问这个是返回了什么东西啊?返回了谁的自身?
public function getmodule() {
$this->cache->key = 'sdmodule';
$result;
.....
....//中间很多句子。
....
$this->smarty->assign("modules", $result);
return $this;
}
就是返回这个对象,在PHP类里面$this关键字就是代表这个类内部的引用。如你上面所说的return $this;就是相当于把该对象返回到方法getmodule() 中。
比如:
$abc=new class; //class是指你那个类
echo $abc->getmodule()->cache->key; //输出sdmodule
也就是getmodule() 拥有了该类的所有的成员和方法。
—————————通过forward方法传入和通过Request::instance()->dispatch();的区别(instance获取uri后调__construct进行action,controller,路径path的类变量赋值实现了类似forward的传参)—————————
/**
* Main request singleton instance. If no URI is provided, the URI will
* be automatically detected using PATH_INFO, REQUEST_URI, or PHP_SELF.
*
* @param string URI of the request
* @return Request
*/
public static function instance ($uri = TRUE)
{
static $instance;
if ($instance === NULL) {
.......//各种获取Uri的方法
$instance = new self($uri);//把uri传入类中,下面:
// public function __construct ($uri)//它就是用来对uri进行解析得到forward里的a,c,d。forward只是直接传入罢了,不用在这儿初始化就在于此,直接传入修改了类需要运行的相关变量,除开get参数外,都在这里进行分析并写入类全局变量,通过this作为句柄作操作。
//
}
return $instance;
}
forward和__construct的相同点和不同点:
public function __construct ($uri)
{
Ko::log('debug', __CLASS__ . ' Library loaded');
// Are query strings enabled in the config file?
// If so, we're done since segment based URIs are not used with query strings.
if (Ko::$enable_query_strings && isset($_GET['c'])) {
$this->setController(Security::xss_clean($_GET['c']));
if (isset($_GET['a'])) {
$this->setAction(Security::xss_clean($_GET['a']));
}
if (isset($_GET['d'])) {
$this->setDirectory(Security::xss_clean($_GET['d']));
}
$this->params = array();
return;
}
......对接受到的变量赋值到类变量后立即进行安全检查并过滤......
foreach ($routes as $route) {
if (($params = $route->matches($uri)) !== false) {
$this->directory = Security::xss_clean($params['directory']);
$this->controller = Security::xss_clean($params['controller']);
$this->action = Security::xss_clean($params['action']);
$this->params = $params;//变量从uri里获取到的
return;
}
}
//上面找不到的则进行404处理并输出找不到的头页面处理
}
而在执行时这个this-;>params还会用到:
function excute(){
if ($this->isDispatched()) {
$class->getMethod($action)->invokeArgs($controller, $this->params);//这儿
}
你会问这个POST,GET参数去哪儿了?在request;.php里有:
public function getParam ($key, $default = NULL)
{//对各种变量都作了xss检测
if (isset($this->params[$key])) {
return Security::xss_clean($this->params[$key]);
} elseif (isset($_GET[$key])) {
return Security::xss_clean($_GET[$key]);
} elseif (isset($_POST[$key])) {
return Security::xss_clean($_POST[$key]);
} else {
return $default;
}
}
再就是通过_set,_get进行属性读取,参看:php面向对象_get(),_set()的用法,http://blog.sina.com.cn/s/blog_4565cc770100bv2u.html
http://jackxiang.com/post/2766/ 里面有代码对其实践和论述。
在实际编写代码调用:
public function minialbum(){
$uuid = $this->getRequest()->getParam('uuid');
public function setController ($controller)
{
$this->controller = $controller;
return $this;
}
//而forward是自己写的,所以就不用进行安全的xss检测
public function forward($action, $controller = null, $directory = null, array $params = null)
{
if (null !== $params && is_array($params)) {
$this->setParams($params);
}
if (null !== $controller) {
$this->setController($controller);
// Directory should only be reset if controller has been specified
if (null !== $directory) {
$this->setDirectory($directory);
}
}
$this->setAction($action)
->setDispatched(false);
}
六:错误报告:
$show_debug_errors = false;
在配置文件:/data/htdocs/jackxiang.com/application/bootstrap.php
Ko::init( array(
'base_url' => '/',
'errors' => isset($show_debug_errors) && $show_debug_errors,
后赋值给Library/core.php里的:Ko::$errors,后在/library里的request;.php里面决定是否输出错误:
public function errorPage($msg = null, $data = array())
{
if(Ko::$errors === TRUE) {
throw new KoException($msg, $data);
exit;
} else {
return $this->forward('index', 'error');
}
}
七:文件位置缓存实现:
查找文件路径的缓存:'caching' => TRUE,
core.php
isset($settings['caching']) && self::$caching = (bool) $settings['caching'];
if (self::$caching === TRUE) {
// Use the default cache directory
self::$cache_dir = DATA_PATH . 'cache';
self::$_files = self::cache('Ko::findFile()');
}
self::$_files = self::cache('Ko::findFile()');
public static function cache ($name, $data = NULL, $lifetime = 3600)
{
// Cache file is a hash of the name
$file = sha1($name) . '.txt';
// Cache directories are split by keys to prevent filesystem overload
$dir = self::$cache_dir . DIRECTORY_SEPARATOR . "{$file[0]}{$f
配置文件:
/data/htdocs/jackxiang.com/application/bootstrap.php
Ko::init( array(
'base_url' => '/',
'errors' => isset($show_debug_errors) && $show_debug_errors,
'index_file' => isset($index_file) ? $index_file : 'index.php',
'caching' => TRUE,
'enable_query_strings' => TRUE,
'threshold' => isset($log_threshold) ? $log_threshold : 0,
));
——————————————————————————————————————————
八.自制session:
protected $session;
$this->session = Session::instance();
操作session:
protected function checkLogin ()//检查登录
{
if ($verifycode != $this->session->verifycode || !$this->session->userinfo ) {//从session里取不到
$this->session->userinfo = $this->ssoCheck($verifycode);//从接口里取并存入session
$this->session->verifycode = $verifycode;
......
}
return $this->session->userinfo;
}
protected function ssoCheck($verifycode){
......//查接口返回数据:'passport_url'=> 'http://passport.cntv.cn/ssocheck.jsp',Curl
$userinfo = json_decode($ret,true);
return $userinfo;//返回
}
九:写错误日志函数及错误的统一捕获:
场景:问,最近我做了一个日志系统,PHP在调用时出现因为异常错误导致没有运行到日志函数就退出了,
请问:怎么实现对这个错误的捕获?还问:PHP里的try catch可能并不能捕获里面的所有错误的实际情况。
这个问题其实是想问对PHP的set_error_handler函数,及错误处理函数,它捕获能导致php脚本停止运行的严重错误(这类错误是不能被set_error_handler ()捕获的),
一旦脚本停止运行,customend()函数就会被调用,在customend()函数中通过error_get_last()来判断脚本是正常结束还是发生严重错误而中断,如果是发生严重错误而中断,则运行错误处理程序。try-catch 无法在类的自动加载函数 __autoload() 内生效。try-catch 用于捕获异常,无法捕获错误,例如 trigger_error() 触发的错误,异常和错误是不一样的。
摘自:http://jingyan.baidu.com/article/046a7b3ed5e233f9c37fa969.html
参考:PHP 的异常处理、错误处理:error_reporting,try-catch,trigger_error,set_error_handler,set_exception_handler,register_shutdown_function
http://www.php-note.com/article/detail/779
******web端打开写SQL日志及分析。———show_debug_errors是前端错误打开于否的开关,
而那个threshold是分开关,如果要写Mysql日志则:$log_threshold = 3;小于3的threshold则不打印日志的,
上面两变量均在index.php入口配置,在application下的bootopen.php进行init()函数的赋值。******
如果等于threshold的值等于4则:debug: Request Library loaded ,也就是相当于debug的日志可打印到日志里来了,等于1只报error错误日志,如等于0则全关闭了即使有错。
* Log Thresholds:
* 0 - Disables logging completely
* 1 - Error Messages (including PHP errors)
* 2 - Alert Messages
* 3 - Informational Messages
* 4 - Debug Messages
*/
$log_threshold = 3;
对于这个;show_debug_errors这个值,则应该用Nginx给屏蔽掉,当然上线后关闭是最好的,提高运行效率及稳定性,Nginx配置如下:
实践Ok如下:
如错误页面在这儿:http://my.jackxiang.com/error
vi my.cnf
在server里添加:
error_page 404 = ./error;
error_page 500 502 503 504 = ./error;
#下面也行:
error_page 404 = http://my.jackxiang.com/error.html;
error_page 500 502 503 504 = http://my.jackxiang.com/error.html;
来自自己的参考:http://jackxiang.com/post/6769/
要想写日志得满足三个条件:
1./application/bootopen.php 里的'errors' 是TRUE,这样就注册了register_shutdown_function函数,进而在log.php里写日志
2.如果是后台程序立即触发就写,前台程序是关闭的写的。
register_shutdown_function这个的用法见,好多单例都给整了这个东东在上面:http://jackxiang.com/post/6784/
例1:核心都有用到register_shutdown_function
/libraries/core.php
// Enable the Ko shutdown handler, which catches E_FATAL errors.
register_shutdown_function(array('Ko' , 'shutdown_handler'));
if (self::$errors === TRUE) {
// Enable the Ko shutdown handler, which catches E_FATAL errors.
register_shutdown_function(array('Ko' , 'shutdown_handler'));
// Enable Ko exception handling, adds stack traces and error source.
set_exception_handler(array('Ko' , 'exception_handler'));
// Enable Ko error handling, converts all PHP errors to exceptions.
set_error_handler(array('Ko' , 'error_handler'));
}
例2:web运行完再写日志更是常用:
/libraries/log.php
// Write the logs at shutdown
register_shutdown_function(array(self::$_instance[$group], 'write'));
frontend/index.php //这儿打开的******(这块打开如果action类和方法不存在则会直接报错)
// Define wether show debug message.
$show_debug_errors = true;
/application/bootopen.php
Ko::init( array(
'base_url' => '/',
'errors' => isset($show_debug_errors) && $show_debug_errors,
/libraries/core.php
if (self::$errors === TRUE) {
// Enable the Ko shutdown handler, which catches E_FATAL errors.
register_shutdown_function(array('Ko' , 'shutdown_handler'));
/libraries/log.php
public static function instance($group ='default')
{
if (!isset(self::$_instance[$group]) ||self::$_instance[$group] === NULL) {
// Create a new instance
self::$_instance[$group] = new self($group);
// Write the logs at shutdown
register_shutdown_function(array(self::$_instance[$group], 'write'));
}
return self::$_instance[$group];
}
\libraries\database.php
// Log levels
private static $log_levels = array (
'error' => 1,
'alert' => 2,
'info' => 3,
'debug' => 4,
);
public static function log($type, $message)
{
// Should we log these to file?
if (self::$log_levels[$type] <= self::$threshold) {//type==debug<====4<=2
self::$log->add($type, $message);
}
}
\libraries\database.php
// Log query
Ko::log('info', str_replace(array("\r\n", "\r", "\n"), " ", $sql));
self::$log->add($type, $message);
}
十:上完静态文件,要改config.php里的version,以防止那个CDN了旧的,没法查看。
<?php
defined('SYS_PATH') or die('No direct access allowed.');
return array(
'version' => '2.005',
————————————————————————————
jquery.Jcrop.js?ver=<?php echo $this->_tpl_vars['version']; ?>
直接在框架初始化时赋值比较方便编写些,多层继承的最上层写上:
class Site_PartinController extends FrontBaseController
class FrontBaseController extends FrontController
class FrontController extends InitController
class InitController extends Controller(init.php里进行模板赋值的)
/application/controllers/init.php
/**
* Overload
*/
public function after ()
{
$this->view->version = KO::config('config.version');
模板里:
<script src="<{$staticUrl}>js/My97TimePicker/WdatePicker_time.js?ver=<{$version}>" type="text/javascript"></script>
flashvars["config"] ="<{$flashXmlUrl}>?ver=<{$version}>";// 如上传组件初始化参数的读取加上版本号
十一:单例模式,一些常规操人作,多用单例模式,这种模式不适合用于写PHP的daemon,容易出现内存不够,如不及时释放:
尽管PHP退出后就释放了,不像Java,但好处还是有的:http://blog.csdn.net/jungsagacity/article/details/7618587
http://blog.sina.com.cn/s/blog_6f49a3c30100qgiy.html
\libraries\session.php
public static function instance ($group = 'default')
protected function __construct ($group = 'default')
register_shutdown_function(array($this , 'write_close'));
public function sessionid ()
public function create ()
public function regenerate ()
public function unsetAll()
public function destroy ()
public function write_close ()
public function delete ($keys)
使用方法:
// Init Session
$this->session = Session::instance();
$this->session->set('findemail',$userInfo['email']);
$findemail = $this->session->get('findemail');
$this->session->unsetAll();
\libraries\config.php
public static function instance ($group = 'default')
final private function __construct ($group = 'default')
public function attach (Config_Driver $reader, $first = TRUE)
public function detach (Config_Driver $reader)
public function load ($group)
public function save ($file, $config = NULL)
public function copy ($group)
final private function __clone ()
使用方法:
$config[$group] = Config::instance('messages')->attach(new Config_File('messages'))->load($group);
Config::instance('lang')->attach(new Config_File('i18n'))->load($lang . DIRECTORY_SEPARATOR . $group);
Config::instance('messages')->attach(new Config_File('messages'))->save($file, $data);
更高级别封装readConfig:$this->_pbConfig = $this->readConfig('pbconfig');
controllers/global.php
/**
* 读取数组配置文件信息
* $this->readConfig('config'); ==> data/messages/config.php
* @param string $group 配置文件名,不带扩展名.php
*/
protected function readConfig($group)
{
\libraries\log.php
public static function instance($group ='default')
final private function __construct()
register_shutdown_function(array(self::$_instance[$group], 'write'));
public function attach(Log_Writer $writer, $types = NULL)
public function detach(Log_Writer $writer)
public function add($type, $message)
public function write ()
private function __clone()
使用方法:
$returnlog = Log::instance('videoreturn')->attach(new Log_File(DATA_PATH . 'logs/' . date('Ymd') . '/', 'setCheckVideoreturn-' . date('Ymd') . '.php'));
$logger = Log::instance(strtoupper($prefix))->attach(new Log_File(DATA_PATH . 'logs/' . date('Ymd') . '/', $prefix . '_' . date('Ymd') . '.php'));
$sharelog = Log::instance('share')->attach(new Log_File(DATA_PATH . 'logs/' . date('Ymd') . '/', 'share-' . date('Ymd') . '.php'));
$sharelog->add('succ:',' from vid:'.$vid.' to uuid :'.$videoData['video']['uuid']);
分析该写日志的实现:
$log = Log::instance(__CLASS__)->attach(new Log_File(DATA_PATH . '/logs/' . date('Ymd') . '/mysql-error-' . date('Ymd') . '.log'));
1)文件及目录都是777:
new Log_File:\libraries\log\file.php, 分析文件函数:
(1)目录777:__construct里直接创建目录: if (! mkdir($directory, 0777, TRUE) || ! is_writable($directory)) {
(2)文件777:public function write(){
\libraries\cookie.php
final private function __construct ()
public static function get ($key, $default = NULL)
public static function set ($name, $value, $expiration = NULL)
public static function delete ($name)
使用方法:
Cookie::set('language1',$language);
$lang = Cookie::get('language1');
十二:关于前端代码的路径等配置:前端Js/css静态域名版本号(反CDNl:version)的配置:
/controllers/site/preview.php
关于版本号及前端,每个controller下都有一个preview.php层层去继承InitController在具体的controller下配置相关前端Js输出及CSS输出的路径:
$this->view->version = KO::config('config.version');
public function after()
{
//$this->getwatch();
$this->view->themePath = $this->themePath;
$this->view->publicPath = 'themes/common';
$this->view->cssPath = 'themes/zh';
$this->view->jsPath = 'themes/zh';
$this->view->lang = (array)Ko::lang('common','zh');
$this->view->staticURL = KO::config('url.static_url');
$this->view->mainURL = KO::config('url.main_url');
$this->view->flashURL = KO::config('url.flash_player_url');
$this->view->basePath = $this->getBaseUrl();
$this->view->version = KO::config('config.version');
$this->view->render();
}
十三:smarty模板输出:
最上层的controller下的before还负责smarty模板的指定:
\libraries\controller.php
public function before ()
{
if ($this->auto_render === TRUE) {
// Load the template
$this->template = $this->request->getController() . '/' . $this->request->getAction();
$this->view = View::factory($this->template);
}
}
继承后再重写:
public function before ()
{
// Auto render , use user defined template.
$this->autoRender(TRUE);
parent::before();
———————上面的before,下面的after都是在Library/request.php里进行按before在前,action在中间,after在后面处理的__________
Before处理如下前期问题,参数获取/模板工厂(smarty)等层层继承,层层parent::before();:
//Overload:英文是重载,国人:同名函数,叫 方法重写
class ActController extends FrontController
{
public function before()
{
parent::before();//访问InitController里的after
}
}
class FrontController extends InitController
{
/**
* Overload
*/
public function before()
{
$lang = '';
parent::before();// 初始化模板等...
class InitController extends Controller
{
public function before()
{
// Init Session
$this->session = Session::instance();//获取参数等全局逻辑准备
$this->varname = trim($this->getRequest()->getParam('varname'));
$this->jsoncallback = trim($this->getRequest()->getParam('jsoncallback'));
}
abstract class Controller
public function before ()
{
if ($this->auto_render === TRUE) {
// Load the template
$this->template = $this->request->getController() . '/' . $this->request->getAction();
$this->view = View::factory($this->template);//模板进入工厂
}
}
十四:关于after:
这块输出头也是放after里的:
\controllers\global.php
public function after()
{
// Send headers: Cache-Control & Expire.
$max_age = $this->getRequest()->getParam('max_age', 0);
if ($max_age > 0) {
$this->getRequest()->addHeader('Cache-Control', 'max-age=' . $max_age);
$this->getRequest()->addHeader('Expires', gmdate('D, d M Y H:i:s', time() + $max_age) . ' GMT');
}
else {
$this->getRequest()->addHeader('Cache-Control', 'no-cache');
$this->getRequest()->addHeader('Expires', '0');
$this->getRequest()->addHeader('Pragma', 'No-cache');
}
parent::after();
}
上面的after函数被:\controllers\front.php重写并parent调用之:
/**
* Overload
*/
public function after()
{
......
$this->view->version = KO::config('config.version');
$this->view->pageCode = $this->getPageCode();
$this->autoRender(TRUE);
parent::after();
}
它也是在后面才输出的,调用在Library/request.php:
上面的日志在后面after写的:
libraries\request.php
$class->getMethod('after')->invoke($controller);
摘录自: \libraries\request.php
try {
// Load the controller using reflection
$class = new ReflectionClass(ucfirst($prefix) . ucfirst($this->controller) . 'Controller');
if ($class->isAbstract()) {
$this->errorPage('Cannot create instances of abstract controller: :error',
array(':error' => $prefix . ucfirst($this->controller) . 'Controller'));
}
// Create a new instance of the controller, Interrupted by dispatcher anytime
$controller = $class->newInstance($this);
if ($this->isDispatched()) {
$class->getMethod('before')->invoke($controller);
}
try {
$action = empty($this->action) ? Route::$default_action : $this->action;
if ($this->isDispatched()) {
$class->getMethod($action)->invokeArgs($controller, $this->params);
}
} catch (ReflectionException $e) {
$class->getMethod('__call')->invokeArgs($controller, array($this->getAction() , $this->params));
}
if ($this->isDispatched()) {
$class->getMethod('after')->invoke($controller);
}
}
十五:关于日志(新加daemon日志实现立即写,不用注册的退出时才写:register_shutdown_function):
日志输出在web时是执行完后再输出,而cli模式是立即输出,(关于错误级别$type定义在第九有备注到,如info级别:Ko::log('info', str_replace(array("\r\n", "\r", "\n"), " ", $sql));),如下:
\libraries\log.php
public function add($type, $message)
{
// Create a new message and timestamp it
$this->_messages[] = array (
'time' => date(self::$timestamp),
'type' => $type,
'body' => is_string($message) ? $message : var_export($message, true),
);
if(Ko::$is_cli === TRUE) {
$this->write();
}
return $this;
}
WEB没有立即写,而是后面才写,并没有在request中的after里,而是注册了shutdown函数write:
public static function instance($group ='default')
{
......
// Write the logs at shutdown
register_shutdown_function(array(self::$_instance[$group], 'write'));//这儿调用写
}
return self::$_instance[$group];
}
调用:\controllers\audit.php
$returnlog->add('check start', '-----------------');
$returnlog->add('data post', $post);
这种实现方法在如果用php做daemon时会遇到从不退出就没有调用写导致里面的数据太大出现php内存不够并意外退出问题:
遇到问题,当守护进程时写日志时往往会出现因为框架是web的,在最后退出才写,而daemon是不退出的,导致内存暴增,解决办法如下:
修改为,如果是cli模式立即就写了得了:
十六:使用modules外挂模块方法:
Ko::modules( array(
'weibo' => 'weibo',
'libspace' => 'libspace',
'face' => 'face',
'nusoap' => 'nusoap',
'sdk' => 'sdk',
));
jackxiang.com/modules/ //jackxiang.com是根目录
face/ libspace/ nusoap/ sdk/ weibo/
ls modules/sdk/ //init.php文件名是必须要有的。
common.php init.php justwinitapi.ex.class.php
调用:
./application/models/app.php: $justwinit = new justwinit();
./application/controllers/front/front.php: $justwinit = new justwinit();
/**
* Overload
*/
public function before()
{
$this->checkToken();
}
public function checkToken()
{
$justwinit = new justwinit();
.......
}
十七:框架扩展:在\libraries\core.php里加一个写日志函数,以给daemon程序写当是cli模式时写:
十八:图片缩放之gd,示例:
关于图片上传及缩小,用到上传类libraries//upload.php,缩小gd类libraries/image.php ,
这儿遇到一个问题是上传后缀是大写的JPG时会报错,最后用strtolower($ext)保存为小写就没这个问题,
我从其它博客试了一下确实存在警告,最好还是小写的jpg后缀为好:http://jackxiang.com/post/3022/
缩小为148.148的等比例缩放,其使用方法如下:
十九:关于单例模式里和观察者模式一块用的问题:
1)写法于传统的PHP写法背离,不像C了,在写得少的人看来很是怪异:
$log = Log::instance(__CLASS__)->attach(new Log_File(DATA_PATH . 'logs/' . date('Ymd'), __CLASS__.'-' . date('Ymd') . '.log'));
2)这种单例与观察模式在代码提示时出现无法跳转,因为再智能的编辑器也没法知道前面这个数组是哪个具体对象了:
$writer['object']->write($messages);//这样当于一个写的类句柄通过观察者数组中取出后:Log_File->write($messages);
3)但为了什么耦合,为了少几个句柄,为了提高效率等,还得用,自己作下分析性参考:http://jackxiang.com/post/6825/ 。
二十:观察者模式:
http://www.cnblogs.com/baochuan/archive/2012/02/22/2362668.html
框架里的观察者模式:
/libraries/log.php
片段如下,这里的writer就是一个进来的类对象数组的一个集合(全是对象),这个:
这个对象去分别调用对象类里的方法,和上面的原始摘录Url里的示例一样的,如下用foreach去一个个的调用write方法,$writer['object']->write($messages);:
二十一:关于memcache或ttserver的缓存实现及配置:
配置文件这样写:
config/cache.php
使用时要这样用:
class TagModel extends Model
{
protected $cachePermanent = 'tt_server';// Permanent Cache KEY,来自配置文件的数组key,及config/cache.php tt_server对应。
return (array)$this->getPermanentCache($userid);
return $this->setPermanentCache($userid, $tags);
为什么可以这样用,因为前面的extends Model这个类的libraries/model.php里有:
[root@iZ25z0ugwgtZ libraries]# grep -r "setPermanentCache" ./
./model.php: protected function setPermanentCache($key, $data)
// Cache KEY
protected $cacheConfig = 'default';
protected function getCache($key)
{
return Cache::instance($this->cacheConfig)->get($this->makeCacheKey($key));//静态调用instance方法获取该类的实例
}
这个cache是有各种cache,这个是ttserver,我们来看它是如何实现这个实例的文件,libraries/cache/ttcache.php :
我都说这个直接传入一个string的tt_server,是怎么从前面配置文件里读取到这个数组的,不仅仅是前面的这个instance函数对这各种cache设备
类进行实例化外,还有对这个下面挂的设备本身的相关配置进行初始化,代码如下:
框架分析到此完结。EOF
AddTime:2016-05-28
加载外部modules方法及入口文件:
// Set the current module list
self::$_modules = $modules;
foreach (self::$_modules as $path) {
$init = $path . DIRECTORY_SEPARATOR . 'init.php';
if (is_file($init)) {
require_once $init;
}
}
也就是说会核心框架会自动加载这个init.php
D:\www\xiyou.cntv.cn[新svn代码]\trunk\codes\modules下的OAuth weibo qiniu 里面都有一个init.php,而这前面三个目录是怎么找的呢?
D:\www\xiyou.cntv.cn[新svn代码]\trunk\codes\application\bootstrap.php 里有让modules相对或绝对路径。 relative or absolute path.:
==================================
概述以上分析,从更高层次来分析其入口的调用层次分析:
model独立,model目录里文件均无互相调用(缓存也在这层,也就是数据层),
service加上业务调用model model/open,service之前也可相互调用给application提供数据。
application可以有多个目录,如多了个openapi的目录,这个目录可通过路由来配置指向即可:
./bootstrap.php:Route::set('openapi_route', 'openapi(/<controller>(/<action>(/<__KO_VARS__>)))', array('__KO_VARS__' => '.+'))
这个对像是传入/libraries/request.php,它又和/libraries/core.php接合,接合autoload和反射类到上面这些model、service、controller里找对应,
找不到时用try..catch出来错即可,而这个bootstrap.php,则被./frontend/index.php ./open/index.php包含进来(它里面有错谴责级别设置)后,进行:
// Bootstrap the application
require_once APP_PATH . 'bootopen.php'; ,于是对外可呈现出多个域名,因为在nginx里直接配置:root /data/htdocs/xiyou_alpha/frontend;就会从
fronted/index.php单入口里穿透并进入到各个层里,运行了,在层次上清晰,在代码上可灵活配置,这就是Ko的优势了,MVC加server层次间的调用规定如下:
一)model目录一个目录:open
二)service目录里调model的open里的类用前面加Open_,调open一级的就不用了。
三)controller里有open openapi两个目录,均调用了service里的open目录:
比如:
controller
./openapi/partner.php: $Open_OpenService = new Open_OpenService();
./open/usercenter.php: $open_OpenService = new Open_OpenService();
_________Model_________
.........//主站的model
/model/open/...开放平台
也就是说model最好是简单一点,service文件间可互相调用:
vi services/open/open.php Open_OpenService 原型可被同级的server其它文件调用如下:
vi services/audit.php: $Open_OpenService = new Open_OpenService();
而这个service呢,也调用了model层里的open目录下的modle:
$tokenModel = new Open_TokensModel();
$info = $tokenModel->getTokenInfoByToken($token);
上面两行的位置在哪儿?能通过类前面加上Open知道是model/open/位置:
/application/models/open/tokens.php class Open_TokensModel extends Model
为何getCache得在services目录里的base.php和library目录下的model.php里有:
protected function getCache($key)
{
return Cache::instance($this->cacheConfig)->get($this->makeCacheKey($key));
}
getCurrentPageVideosFromDB在model里不需要cache,但是getCurrentPageVideos就需要cache了:
public function getVideosByUserId($userId)
{
$userVideos = $this->getCache($userId);
if ($userVideos) {
return $userVideos;
}
$userVideos = $this->getVideosByUserIdFromDB($userId);
if ($userVideos && count($userVideos) > 0) {
$this->setCache($userId, $userVideos);
}
return $userVideos;
}
同样,在service里有些变量也需要cache,如cookie变量从cache里读取,为何需要专门函数实现?因为怕cache重复了,结合函数名,查询参数确保唯一。
/**
* 写入cookie - 您最近看过的
*/
public function setCookieHistoryVideoList($uuid)
{
//设置显示“您最近看过的”视频的个数
$MAX_COUNT = 5;
$cookieId = Cookie::get('xiyou_ck_id');
$cookieData = $this->getCache($cookieId);
if(!$cookieData){
$cookieData = array();
}
$videolist = isset($cookieData['history_list']) ? $cookieData['history_list'] :false;
if(!is_array($videolist)){
$videolist = array();
作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/2090/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!
最后编辑: jackxiang 编辑于2016-8-4 23:41
评论列表