东东东 陈煜东的博客

分类存档: PHP

laravel session的使用,填一下坑

用Laravel开发应用,把原有的代码copy过来,以前的代码session使用了$_SESSION,本以为移植过来可以很好的运行的,因为没有依赖其他的组件,结果出现了这个

Undefined variable: _SESSION

Laravel的session的配置文件配置在 app/config/session.php 中,使用时可以看看 session 配置文件中可用的选项设定及注释。

Laravel 默认使用 file 的方式来实现 session的。她并不用php原生的$_SESSION(php原生的session要看php.ini的位置),所以忽略php相关的session函数,例如session_start(), $_SESSION。Laravel在运行过程中会在app/storage/session/目录写入session的信息,所以这个目录需要有写权限,否者session就无法写入成功。

Laravel除了使用默认的file作为session的实现,还支持cookie, Memcached, Redis数据库的后端驱动作为session的实现。必要的时候还需要自己实现一个session的实现方式,比如在微信公众账号和用户的交互,这中session就无法直接使用,因为每次都是微信服务器来请求,无法通过请求的来源来辨别用户。

laravel的session简要API

Session的API还是比较简单的,大家看看中文文档也大概知道是怎么个意思。但是有那么几个还不太好理解。

//session的永久保存(在不过期范围内)
Session::put('key', 'value');

//等同于PHP的原生session
$_SESSION['key'] = 'value';

//get操作
$value = Session::get('key', 'default');

//去除操作并删除,类似pop概念
$value = Session::pull('key', 'default');

//检测是否存在key
Session::has('users');

//删除key
Session::forget('key');

这个对应只要session不过期,基本上是永久保存,下次http请求也是存在的。不同于下面的flash概念。

laravel的session中flash概念

但是Laravel出了个快闪flash的概念,把我一下子给搞混了。这个flash两次请求有效(本次和下次请求有效),与本次请求取操作多少次无关。

//保存key,value
Session::flash('key', 'value');

//取值方法还是一样的
Session::get('key');

//刷新快闪数据时间,保持到下次请求
Session::keep(array('username', 'email'));

这个flash的概念和上面的put的概念不太一样。

  • put :这个对应只要session不过期,基本上是永久保存,下次请求也是存在的。
  • flash :保存的值,本次请求可以使用,下次http请求可以使用,再下一次就不存在了。

也就是说下一次的请求用完就被销毁了,不会让session的值变的越来越大,可以保存一些临时的数据。

这中情况的使用场景比如有:

  • 用户请求了页面,出现错误信息,重定向到一个新的页面,需要展示之前的数据。(虽然可以通过url参数来传递,处理不好可能会有xss漏洞)。
  • 用户访问了一个页面,过滤器发现没权限,保存当前页面url,重定向到登录页面,登录成功,取出值,重定向到原先的页面。(这里可能需要刷新保存的快闪数据)

session落地的时间

我天真的以为使用了Session::put函数就能保存这个变量了。于是我的代码这样写:

class LoginController {

    public function login(){
        Session::put('key','value'); 
        print_r( Session::all() ); //取出来看看是否put成功
        exit;   //习惯性的调试都exit,不执行后续代码
        //return Redirect::to(/); 框架在return后还会有后续的代码执行的
    }
}

结果下次请求就是找不到本次的Session,而且看app/storage/session目录就是没有文件生成。总感觉不对劲啊。

后来看到网络上有个方法Session::save(),于是我也用了下,居然发现成功的生成了session的文件。于是我感觉到,Laravel不用php原生的session,那么在controller之后应该做了一些事情,将session写入到文件中,而不是每次put操作都写操作,这样会IO操作太频繁的,影响性能的。

查看调用相关的代码。laravel编译后,在bootstrap/compiled.php

class Middleware implements HttpKernelInterface
{
    ...
    public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
    {
        $this->checkRequestForArraySessions($request);
        if ($this->sessionConfigured()) {
            $session = $this->startSession($request); // 启动session
            $request->setSession($session);
        }
        $response = $this->app->handle($request, $type, $catch); // 调用controller的method
        if ($this->sessionConfigured()) {
            $this->closeSession($session);         //关闭session
            $this->addCookieToResponse($response, $session);
        }
        return $response;
    }
    ...

    protected function closeSession(SessionInterface $session)
    {
        $session->save();    // 保存session
        $this->collectGarbage($session);
    }
}

小提示:如果不知道函数调用情况,可以在controller中throw new Exception();,然后在/config/app.php的debug更改为debug=>true。可以看到函数的调用关系。

可以看见,在调用完controller之后,调用了session->save()的方法,来主动的保存session。这样session才能落地保存起来,如果在controller或者view里面写了exit;,那么session是不会被保存的,除非主动的写Session::save()才能手工的保存起来。因此在debug调试的时候千万要注意啊。

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: laravel session的使用,填一下坑 – https://www.chenyudong.com/archives/laravel-session-use.html

分类: PHP

[PHP]CodeIgniter自动检测https出现问题

先说下我的环境吧,我是nginx的服务器,并不是apache的。可能此文章导致的这个问题和nginx有关。

在用codeigniter编写项目的时候,使用redirect()函数发现出来的居然是https的URL。这个就令我纳闷了,为什么,我明明没有https连接,为什么老给我解析成https呢。

我看了下redirect()的代码,里面调用到了site_url()。发现这个是系统类库的一个文件,在构造器的地方初始化了这个https的问题。我们来看看这个相关的代码。

if (isset($_SERVER['HTTP_HOST']))
{
    $base_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http';
    $base_url .= '://'. $_SERVER['HTTP_HOST'];
    $base_url .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
}
else
{
    $base_url = 'http://localhost/';
}

我们看第三行,对$_SERVER['HTTPS']里面的相关值进行的。因此我们必须得打印出来看看系统的值是什么。

Array(
    [HTTPS] =>  string(0) ""
    [HTTP_HOST] => string(10)  "host.local"
)

可以看到我的Nginx在$_SERVER['HTTPS']居然为空字符串。因此codeigniter的这个判断是否是https是有问题的。应该修改修改。

应该修改成

isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http';

关于$_SERVER['HTTPS']这个定义,我们是可以在Nginx的站点配置进行更改。因此我们也不好说会是个什么情况。

另外在/system/helpers/url_helper.php中的redirect()函数我觉得不是太合理。官方在拼接URL的时候使用了site_url('/news/local/123')这个函数拼接出来的结果是http://host.local/index.php/news/local/123。个人感觉不太合适,因为我们自己做了URL的重写,那么就不应该出现/index.php/的字眼。因此这里我改用了base_url()函数。

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: [PHP]CodeIgniter自动检测https出现问题 – https://www.chenyudong.com/archives/fix-codeigniter-detect-https-url.html

分类: PHP

PHP的MVC框架模式是如何实现的

PHP的mvc框架很多,像YiiCodeIgniterThinkPHP等现在流行的框架,利用MVC模式进行web页面的开发,我们可以非常方便的编写web程序。他们的工作原理大家应该也比较感兴趣,下面我说说一个mvc框架长什么样。

路由机制

在互联网我们都是通过url提供服务,因此不同的url有不同的服务。用户访问不同的页面也就获得了不同的服务。那么我们的服务是如何通过url来区分不同的服务呢。

我们的web程序就要通过url寻找到不同的文件,进行不同的业务逻辑处理。我们的路由机制就是根据url,寻找到对应的controller,和action,然后由action进行具体的业务逻辑处理。

//定义一个controller
class UserControler extends Controller{
     //定义一个action方法,注意一定是public的
     public function index(){
          // do business code
     }
}

具体的对应规则不同的框架映射不同。以下是CodeIgniter框架的URL路由,它会尽力的尝试各种的可能,来分析URL的情况。

// 看看是否是从命令行运行的
if (php_sapi_name() == 'cli' or defined('STDIN')){
    $this->_set_uri_string($this->_parse_cli_args());
    return;
}

// 首先尝试 REQUEST_URI 这个适应大部分的情况
if ($uri = $this->_detect_uri()){
    $this->_set_uri_string($uri);
    return;
}

// 看看PATH_INFO变量是否存在?nginx需要配置
// Note: some servers seem to have trouble with getenv() so we'll test it two ways
$path = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
if (trim($path, '/') != '' && $path != "/".SELF){
    $this->_set_uri_string($path);
    return;
}

// 没有PATH_INFO,看看 QUERY_STRING?
$path =  (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
if (trim($path, '/') != ''){
    $this->_set_uri_string($path);
    return;
}

//尝试去从 $_GET 获取信息
if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != ''){
    $this->_set_uri_string(key($_GET));
    return;
}

// 尽力了,放弃了路由
$this->uri_string = '';
return;

通过上面的尝试,接下来就是如何利用路由机制加载正确的controller了。

Controller加载机制

我们来看看Codeigniter框架是如何加载到controller并且调用action的。

/system/core/Codeigniter.php中有如下的代码。Codeigniter在这之前会根据$_SERVER['PATH_INFO]里面的值来进行赋值(这个都是靠自己的设定的,默认的话CI他会有许多的if分支进行判断)。

//大约在250行
include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php');

$class  = $RTR->fetch_class();
$method = $RTR->fetch_method();

//大约在308行
$CI = new $class();

//大约在359行
call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2));

就这样,通过这个就调用到了我们的controller及其方法了,接下来就是编写自己的业务逻辑代码了。

视图view的显示

当我们的业务逻辑代码写完后,就需要页面的展示了。很多常见的MVC框架在页面的调用是这么写的。

//controller中action的方法
public function index(){
    // ... 许多的业务逻辑代码
    $data = array('name'=>'abc', 'age'=>12, .... );
    return $this->render('view/path/file.html',$data);
}

接着在视图文件view/path/file.html里写上一下代码。

<div>
姓名 : <?=$name ?>
年龄 : <?php echo $age; ?> 
</div>

这段如何将数据渲染到视图中,这段代码以前我一直很好奇,现在我明白了,我们来看看是如何实现的。

protected function render($template, array $var = array() ) 
{
    extract($var);   // 抽取数组中的变量
    ob_end_clean (); //关闭顶层的输出缓冲区内容
    ob_start ();     // 开始一个新的缓冲区
    require TEMPLATE_ROOT . $template . '.html';  //加载视图view
    $content = ob_get_contents ();             // 获得缓冲区的内容
    ob_end_clean ();           // 关闭缓冲区

    //ob_end_flush();      // 这个是直接输出缓冲区的内容了,不用再次缓存起来。
    ob_start();            //开始新的缓冲区,给后面的程序用
    return $content;       // 返回文本,此处也可以字节echo出来,并结束代码。
}

在这短短的几行代码中,全都是精华,就是这些非常重要的,全是php的内置函数,接下来我们来具体分析分析。

看看第一个extract($var)。这个函数从数组中将变量导入到当前的符号表。刚刚就将$data数组里面的name、age抽取出来,这样就可以在视图view中使用$name $age。更详细的请参考http://www.php.net/manual/zh/function.extract.php

第二个ob_end_clean()的作用是关闭顶层的缓冲区,为了是之前的程序不小心echo出的一些文字给清楚了,为了下一行的重新开辟一块缓冲区。

第三个ob_start()是开启一块新的缓冲区,为了是将视图的内容放到缓冲区。当然了,缓冲区有一定的大小,如果内容超出了缓冲区的设定值,那么会自动的发送给server。

第四个require file,这个就是第一个参数,根据自己的规则去加载视图的文件。其中文件里可以夹杂php、html的代码。你在这个render()函数声明的任何局部变量或者这里能访问到的任何全局变量,都可以在require的file文件中访问到。

第五个$content = ob_get_contents ()很重要,是为了将缓冲区的内容取出来,但不清除它。

第七个ob_start()是重新开启一个缓冲区,为了是下面的程序需要使用缓冲区。有写框架可能不用对$content的内容进行操作了,那么直接ob_end_flush()将缓冲区的内容输出出来就行了。

这个是一个很简单的展示视图的过程。如果直接使用这个不方便对视图view进行模块化,因此一些框架都不会这么直接用的。

我们从这个函数也可以看到程序有点类似程序中断保护现场的感觉。只不过中断保护现场会先保存数据,然后在返回的时候恢复回来。这里只有关闭上一个缓冲区,开启一个新的缓冲区,关闭这个缓冲哦过去,开启另外一个缓冲区。

至此,我们看到一个简单的PHP的MVC框架。如果你有兴趣可以自己开发一个MVC框架,或者更深入点的HMVC。

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: PHP的MVC框架模式是如何实现的 – https://www.chenyudong.com/archives/how-to-php-mvc-framework.html

分类: PHP, Web 开发

php声明(静态)字符串成员变量要注意的地方

PHP在声明一个成员变量时,你可能会遇到以下问题。

这是一个从Java迁移过来的程序员犯的错误。很悲剧,花了许多时间来解决。

遇到情况

我们先来看看,正确的定义一个成员变量。如果学过其他的语言,我们很快就会发现,这样的结构我们是很熟悉的。

class Test{
    public $a = "ab";
}

接下来,我们定义的字符串比较长,需要采用字符串的拼接来进行。如果改成这样的命名,将abcd拼接在一起,这样就报错了,说语法错误。

<?php

class Test{
    public $a = "ab"."cd";
}

$t = new Test();
echo $t->a;
?>

会出现错误

Parse error: syntax error, unexpected '.', expecting ',' or ';' in ..../test.php on line 4

也许,你曾今见过这样的拼接字符串,而且能够正确运行。

$str = "ab"."cd";
echo $str;

而且如果你知道Java语言声明成员变量,那么知道一下定义没问题。

class Test{
    String str="ab"+"cd";
}

从Java的风格到PHP的风格,于是就杯具了。完全不对。看着php中的定义一个字符串没问题,但是在成员变量中就报错误。

原因 – 解释

后来在http://stackoverflow.com/questions/4976717/static-function-variables-and-concatenation-in-php上发现了这个问题的解决。

说了static的静态变量,是不运行进行赋值表达式的,只能使用 literal和const。

同样的,属性中的变量可以初始化,但是初始化的值必须是常数,这里的常数是指 PHP 脚本在编译阶段时就可以得到其值,而不依赖于运行时的信息才能求值。即成员初始化只能使用简单类型(字符串,整形,数组等),不能有函数和运算符。详情请见:http://php.net/manual/zh/language.oop5.properties.php

另外,使用var声明一个成员变量新版本已经废弃了。如果看代码看到这样的,千万不要这样学习。

解决

通过define方法解决。

define('str' ,"ab"."cd");

class Test{
    public $a = str;
}


$t = new Test();
echo $t->a;

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: php声明(静态)字符串成员变量要注意的地方 – https://www.chenyudong.com/archives/php-declare-static-member-variable-with-string.html

分类: PHP

Copyright © 2017 东东东 陈煜东的博客 粤ICP备13059639号-1

SITEMAP回到顶部 ↑