看了Solar框架的几点感触

p(date). 2010-12-09

Solar框架,大家可能不是很熟悉,我也是看了这篇文章:The Future of Zend Framework is Solar后,眼前一亮,就花了点时间了解了一下。标题很吸引眼球,不过确实也把Solar的一些特性通过对比的方式很好地展现出来了,下面是我总结的几点。

全局构建方法

很多框架都没有做到这点,或者说没有意识到这点,要实例化一个类时,可以new,可以getInstance(),可以factory,参数的传递也没有统一的规范。Solar这点做得很好

<?php
class Solar_Example extends Solar_Base {
    // note that the config property is named for the class
    // with an underscore prefix.  this lets us collect the
    // parent config defaults as well.
    protected $_Solar_Example = array(
        'a' => null,
        'b' => null,
        'c' => null,
    );
}

$config = array(
    'a' => 'one',
    'b' => 'two',
    'c' => 'three'
);

$example = Solar::factory('Solar_Example', $config);

可以看到Solar在实例化一个类时是通过一个全局的factory方法来实现的,同时参数的传递也很讲究,只能传递一个config数组,当然这个config可以从配置中读取。

统一的config调用

Solar在运行时只调用一个config文件,这个跟yii有点类似,Kohana则是把配置文件打得很散。这样做的好处是可以避免在运行时多次读取配置文件,影响效率,而且配置文件一多的话也容易乱。

Solar的config配置里有几项是特殊项,如ini_set,registry_set。还有一个很重要的特性是将类名作为key,然后将该类的属性作为值,运行时这些值将自动覆盖类的默认值。

<?php
/**
 * ini_set values
 */
$config['Solar']['ini_set'] = array(
    'error_reporting'   => (E_ALL | E_STRICT),
    'display_errors'    => true,
    'html_errors'       => true,
    'session.save_path' => "$system/tmp/session/",
    'date.timezone'     => 'UTC',
);

/**
 * auto-register some default objects for common use. note that these are 
 * lazy-loaded and only get created when called for the first time.
 */
$config['Solar']['registry_set'] = array(
    'sql'              => 'Solar_Sql',
    'user'             => 'Solar_User',
    'model_catalog'    => 'Solar_Sql_Model_Catalog',
    'mail_transport'   => 'Solar_Mail_Transport',
    'controller_front' => 'Solar_Controller_Front',
);

/**
 * sql adapter to use
 */
$config['Solar_Sql'] = array(
    'adapter' => 'Solar_Sql_Adapter_Sqlite',
);

/**
 * front controller
 */
$config['Solar_Controller_Front'] = array(
    'classes' => array('Solar_App'),
    'disable'  => array('base'),
    'default' => 'hello',
    'routing' => array(),
);

有一点我觉得Solar做得不太好,就是把类的属性的默认值放在了类里,而不是配置文件里。将来如果因为某个属性导致系统出问题,调试起来将会很困难。

全局注册器(消灭单例)

通过全局注册器就可以在整个应用程序中共享内容,这个内容可以是字符串/数组/对象等等。

<?php
// script 1
$obj = Solar::factory('Solar_Example');
Solar_Registry::set('example', $obj);

// script 2
$obj = Solar_Registry::get('example');
?>

这个就是最简单的存取,也可以设置为lazy-loading,就是不直接存对象,而是类名和config,这样只有到真正需要时才实例化。

<?php
$config = array(...);
Solar_Registry::set('example', 'Solar_Example', $config);

通过这种方法,其实就实现了单例。现在很多框架都在大力打压单例,Zend Framework更是在2.0的roadmap里提出要尽可能地消灭单例,不在类的内部实现单例方法,因为这样的话子类就很难扩展,尤其是在单例里又加入了一些特有的逻辑。

异常机制

Solar的异常机制也有自己的特点,做得很细致。自带了16个常用异常,如DirNotFound/FileNotReadable等等,不过感觉这块有点过设计了。

<?php
$class = 'My_Example_Class'; //出错的类名
$code = 'ERR_SOMETHING_WRONG'; //Exception类
$text = 'Something is wrong.'; //出错信息
$info = array('foo' => 'bar'); //附加信息
$exception = Solar::exception($class, $code, $text, $info);
throw $exception;

依赖注入

所谓依赖注入(dependency injection),就是某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。

Solar一般是把要注入的类定义在config里,比如Auth类要用到Cache,但不确定具体使用时会用到哪个Cache,此时可以通过配置config文件来实现

<?php
$config['Solar_Auth_Adapter'] = array(
	'cache' => array(
		'adapter' => 'Solar_Cache_Adapter_Memcache',
	),
);

$config['Solar_Cache_Adapter_Memcache'] = array(
        'host' => 'localhost',
        'port' => 11211,
        'timeout' => 1,
);

这样Auth类就会使用Memcache作为缓存引擎。可以在类内部定义多个注入点,这样就可以使用不同的类来完成同样的目的,只要该类有特定的方法。Zend Framework在2.0的roadmap中也提到”All components MUST allow for dependency injection”。

依赖注入可以让模块之间更加松耦合,比如之前用的是XCache缓存殷勤,后来由于业务变动要使用Memcache引擎,这时只需在配置文件里修改一下缓存类型就行了,多省事啊。

我们学习各种设计模式,最终都是为了一个目的:应对变化。而依赖注入可以让我们更加从容地应对变化。

适配器

使用适配器是依赖注入的基础,适配器其实就是一个抽象基类,所有的子类都必须继承该抽象基类。这样才能保证在使用依赖注入时,一定存在某个特定方法。比如上面说到的Auth引用缓存问题,因为所有的缓存都是继承Solar_Cache_Adapter而来,所以一定会有save方法,Auth在调用缓存时就可以放心地save了。

Solar内部使用了大量的Adapter,目的就是为了方便依赖注入。可能系统提供的几个类都不适合具体应用,这时只要继承Adapter,自己写一个类,然后加到配置文件里就行了。

总结

Solar框架还是值得看一看的,即使不打算拿他来做应用。也可以关注一下Zend Framework 2.0,如果真的实现了roadmap里所说的种种,相信一定会给其他的php框架带来巨大的冲击。


--EOF--

若无特别说明,本站文章均为原创,转载请保留链接,谢谢