Featured Posts

纠结与释怀 这几天的纠结让我度日如年,伴随我的是焦虑和失眠。好久没有这样的感觉了。 我总是患得患失,这是一种心理问题,在做选择的时候,反复对比各方的优劣,放不下东,也舍不得西。 自己不知道怎么选择,然后反复问家人和朋友,即使问到答案,也不能让自己安心顺从。 幸运的是,在反复纠结之后,我逐渐想明白了一些道理。 选择,就要付出代价,必定有所得有所失,我应该勇敢承担起责任,坦然面对自己的选择带来的变化和影响。 纠结的时候,我感觉自己是个懦弱的人,害怕犯错,害怕不好的结果。 现在,我鼓起勇气,自己做出选择,不管对错,我接受,不后悔。 我要感谢猛哥,花费很多时间和精力,前后沟通,给我提供了非常难得的机会,在我最终没有选择这个机会的时候,仍然支持我的选择,并告诉我他这里的大门永远向我敞开。 此时,时间像突然停止了一样,飞快打字的手,也一下停住了,我反复看着这句话,感觉到眼眶周围热热的,滑滑的…… 我想我的勇气,多半来自于猛哥对我的关照。 我只求将来有机会能够报答猛哥的知遇之恩。 是时候为自己的选择努力工作了,大家一起加油!

Readmore

CentOS: cannot restore segment prot after reloc 最近在研究CentOS,用xampp装一套集成的LAMP环境,结果在启动Apache的时候报错: cannot restore segment prot after reloc: Permission denied 原因是 modules/mod_perl.so 不能加载。 查了一下可能是SELINUX的问题,有一个解决方法: 用 chcon...

Readmore

PHP 文件下载 IE 无法打开页面 IE 又有一个弱得不行的问题让我发现! 有个项目,要限制文件的下载权限,只有注册用户才可以下载,用户登录后,点击下载链接,弹出保存附件的提示。 我用...

Readmore

Subversion neon 诡异配置 一波三折 今天发现前几天装的 subversion 居然没法通过 http 协议访问版本库! Subversion 出现 svn: Unrecognized URL scheme for 'http://.....'  这样的错误提示。 检查 svn 客户端是否支持...

Readmore

  • Prev
  • Next

FCKeditor 2.6 安装 配置 问题 图片上传

Posted on : 09-04-2009 | By : leakon | In : Symfony, Web开发

0

今天给使用 Symfony 的项目装了 FCKeditor 编辑器,配了图片上传。

遇到一些问题,总结下。

安装时,为了简单,最好把解压的 fckeditor 文件夹放在 web/ 目录下。

在模板中加入编辑器很简单:

$webDir = sfConfig::get('sf_web_dir'); // DocumentRoot 的服务端路径
$editorInclude = $webDir . "fckeditor/fckeditor.php";
require_once($editorInclude);

$oFCKeditor = new FCKeditor('detail') ; // form 表单字段名称是 detail
$oFCKeditor->BasePath = '/fckeditor/' ; // 客户端访问编辑器资源文件的路径
$oFCKeditor->Width = '100%'; // 宽
$oFCKeditor->Height = '100%'; // 高
$oFCKeditor->Value = $articleItem->detail; // $articleItem 是 ORM 对象,直接引用 detail 字段
$oFCKeditor->Config = array(
        'AutoDetectLanguage' => false, // 关闭浏览器语言自动检测
        'DefaultLanguage' => 'zh-cn' // 设置简体中文
);
$oFCKeditor->Create() ;

设置完了,刷新页面,直接可用!很简单。

已经检查的问题,可以很好地处理 XSS 问题,在编辑器里填写 <script>alert(123)</script> 这样的字符串,都会转义成纯字符,不会被浏览器解析为 HTML 代码。这在编辑器和最终页面都可以正常显示,需要注意的是输出的时候都要加 htmlspecialchars 转义,这是必须养成的习惯!

图片上传的问题。

默认是关闭的,修改 fckeditor/editor/filemanager/connectors/php/config.php 为:

$Config['Enabled'] = true;

$Config['UserFilesPath'] = '/uploads/'; // 这是 Symfony 自动生成的目录。

现在可以上传了,不过会遇到 2 个问题。

问题 1:点击“发送到服务器上”按钮,浏览器 alert 一个错误,显示“Error creating folder "redirect:/index.php" (Can't create redirect: directory)”。参考了“在symfony中使用FCKeditor上传图片附件” 这篇文章,得到了很好的解决。是 .htaccess 造成的。最简单的办法是在 uploads 目录下再放置一个 .htaccess,关闭 rewrite 即可,内容如下:

Options +FollowSymLinks +ExecCGI
<IfModule mod_rewrite.c>
  RewriteEngine Off
</IfModule>

问题 2:上传文件后修改文件名。fckeditor 默认是保留原有文件名。英文名的文件还勉强能用,如果是中文文件名就麻烦了。最好是统一按照自己的命名规则修改文件名。这时编辑 fckeditor/editor/filemanager/connectors/php/io.php,修改一个函数:

function SanitizeFileName( $sNewFileName ) {
  $arr = explode('.', $sNewFileName);
  $ext = array_pop($arr);
  $filename = date('Ymd_His_') . rand(1000, 9999) . '.' . $ext;
  return $filename;

把原函数改名作为备份。这函数就是保留了扩展名,把主文件名按照日期编码。呃,为了更好地保存文件,其实还应该把扩展名统一大小写,我记得有一个 pathinfo 函数,返回值有一个 extension 字段就是扩展名。其实过程都差不多,您自选吧。

总的说来,fckeditor 给我的感觉是安装简单,功能强大,代码逻辑清晰,便于修改。

赞一个~~

另:推荐一个 Blog

4’s symfony blog

这兄弟很深入地了解 Symfony 框架,我已订阅了他的 RSS,每天学习中……

睡了,晚安……

What is Dependency Injection? 依赖注入?

Posted on : 27-03-2009 | By : leakon | In : Symfony

0

转自 Symfony-Project 的创始人:Fabien Potencier。

查了下中文,貌似应该翻译成 “依赖注入”?

This article is the first of a series on Dependency Injection in general and the implementation of a Dependency Injection Container in PHP.

Today, I won't talk about the container yet as I first want to introduce the concept of Dependency Injection with some concrete examples that will hopefully demonstrate the problems it tries to solve and the benefits it gives to the developer. If you already knows the concept of Dependency Injection, you can safely skip this article and instead wait for the next one.

Dependency Injection is probably one of the most dead simple design pattern I know. And odds are you have probably already used Dependency Injection. But it is also one of the most difficult one to explain well. I think it is partly due to the nonsense examples used in most introductions to Dependency Injection. I have tried to come up with examples that fits the PHP world better. As PHP is a language mainly used for web development, let's take a simple Web example.

To overcome the statelessness of the HTTP protocol, web applications need a way to store user information between web requests. This is of course quite simple to achieve by using a cookie, or even better, by using the built-in PHP session mechanism:

$_SESSION['language'] = 'fr';
 

The above code stores the user language in the language session variable. So, for all subsequent requests of the same user, the language will be available in the global $_SESSION array:

$user_language = $_SESSION['language'];
 

As Dependency Injection only makes sense in an Object-Oriented world, let's pretend we have a SessionStorage class that wraps the PHP session mechanism:

class SessionStorage
{
  function __construct($cookieName = 'PHP_SESS_ID')
  {
    session_name($cookieName);
    session_start();
  }
 
  function set($key, $value)
  {
    $_SESSION[$key] = $value;
  }
 
  function get($key)
  {
    return $_SESSION[$key];
  }
 
  // ...
}
 

... and a User class that provides a nice high-level interface:

class User
{
  protected $storage;
 
  function __construct()
  {
    $this->storage = new SessionStorage();
  }
 
  function setLanguage($language)
  {
    $this->storage->set('language', $language);
  }
 
  function getLanguage()
  {
    return $this->storage->get('language');
  }
 
  // ...
}
 

Those classes are simple enough and using the User class is also rather easy:

$user = new User();
$user->setLanguage('fr');
$user_language = $user->getLanguage();
 

All is good and well... until you want more flexibility. What if you want to change the session cookie name for instance? Here are some random possibilities:

  • Hardcode the name in the User class in the SessionStorage constructor:
    class User
    {
      function __construct()
      {
        $this->storage = new SessionStorage('SESSION_ID');
      }
     
      // ...
    }
     
  • Define a constant outside of the User class:
    class User
    {
      function __construct()
      {
        $this->storage = new SessionStorage(STORAGE_SESSION_NAME);
      }
     
      // ...
    }
     
    define('STORAGE_SESSION_NAME', 'SESSION_ID');
     
  • Add the session name as a User constructor argument:
    class User
    {
      function __construct($sessionName)
      {
        $this->storage = new SessionStorage($sessionName);
      }
     
      // ...
    }
     
    $user = new User('SESSION_ID');
     
  • Add an array of options for the storage class:
    class User
    {
      function __construct($storageOptions)
      {
        $this->storage       new SessionStorage($storageOptions['session_name']);
      }
     
      // ...
    }
     
    $user = new User(array('session_name' => 'SESSION_ID'));
     

All these alternatives are quite bad. Hardcoding the session name in the User class does not really solve the problem as you cannot easily change your mind later on without changing the User class again. Using a constant is also a bad idea as the User class now depends on a constant to be set. Passing the session name as an argument or as an array of options is probably the best solution, but it still smells bad. It clutters the User constructor arguments with things that are not relevant to the object itself.

But there is yet another problem that cannot be solved easily: How can I change the SessionStorage class? For instance, to replace it with a mock object to ease testing. Or perhaps because you want to store the sessions in a database table or in memory. That's impossible with the current implementation, except if you change the User class.

Enter Dependency Injection. Instead of creating the SessionStorage object inside the User class, let's inject the SessionStorage object in the User object by passing it as a constructor argument:

class User
{
  function __construct($storage)
  {
    $this->storage = $storage;
  }
 
  // ...
}
 

That's Dependency Injection. Nothing more! Using the User class is now a bit more involving as you first need to create the SessionStorage object:

$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
 

Now, configuring the session storage object is dead simple, and replacing the session storage class is also very easy. And everything is possible without changing the User class thanks to the better separation of concerns.

The Pico Container website describes Dependency Injection like this:

"Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields."

As any other design pattern, Dependency Injection also has some anti-patterns. The Pico Container website describes some of them.

Dependency Injection is not restricted to constructor injection:

  • Constructor Injection:
    class User
    {
      function __construct($storage)
      {
        $this->storage = $storage;
      }
     
      // ...
    }
     
  • Setter Injection:
    class User
    {
      function setSessionStorage($storage)
      {
        $this->storage = $storage;
      }
     
      // ...
    }
     
  • Property Injection:
    class User
    {
      public $sessionStorage;
    }
     
    $user->sessionStorage = $storage;
     

As a rule of thumb, constructor injection is best for required dependencies, like in our example, and setter injection is best for optional dependencies, like a cache object for instance.

Nowadays, most modern PHP frameworks heavily use Dependency Injection to provide a set of decoupled but cohesive components:

// symfony: A constructor injection example
$dispatcher = new sfEventDispatcher();
$storage = new sfMySQLSessionStorage(
         array(
            'database' => 'session',
            'db_table' => 'session'));
$user = new sfUser($dispatcher, $storage,
         array('default_culture' => 'en'));
 
// Zend Framework: A setter injection example
$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com',
 array(
  'auth'     => 'login',
  'username' => 'foo',
  'password' => 'bar',
  'ssl'      => 'ssl',
  'port'     => 465,
));
 
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);
 

If you are interested in learning more about Dependency Injection, I highly recommend you read the Martin Fowler introduction or the excellent Jeff More presentation. You can also have a look at a presentation I gave last year on Dependency Injection, where I talk in more detail on the example I have talked about in this article.

That's all for today. I hope you now have a better understanding of the Dependency Injection concept. In the next installment of this series, I will talk about Dependency Injection Containers.

Symfony Helper select_tag

Posted on : 12-04-2008 | By : leakon | In : Symfony

1

我们做数据库设计时,会用数字标记某条记录的状态。最简单的例子,一个 gender 字段,是 tinyint 类型,用 1 代表男性,2 代表女性。

在设计 html 页面编辑这条记录时,我们总要写好多 html 代码,很麻烦。

用 Symfony 的 select_tag 可以非常方便地帮我们完成这些复杂的工作。

下面介绍一个实例。

有一个项目,需要提交问题处理记录,表单有一项“优先级”,分为 6 种级别,希望可以用下拉列表进行选择。

select_tag.png

...

详情请见:

http://wiki.leakon.com/SymfonyHelper-select_tag

Symfony 文档 电子书 PHP Quebec 2007

Posted on : 21-03-2008 | By : leakon | In : Symfony

5

最近在看 Symfony 的源码,从中体会到很多这个框架背后实现的原理和过程。

其实,我们在讨论框架的时候,不是要把他直接拿来用,而是要研究他优秀的部分,吸取他的经验。

我真是不明白为什么很多搞开发的人那么讨厌框架,那么鄙视框架的性能。

也许,就像我的一个朋友说的那样,有些人在技术上比较保守,不思进取,当别人学新东西的时候,他总是极力反对,原因就是怕别人抢饭碗……

不说那么多了,今天在一个比较牛的下载站,下了很多 Symfony 的文档和一些相关参考资料。

其中有一个 pdf ,是 Symfnoy 创始人 Fabien Potencier 在 Quebec 2007 PHP 大会上演示的文档,制作相当精美,而且用图形整体描述了 Symfony  的主体框架,很赞……

Development-with-Symfony.ppt
doctrine-manual.pdf
symfony-api.chm
symfony-book.pdf
symfony-guide.chm
symfony-PHPQuebec-2007-en.pdf
SymfonyBook.zip

给大家一个压缩包下载,里面包括了上述的几个文件。

希望大家多交流。

http://wiki.leakon.com/ 是我最近刚建立的 Wiki,我会把平时积累的一些经验,尤其是代码部分放在上面。

接触 Symfony 也有将近一年的时间了,我也会把自己总结的 Symfony 的一些开发经验,在 Wiki 上与大家分享。

好啦,现在给大家压缩包的地址:

http://down.leakon.com/SymfonyBook/SymfonyBook.zip

宣传一下 Symfony 的官方网站地址:

http://www.symfony-project.org/

Symfony 温故而知新

Posted on : 03-01-2008 | By : leakon | In : Symfony, 原创, 默认分类

5

使用Symfony的同学,连接数据库时,如果想指定编码,需要在得到数据库Connection的时候发送一条SET NAMES UTF-8语句,以保证统一。

针对这个问题,我以前写过2篇Blog,地址:

里面是通过修改Creole的源码,symfony/vendor/creole/Creole.php,加入一个setUTF8方法。

这个方式管用,但不是最好。

这两天看了Symfony的 Code snippets ,发现很多Symfony的运行机制,看到一个FilterChains,可以由用户添加一个Filter到调用链中,可以在系统初始化时自动运行。

利用这个特点,把设置连接编码的语句,添加到一个filter中,每次自动运行:

// 首次执行时,设置数据库连接编码为 GBK
class FilterGBK extends sfFilter {
public function execute($filterChain) {
// execute this filter only once
// 这里确保了这几行代码只执行一次
if ($this->isFirstCall()) {
$connection = Propel::getConnection();
$query = 'SET NAMES gbk';
$statement = $connection->prepareStatement($query);
$statement->executeQuery();
}
// execute next filter
$filterChain->execute();
}
}

再编辑 apps/front/config/filters.yml 文件,在开头加入:


FilterGBK:
class: FilterGBK

这样就可以达到目的,而以前的实现方式需要修改插件源码,无法在一个系统中运行多个Symfony项目。

随着逐渐深入Symfony,并反复查看文档和代码,可以学到很多Web开发的经验和技巧。

这回,深刻体会到了“温故而知新”的内涵。

Symfony Apache Url Rewrite

Posted on : 03-01-2008 | By : leakon | In : Symfony, 原创

0

今天在服务器上新装了apache,把用Symfony开发的后台程序部署到服务器上,发现生产版本的环境无法使用,居然报404 Not Found。

刚开始没想到是什么原因,反复翻文档,检查虚拟主机的配置,和目录权限,以及.htaccess文件,都正确无误。

在自己的Windows环境是好好的,但在服务器上却不行。

后来就拿.htaccess开刀,因为受到一点Apache手册的启发,就是在.htaccess中添加一些错误代码,以检查.htaccess是否正确运行。

其实本来就应该能想到这样做的,只是觉得问题可能出现在其他地方。

果不其然,就是.htaccess中的<IfModule mod_rewrite.c>,在这个分支里面随便写任何东西,都不会起作用。

看来Apache没有正确安装mod_rewrite.so。

一时也不知道如何验证,后来在网上查了半天才查到。

先列一串命令吧:

  • apache/bin/httpd -l
    列出当前环境中安装了哪些module
  • apache2/bin/apxs -c mod_rewrite.c -lgdbm
    在Apache源码路径的modules/mappers/中,可以找到mod_rewrite.c等所有模块的源码,在这个路径执行apache2/bin/apxs(注意,这里的apxs是apache安装的绝对路径),就会自动编译这个模块,生成.so文件到modules/mappers/.libs/,刚才那个mod_rewrite.so就生成在这里
  • apache2/bin/apxs -i -A -n rewrite mod_rewrite.so
    在modules/mappers/.libs/这个目录下执行上面的命令,会在apache2/conf/httpd.conf中加入一行
    #LoadModule rewrite_module     modules/mod_rewrite.so
    注意前面是带#的
  • apache2/bin/apachectl configtest
    检查当前的配置文件语法是否正确,不用等restart不成功再去检查错误了

通过这样几步,检查没问题后,重启apache,mod_rewrite模块就安装完成了。

再打开Symfony的后台,所有页面都可以正确打开。

我通常都在自己的笔记本上开发程序,用xampp套件,很多常用模块都是默认安装好的,使用之前只要把httpd.conf文件中相应模块前面的#去掉就可以了,一下子把程序部署到服务器,很多模块都没装,还真是不适应。

Symfony 限制

Posted on : 23-12-2007 | By : leakon | In : Symfony

0

发现Symfony的2个限制,其实这个是Propel的限制,Propel是PHP的一个ORM的数据库代码生成器,通过XML或YML文件配置,自动创建针对每个数据库和表的CRUD模型,并有良好的继承关系,真正地以面向对象的方式操纵数据库的每一行每一列。

目前发现2点限制:

  1. 不能适应分表(动态选择表名)
  2. 整型字段不能设置unsigned类型

在设计数据库的时候,采用分表存储,是一个优化性能的好方法, 比如,保存用户的文章,按用户ID,散列到8个表上。这8个表的结构都完全一样,只是表名不一样:tbl_post_0, tbl_post_1, ..., tbl_post7。

当前版本的Propel,生成的代码,是和表名紧紧耦合在一起的,也就是说这8个表,会生成8套几乎一摸一样的代码,同样需要写8份schema.xml。

这样很不合理。

应该再抽象出一个机制,考虑结构相同表的统一处理。

Propel的另一个问题就是整数字段不能设置unsigned类型,查了官方的说明,说是PHP的整数类型就是-2的31次方到2的31次方减一。我还没来得及证实,不过这也不是很大的问题。因为,Symfony就是为了从零开始快速开发。一个新项目,数据量从零增加到20多亿条记录,还是需要一些时间的,应该避免项目初期的过度设计。

等你的数据规模达到10亿的时候,考虑找风投给你1000万美元,招人帮你重构吧。

Symfony 如何在项目中使用多个数据库

Posted on : 01-12-2007 | By : leakon | In : Symfony

0

all:
database1:
class: sfPropelDatabase
param:
dsn: pgsql://foo:bar@hostname/database1

database2:
class: sfPropelDatabase
param:
dsn: mysql://foo:bar@hostname/database2

先建立一个
PROJECT_DIR/config/database1.schema.xml
再建立一个
PROJECT_DIR/config/database2.schema.xml

最后build一下:

$ symfony propel-build-model

$database1_connection_handler = Propel::getConnection(FooPeer::DATABASE_NAME);
$database2_connection_handler = Propel::getConnection(BarPeer::DATABASE_NAME);