<?php
/**
 * -----------------------------------------------------------------------------
 *
 * SyL - Web Application Framework for PHP
 *
 * PHP version 4 (>= 4.3.x) or 5
 *
 * Copyright (C) 2006 k.watanabe
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * -----------------------------------------------------------------------------
 * @package   SyL
 * @author    k.watanabe <k.watanabe@syl.jp>
 * @copyright 2006 k.watanabe
 * @license   http://www.opensource.org/licenses/lgpl-license.php
 * @version   CVS: $Id: SyL_Container.php,v 1.9 2007/01/03 11:17:37 seasonstream Exp $
 * @link      http://www.syl.jp/
 * -----------------------------------------------------------------------------
 */

/**
 * 設定ファイルを読み込むクラス
 */
require_once SYL_INCLUDE_DIR . '/framework/SyL_Config.php';

/**
 * コンテナクラス
 *
 * @package   SyL
 * @author    k.watanabe <k.watanabe@syl.jp>
 * @copyright 2006 k.watanabe
 * @license   http://www.opensource.org/licenses/lgpl-license.php
 * @version   CVS: $Id: SyL_Container.php,v 1.9 2007/01/03 11:17:37 seasonstream Exp $
 * @link      http://www.syl.jp/
 */
class SyL_Container
{
    /**
     * コンポーネントオブジェクト格納配列
     * 
     * @access private
     * @var array
     */
    var $components = array();
    /**
     * コンポーネントオブジェクトメソッド戻り値格納配列
     * ※メソッドインジェクションの結果のみ
     * ※複数回同一メソッドを呼び出した場合は最後の結果のみ取得可能
     * 
     * @access private
     * @var array
     */
    var $component_returns = array();

    /**
     * コンストラクタ
     *
     * @access public
     */
    function SyL_Container()
    {
    }

    /**
     * イベント起動
     * 設定ファイルを読み込みコンテナに登録 or 実行
     *
     * @access public
     * @param string イベントメソッド名
     * @param string 設定ファイル
     * @param string 実行コンポーネント名
     */
    function raiseEvent($event, $config_type=array(), $component_name='')
    {
        // イベント開始ログ
        $this->log('debug', "$event event start");

        // グローバル設定
        static $config = array();
        // アプリケーション設定
        static $app_config = array();

        $tmp_config = array();

        // 設定ファイルの読み込み
        if ($event == 'appStream') {
            if ($component_name == '') {
                // コンポーネントが指定されていないと起動しない
                return;
            }
            if (count($config_type) > 0) {
                $app_config += $this->readConfig($event, $config_type);
            }
            if (!isset($app_config[$component_name])) {
                trigger_error("[SyL error] Application Component not exists ({$component_name})", E_USER_ERROR);
            }
            $tmp_config[$component_name] =& $app_config[$component_name];

        } else {
            if (count($config_type) > 0) {
                $config += $this->readConfig($event, $config_type);
                // 優先順位ソート処理
                uasort($config, array(&$this, 'sortConfig'));
            }
            $tmp_config =& $config;
        }

        // コンテナの設定ログ
        if ($event == 'finalStream') {
            $this->log('debug', "$event event global config ..." . print_r($config, true));
            //$this->log('debug', "$event event application config ..." . print_r($app_config, true));
        }
/*
echo "-------------- " . $event . " ---------------------------------------------------<br>";

echo "<pre>";
print_r($config);
echo "</pre>";

*/

        // コンポーネント取得・実行ループ
        foreach ($tmp_config as $name => $keys) {
            $classname = $keys['class'];

            // イベントがなければスキップ
            if (count($keys['event']) == 0) {
                continue;
            }



            // コンストラクタインジェクションであるか、または既にコンポーネントが存在すればスキップ
            if (!$keys['constructor'] && !$this->isComponent($name)) {
                // コンポーネントイベントスキップ
                if (!isset($keys['event'][$event]) || !in_array($name, $keys['event'][$event])) {
                    continue;
                }

                // コンポーネントをロード
                if (!$this->LoadLib($keys['file'])) {
                    // イベント削除
                    $this->deleteEventConfig($name, $name, $event, $keys, $tmp_config);
                    continue;
                }

                // クラス判定
                if (!class_exists($classname)) {
                    // クラスが無い場合
                    trigger_error("[SyL error] Component class not found (class name: {$classname})", E_USER_ERROR);
                }
                if ($keys['reference']) {
                    $component =& new $classname();
                } else {
                    $component = new $classname();
                }
                $this->setComponent($name, $component);
                // 取得したオブジェクト削除
                unset($component);
                // イベント削除
                $this->deleteEventConfig($name, $name, $event, $keys, $tmp_config);
            }

            foreach ($keys['args'] as $func => $args) {
                // メソッドイベントスキップ
                if (!isset($keys['event'][$event]) || !in_array($func, $keys['event'][$event])) {
                    continue;
                }

                $eval_args  = '';
                $value_args = array();
                for ($i=0; $i<count($args); $i++) {
                    list($type, $ref, $value) = explode(':', $args[$i], 3);
                    switch ($type) {
                    case 'constant':
                        if ($ref === 'true') {
                            $value_args[$i] =& constant($value);
                        } else {
                            $value_args[$i] = constant($value);
                        }
                        break;

                    case 'component':
                        $classmethod = explode('.', $value, 2);
                        $obj =& $this->getComponent($classmethod[0]);
                        if (isset($classmethod[1])) {
                            if (($idx = strpos($classmethod[1], '(')) !== false) {
                                $method = substr($classmethod[1], 0, $idx);
                                // メソッドが存在した場合のみセット
                                if (method_exists($obj, $method)) {
                                    if ($ref === 'true') {
                                        $value_args[$i] =& $obj->$method();
                                    } else {
                                        $value_args[$i] = $obj->$method();
                                    }
                                }
                            } else {
                                $property = $classmethod[1];
                                // プロパティが存在した場合のみセット
                                if (array_key_exists($property, get_object_vars($obj))) {
                                    if ($ref === 'true') {
                                        $value_args[$i] =& $obj->$property;
                                    } else {
                                        $value_args[$i] = $obj->$property;
                                    }
                                }
                            }
                        } else {
                            if ($ref === 'true') {
                                $value_args[$i] =& $obj;
                            } else {
                                $value_args[$i] = $obj;
                            }
                        }
                        break;

                    default:
                      if ($ref === 'true') {
                        $value_args[$i] =& $value;
                      } else {
                        $value_args[$i] = $value;
                      }
                      break;
                    }
                    $eval_args .= ($eval_args == '') ? '$value_args[' . $i . ']' : ', $value_args[' . $i . ']';
                }

                $funcs = explode(':', $func);
                switch (strtolower($funcs[0])) {
                case 'constructor':
                    // コンポーネントをロード
                    if ($this->LoadLib($keys['file'])) {
                        $ref = $keys['reference'] ? '&' : '';
//echo '$component =' . $ref . ' ' . $classname  . '::' . $funcs[1] . '(' . $eval_args . ');' . "<br>";
                        if ($funcs[1] != '') {
                            eval('$component =' . $ref . ' ' . $classname  . '::' . $funcs[1] . '(' . $eval_args . ');');
                        } else {
                            eval('$component =' . $ref . ' new ' . $classname . '(' . $eval_args . ');' ); 
                        }
                        $this->setComponent($name, $component);
                    }
                    break;

                case 'method':
                    $component =& $this->getComponent($name);
                    $method_name = $name . '.' . $funcs[1];
                    if (method_exists($component, $funcs[1])) {
                        $this->component_returns[$method_name] = call_user_func_array(array(&$component, $funcs[1]), $value_args);
                    } else {
                        $this->log('info', "Method injection failed: method not found (" . get_class($component) . ".{$funcs[1]})");
                        $this->component_returns[$method_name] = null;
                    }
                    break;

                case 'setter':
                    $component =& $this->getComponent($name);
                    if ($funcs[2] === 'true') {
                        foreach ($value_args as $key => $value) {
                            $component->$funcs[1] =& $value_args[$key];
                        }
                    } else {
                        call_user_func_array(array(&$component, $funcs[1]), $value_args);
                    }
                    break;
                }

                // 取得したオブジェクト削除
                unset($component);
                // イベント削除
                $this->deleteEventConfig($name, $func, $event, $keys, $tmp_config);
            }
        }
    }

    /**
     * 設定ファイルを読み込む
     *
     * @access private
     * @param string イベント名
     * @param string 設定ファイルのタイプ
     * @return array 設定ファイルの内容
     */
    function readConfig($event, $config_type)
    {
        $config = array();
        foreach ($config_type as $type => $file) {
            $reader =& SyL_Config::getObject($type, $file, $this->getComponent(SYL_CONFIG_CONTROLLER_NAME), $event);
            $reader->parseXML();
            $config += $reader->getConfig();
        }
        return $config;
    }

    /**
     * 設定ファイルを取得する
     *
     * @access private
     * @param string イベント名
     * @param string 設定ファイルのタイプ
     * @param string 設定ファイル
     * @return array 設定ファイルの内容
     */
/*
    function getConfig($event, $type, $file)
    {
        if (file_exists($file)) {
            $reader =& SyL_Config::getObject($type, $file, $this->getComponent(SYL_CONFIG_CONTROLLER_NAME), $event);
            $reader->parseXML();
            return $reader->getConfig();
        } else {
            return array();
        }
    }
*/

    /**
     * 実行順序を優先順位ごとにソートする
     *
     * @access public
     * @param array 現在の設定配列
     * @param array 次の設定配列
     * @return object コンポーネントオブジェクト
     */
    function sortConfig($current, $next)
    {
        return strcmp($current['priority'], $next['priority']);
    }

    /**
     * ライブラリをロード
     *
     * @access public
     * @param string ロードファイル
     * @return bool true: OK, false, NG
     */
    function LoadLib($file)
    {
        if (is_file(SYL_INCLUDE_DIR . "/{$file}")) {
            // SyLディレクトリからのパス
            include_once SYL_INCLUDE_DIR . "/{$file}";
        } else if (is_file(SYL_WEBAPP_LIB_DIR . "/{$file}")) {
            // アプリケーションディレクトリからのパス
            include_once SYL_WEBAPP_LIB_DIR . "/{$file}";
        } else if (is_file($file)) {
            // フルパス
            include_once $file;
        } else {
            // コンポーネントファイルが無い場合
            $this->log('info', "Component include failed: file not found (" . SYL_INCLUDE_DIR . "/{$file} or " . SYL_WEBAPP_LIB_DIR . "/{$file} or {$file})");
            return false;
        }
        return true;
    }

    /**
     * イベント設定を削除する
     *
     * @access private
     * @param string 設定名
     * @param string メソッド名
     * @param string イベント名
     * @param array 現在の設定名に対する設定配列
     * @param array 設定配列
     */
    function deleteEventConfig($name, $func, $event, &$keys, &$config)
    {
        // イベント削除
        if (isset($keys['event']['*'])) {
            $index = array_search($func, $keys['event']['*']);
            if ($index !== false) {
                unset($config[$name]['event']['*'][$index]);
                if (count($config[$name]['event']['*']) == 0) {
                    unset($config[$name]['event']['*']);
                }
            }
        } else if (isset($keys['event'][$event])) {
            $index = array_search($func, $keys['event'][$event]);
            if ($index !== false) {
                unset($config[$name]['event'][$event][$index]);
                if (count($config[$name]['event'][$event]) == 0) {
                    unset($config[$name]['event'][$event]);
                }
            }
        }
    }

    /**
     * コンポーネントを取得
     *
     * @access public
     * @return object コンポーネントオブジェクト
     */
    function &getComponent($name)
    {
        if (!$this->isComponent($name)) {
            
            trigger_error("[SyL error] Component not found in container ($name)", E_USER_ERROR);
        }
        return $this->components[$name];
    }

    /**
     * コンポーネントをセット
     *
     * @access public
     * @param string コンポーネント名
     * @param object コンポーネントオブジェクト
     */
    function setComponent($name, &$component)
    {
        $this->components[$name] =& $component;
    }

    /**
     * コンポーネントの存在判定
     *
     * @access public
     * @param string コンポーネント名
     * @param bool true: 存在する、false: 存在しない
     */
    function isComponent($name)
    {
        return isset($this->components[$name]);
    }

    /**
     * コンポーネントの削除
     *
     * @access public
     * @param string コンポーネント名
     */
    function deleteComponent($name)
    {
        unset($this->components[$name]);
    }

    /**
     * コンポーネントメソッドの結果を取得
     * ※複数回同一メソッドを呼び出した場合は最後の結果のみ取得可能
     *
     * @access public
     * @param string コンポーネント名 + '.' + メソッド名
     * @return string コンポーネントメソッドの結果
     */
    function getComponentMethodResult($name)
    {
        return $this->component_returns[$name];
    }

    /**
     * ログ保存
     *
     * @access private
     * @param string ログ保存文字列
     */
    function log($type, $value)
    {
        if ($this->isComponent('log')) {
            SyL_Loggers::$type("[Container] " . $value);
        }
    }
}

?>
