OSDN Git Service

84347a290d4b3a8349274f40cdf11d286dce5b12
[ethna/ethna.git] / class / ClassFactory.php
1 <?php
2 // vim: foldmethod=marker
3 /**
4  *  ClassFactory.php
5  *
6  *  @author     Masaki Fujimoto <fujimoto@php.net>
7  *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
8  *  @package    Ethna
9  *  @version    $Id$
10  */
11
12 // {{{ Ethna_ClassFactory
13 /**
14  *  Ethnaフレームワークのオブジェクト生成ゲートウェイ
15  *
16  *  DIコンテナか、ということも考えましたがEthnaではこの程度の単純なものに
17  *  留めておきます。アプリケーションレベルDIしたい場合はフィルタチェインを
18  *  使って実現することも出来ます。
19  *
20  *  @author     Masaki Fujimoto <fujimoto@php.net>
21  *  @access     public
22  *  @package    Ethna
23  */
24 class Ethna_ClassFactory
25 {
26     /**#@+
27      *  @access private
28      */
29
30     /** @var    object  Ethna_Controller    controllerオブジェクト */
31     var $controller;
32
33     /** @var    object  Ethna_Controller    controllerオブジェクト(省略形) */
34     var $ctl;
35     
36     /** @var    array   クラス定義 */
37     var $class = array();
38
39     /** @var    array   生成済みオブジェクトキャッシュ */
40     var $object = array();
41
42     /** @var    array   生成済みアプリケーションマネージャオブジェクトキャッシュ */
43     var $manager = array();
44
45     /** @var    array   メソッド一覧キャッシュ */
46     var $method_list = array();
47
48     /**#@-*/
49
50
51     /**
52      *  Ethna_ClassFactoryクラスのコンストラクタ
53      *
54      *  @access public
55      *  @param  object  Ethna_Controller    &$controller    controllerオブジェクト
56      *  @param  array                       $class          クラス定義
57      */
58     public function __construct(&$controller, $class)
59     {
60         $this->controller = $controller;
61         $this->ctl = $controller;
62         $this->class = $class;
63     }
64
65     /**
66      *  typeに対応するアプリケーションマネージャオブジェクトを返す
67      *  注意: typeは大文字小文字を区別しない
68      *         (PHP自体が、クラス名の大文字小文字を区別しないため)
69      *
70      *  @access public
71      *  @param  string  $type   アプリケーションマネージャー名
72      *  @param  bool    $weak   オブジェクトが未生成の場合の強制生成フラグ(default: false)
73      *  @return object  Ethna_AppManager    マネージャオブジェクト
74      */
75     function &getManager($type, $weak = false)
76     {
77         $obj = null;
78
79         //  すでにincludeされていなければ、includeを試みる
80         //  ここで返されるクラス名は、AppObjectの命名規約によるもの
81         //
82         //  これは、AppObject のファイル中にAppManagerが含まれる場合が
83         //  あるため必要なルーチンである
84         $obj_class_name = $this->controller->getObjectClassName($type);
85         if (class_exists($obj_class_name) === false) {
86             $this->_include($obj_class_name);
87         }
88
89         //  すでにincludeされていなければ、includeを試みる
90         //  ここで返されるクラス名は、AppManagerの命名規約によるもの
91         $class_name = $this->controller->getManagerClassName($type);
92         if (class_exists($class_name) === false
93             && $this->_include($class_name) === false) {
94             return $obj;  //  include 失敗。戻り値はNULL。
95         }
96
97         //  メソッド情報を集める 
98         if (isset($this->method_list[$class_name]) == false) {
99             $this->method_list[$class_name] = get_class_methods($class_name);
100             for ($i = 0; $i < count($this->method_list[$class_name]); $i++) {
101                 $this->method_list[$class_name][$i] = strtolower($this->method_list[$class_name][$i]);
102             }
103         }
104
105         //  PHPのクラス名は大文字小文字を区別しないので、
106         //  同じクラス名と見做されるものを指定した場合には
107         //  同じインスタンスが返るようにする
108         $type = strtolower($type);
109
110         //  以下のルールに従って、キャッシュが利用可能かを判定する
111         //  利用可能と判断した場合、キャッシュされていればそれを返す
112         //
113         //  1. メソッドに getInstance があればキャッシュを利用可能と判断する
114         //     この場合、シングルトンかどうかは getInstance 次第
115         //  2. weak が true であれば、キャッシュは利用不能と判断してオブジェクトを再生成
116         //  3. weak が false であれば、キャッシュは利用可能と判断する(デフォルト) 
117         if ($this->_isCacheAvailable($class_name, $this->method_list[$class_name], $weak)) {
118             if (isset($this->manager[$type]) && is_object($this->manager[$type])) {
119                 return $this->manager[$type];
120             }
121         }
122
123         //  インスタンス化のヘルパ(getInstance)があればそれを使う
124         if (in_array("getinstance", $this->method_list[$class_name])) {
125             $obj = call_user_func(array($class_name, 'getInstance'));
126         } else {
127             $backend = $this->controller->getBackend();
128             $obj = new $class_name($backend);
129         }
130
131         //  生成したオブジェクトはとりあえずキャッシュする
132         if (isset($this->manager[$type]) == false || is_object($this->manager[$type]) == false) {
133             $this->manager[$type] = $obj;
134         }
135
136         return $obj;
137     }
138
139     /**
140      *  クラスキーに対応するオブジェクトを返す/クラスキーが未定義の場合はAppObjectを探す
141      *  クラスキーとは、[Appid]_Controller#class に定められたもの。
142      *
143      *  @access public
144      *  @param  string  $key    [Appid]_Controller#class に定められたクラスキー
145      *                          このキーは大文字小文字を区別する 
146      *                          (配列のキーとして使われているため)
147      *  @param  bool    $ext    オブジェクトが未生成の場合の強制生成フラグ(default: false)
148      *  @return object  生成されたオブジェクト(エラーならnull)
149      */
150     function &getObject($key, $ext = false)
151     {
152         $object = null;
153
154         $ext = to_array($ext);
155         if (isset($this->class[$key]) == false) {
156             // app object
157             $class_name = $this->controller->getObjectClassName($key);
158             $ext = array_pad($ext, 3, null);
159             list($key_type, $key_value, $prop) = $ext;
160         } else {
161             // ethna classes
162             $class_name = $this->class[$key];
163             $ext = array_pad($ext, 1, null);
164             list($weak) = $ext;
165         }
166
167         //  すでにincludeされていなければ、includeを試みる
168         if (class_exists($class_name) == false) {
169             if ($this->_include($class_name) == false) {
170                 return $object;  //  include 失敗。返り値はnull
171             }
172         }
173
174         //  AppObject をはじめに扱う 
175         //  AppObject はキャッシュされないことに注意
176         if (isset($this->class[$key]) == false) {
177             $backend = $this->controller->getBackend();
178             $object = new $class_name($backend, $key_type, $key_value, $prop);
179             return $object;
180         }
181
182         //  Ethna_Controllerで定義されたクラスキーの場合
183         //  はメソッド情報を集める 
184         if (isset($this->method_list[$class_name]) == false) {
185             $this->method_list[$class_name] = get_class_methods($class_name);
186             for ($i = 0; $i < count($this->method_list[$class_name]); $i++) {
187                 $this->method_list[$class_name][$i] = strtolower($this->method_list[$class_name][$i]);
188             }
189         }
190
191         //  以下のルールに従って、キャッシュが利用可能かを判定する
192         //  利用可能と判断した場合、キャッシュされていればそれを返す
193         //
194         //  1. メソッドに getInstance があればキャッシュを利用可能と判断する
195         //     この場合、シングルトンかどうかは getInstance 次第
196         //  2. weak が true であれば、キャッシュは利用不能と判断してオブジェクトを再生成
197         //  3. weak が false であれば、キャッシュは利用可能と判断する(デフォルト) 
198         if ($this->_isCacheAvailable($class_name, $this->method_list[$class_name], $weak)) {
199             if (isset($this->object[$key]) && is_object($this->object[$key])) {
200                 return $this->object[$key];
201             }
202         }
203
204         //  インスタンス化のヘルパがあればそれを使う
205         $method = sprintf('_getObject_%s', ucfirst($key));
206         if (method_exists($this, $method)) {
207             $object = $this->$method($class_name);
208         } else if (in_array("getinstance", $this->method_list[$class_name])) {
209             $object = call_user_func(array($class_name, 'getInstance'));
210         } else {
211             $object = new $class_name();
212         }
213
214         //  クラスキーに定められたクラスのインスタンスは
215         //  とりあえずキャッシュする
216         if (isset($this->object[$key]) == false || is_object($this->object[$key]) == false) {
217             $this->object[$key] = $object;
218         }
219
220         return $object;
221     }
222
223     /**
224      *  クラスキーに対応するクラス名を返す
225      *
226      *  @access public
227      *  @param  string  $key    クラスキー
228      *  @return string  クラス名
229      */
230     function getObjectName($key)
231     {
232         if (isset($this->class[$key]) == false) {
233             return null;
234         }
235
236         return $this->class[$key];
237     }
238
239     /**
240      *  オブジェクト生成メソッド(backend)
241      *
242      *  @access protected
243      *  @param  string  $class_name     クラス名
244      *  @return object  生成されたオブジェクト(エラーならnull)
245      */
246     function &_getObject_Backend($class_name)
247     {
248         $_ret_object = new $class_name($this->ctl);
249         return $_ret_object;
250     }
251
252     /**
253      *  オブジェクト生成メソッド(config)
254      *
255      *  @access protected
256      *  @param  string  $class_name     クラス名
257      *  @return object  生成されたオブジェクト(エラーならnull)
258      */
259     function &_getObject_Config($class_name)
260     {
261         $_ret_object = new $class_name($this->ctl);
262         return $_ret_object;
263     }
264
265     /**
266      *  オブジェクト生成メソッド(i18n)
267      *
268      *  @access protected
269      *  @param  string  $class_name     クラス名
270      *  @return object  生成されたオブジェクト(エラーならnull)
271      */
272     function &_getObject_I18n($class_name)
273     {
274         $_ret_object = new $class_name($this->ctl->getDirectory('locale'), $this->ctl->getAppId());
275         return $_ret_object;
276     }
277
278     /**
279      *  オブジェクト生成メソッド(logger)
280      *
281      *  @access protected
282      *  @param  string  $class_name     クラス名
283      *  @return object  生成されたオブジェクト(エラーならnull)
284      */
285     function &_getObject_Logger($class_name)
286     {
287         $_ret_object = new $class_name($this->ctl);
288         return $_ret_object;
289     }
290
291     /**
292      *  オブジェクト生成メソッド(plugin)
293      *
294      *  @access protected
295      *  @param  string  $class_name     クラス名
296      *  @return object  生成されたオブジェクト(エラーならnull)
297      */
298     function &_getObject_Plugin($class_name)
299     {
300         $_ret_object = new $class_name($this->ctl);
301         return $_ret_object;
302     }
303
304     /**
305      *  オブジェクト生成メソッド(renderer)
306      *
307      *  @access protected
308      *  @param  string  $class_name     クラス名
309      *  @return object  生成されたオブジェクト(エラーならnull)
310      */
311     function &_getObject_Renderer($class_name)
312     {
313         $_ret_object = new $class_name($this->ctl);
314         return $_ret_object;
315     }
316
317     /**
318      *  オブジェクト生成メソッド(session)
319      *
320      *  @access protected
321      *  @param  string  $class_name     クラス名
322      *  @return object  生成されたオブジェクト(エラーならnull)
323      */
324     function &_getObject_Session($class_name)
325     {
326         $_ret_object = new $class_name($this->ctl, $this->ctl->getAppId());
327         return $_ret_object;
328     }
329
330     /**
331      *  オブジェクト生成メソッド(sql)
332      *
333      *  @access protected
334      *  @param  string  $class_name     クラス名
335      *  @return object  生成されたオブジェクト(エラーならnull)
336      */
337     function &_getObject_Sql($class_name)
338     {
339         $_ret_object = new $class_name($this->ctl);
340         return $_ret_object;
341     }
342
343     /**
344      *  指定されたクラスから想定されるファイルをincludeする
345      *
346      *  @access protected
347      */
348     function _include($class_name)
349     {
350         $file = sprintf("%s.%s", $class_name, $this->controller->getExt('php'));
351         if (file_exists_ex($file)) {
352             include_once $file;
353             return true;
354         }
355
356         if (preg_match('/^(\w+?)_(.*)/', $class_name, $match)) {
357             // try ethna app style
358             // App_Foo_Bar_Baz -> Foo/Bar/App_Foo_Bar_Baz.php
359             $tmp = explode("_", $match[2]);
360             $tmp[count($tmp)-1] = $class_name;
361             $file = sprintf('%s.%s',
362                             implode(DIRECTORY_SEPARATOR, $tmp),
363                             $this->controller->getExt('php'));
364             if (file_exists_ex($file)) {
365                 include_once $file;
366                 return true;
367             }
368
369             // try ethna app & pear mixed style
370             // App_Foo_Bar_Baz -> Foo/Bar/Baz.php
371             $file = sprintf('%s.%s',
372                             str_replace('_', DIRECTORY_SEPARATOR, $match[2]),
373                             $this->controller->getExt('php'));
374             if (file_exists_ex($file)) {
375                 include_once $file;
376                 return true;
377             }
378
379             // try ethna master style
380             // Ethna_Foo_Bar -> class/Ethna/Foo/Bar.php
381             $tmp = explode('_', $match[2]);
382             array_unshift($tmp, 'Ethna', 'class');
383             $file = sprintf('%s.%s',
384                             implode(DIRECTORY_SEPARATOR, $tmp),
385                             $this->controller->getExt('php'));
386             if (file_exists_ex($file)) {
387                 include_once $file;
388                 return true;
389             }
390
391             // try pear style
392             // Foo_Bar_Baz -> Foo/Bar/Baz.php
393             $file = sprintf('%s.%s',
394                             str_replace('_', DIRECTORY_SEPARATOR, $class_name),
395                             $this->controller->getExt('php'));
396             if (file_exists_ex($file)) {
397                 include_once $file;
398                 return true;
399             }
400         }
401         return false;
402     }
403
404     /**
405      *  指定されたクラスがキャッシュを利用可能かどうかをチェックする
406      *
407      *  @access protected
408      */
409     function _isCacheAvailable($class_name, $method_list, $weak)
410     {
411         // if we have getInstance(), use this anyway
412         if (in_array('getinstance', $method_list)) {
413             return false;
414         }
415
416         // if not, see if weak or not
417         return $weak ? false : true;
418     }
419 }
420 // }}}