/** * HAPPYBORN OOP/AOP向けフレームワークのほぼ本体 * クラス・関数の自動走査・自動読み込み・自動生成を行う * @param string モジュール本体を探すヒント文字列(URI) or .区切り文字 (混在はNG /がある場合、URIとして優先して処理される) * @param bool クラスの存在チェックをからめたコールの場合は、sysエラーで終了せず、エラーをretrunしてあげる * @return return instance名を返す */ function loadModule($argHint, $argClassExistsCalled = FALSE) { // オートジェネレートフラグの取得 $autoGenerateFlag = getAutoGenerateEnabled(); // パッケージヒント文字列 $hintPath = str_replace('/', '.', $argHint); // パッケージ定義の初期化 $pkConfXMLs = _initFramework(TRUE); // オートジェネレートチェック if (TRUE === $autoGenerateFlag) { $generatedIncFileName = getAutoGeneratedPath() . $hintPath . '.generated.inc.php'; if (FALSE === resolveUnlinkAutoGeneratedFile($generatedIncFileName)) { // ジェネレートされたファイルを読み込んで終了 return TRUE; } } // パッケージ名に該当するノードが格納されたパッケージXML格納用 $pkConfXML = NULL; // パッケージ名 $packageName = NULL; // 代表クラス名 本来は必要ないので空 // ※パッケージ名と実際にパッケージ読み込み後に利用するクラス名が違う場合に利用される // ※一つのリンクされたファイルの中に複数のクラス定義がある場合等 $className = NULL; // defaultパッケージの走査は最後 or 明示の時だけに絞る $defaultPackageFlag = FALSE; // defaultパッケージの明示指定があるかどうか $matches = NULL; if (preg_match('/^default\\.(.+)/', $argHint, $matches)) { // defaultパッケージを走査対象のパッケージとする $defaultPackageFlag = TRUE; $packageName = $matches[1]; $pkConfXML = $pkConfXMLs[0]['dom']; } else { // Hintパスからパッケージを当てる if (!is_file($argHint)) { // 読み込み済みの$pkConfXMLの数分だけ処理 for ($pkConfXMLCnt = 0; count($pkConfXMLs) > $pkConfXMLCnt; $pkConfXMLCnt++) { $pkConfXML = $pkConfXMLs[$pkConfXMLCnt]['dom']; if (isset($pkConfXML->{$argHint})) { // ※見つかった! // Hintのパス情報そのままの定義があればそれを使う $packageName = $argHint; break; } // ※まだ見つからない! // パッケージXMLの子ノード総当りでパターンマッチ検索 foreach (get_object_vars($pkConfXML) as $key => $ChildNode) { if (isset($ChildNode->pattern) && preg_match('/' . $ChildNode->pattern . '/', $argHint)) { // ※見つかった! $packageName = $key; // ルートのループも含めてスキップ break 2; } } // ※まだ見つからない! // Hintのパス情報からたどる $pathHints = explode('/', $argHint); if (count($pathHints) == 0) { $pathHints = explode('.', $argHint); } $pathHintMaxCnt = count($pathHints); // XXX コレなーんか処理おかしい気がする・・・後ろから回すべきじゃね?? // hintの長い状態から上を徐々に削って短くし、完全一致する場所を探す for ($pathHintCnt = 0; $pathHintMaxCnt > $pathHintCnt; $pathHintCnt++) { $packageName = implode('.', $pathHints); if (isset($pkConfXML->{$packageName})) { // ※見つかった! // ルートのループも含めてスキップ break 2; } // 見つからないのでやり直し $packageName = NULL; unset($pathHints[$pathHintCnt]); } } } // ※まだ見つからない! if (NULL === $packageName) { // ここまできてなかったら仮でdefaultパッケージを走査対象のパッケージとする $defaultPackageFlag = TRUE; $packageName = $argHint; $pkConfXML = $pkConfXMLs[0]['dom']; } } // 実際にモジュールを設定したパッケージから設定内容を取得して読み込みを行う if (TRUE === $defaultPackageFlag) { // defaultパッケージを捜査 _loadDefaultModule($argHint, $argClassExistsCalled, $packageName); $className = NULL; } else { // オートジェネレートされるファイルを初期化 if (TRUE === $autoGenerateFlag) { // 空でジェネレートファイルを生成 @file_put_contents($generatedIncFileName, ''); } // 明示的な指定がある場合の捜査 // パッケージ定義の中に、複数のlinkが設定されていたら、そのlink数分処理をループ // linkを全て読み込む for ($packagePathCnt = 0, $errorCnt = 0; count($pkConfXML->{$packageName}->link) > $packagePathCnt; $packagePathCnt++) { $fileget = FALSE; $addmethod = FALSE; $rename = FALSE; // メソッドを追加する処理 if (0 < @strlen($pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->addmethod)) { $fileget = TRUE; $addmethod = TRUE; } // クラス名をリネームする処理 if (0 < @strlen($pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->renameto) && 0 < @strlen($pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->renamefrom)) { $fileget = TRUE; $rename = TRUE; } // ファイルを変数に読み込むかどうか if (TRUE === $fileget) { // 変数に読み込む(addmethodかrenmae処理が予定されている) $classdef = @file_get_contents($pkConfXML->{$packageName}->link[$packagePathCnt], TRUE); if (strlen($classdef) == 0) { // 読み込みに失敗した場合、パッケージがハズレだったので_loadDefaultModuleでdefault定義パッケージを走査して貰ってみる事にする $subPackageName = $pkConfXML->{$packageName}->link[$packagePathCnt]; if (preg_match('/^default\\.(.+)/', $subPackageName, $matches)) { $subPackageName = $matches[1]; } // _loadDefaultModuleの再帰処理による自動解決を試みる $classdefs = _loadDefaultModule($argHint, $argClassExistsCalled, $subPackageName, TRUE); // 読み込みに成功したクラス定義を変数に詰め直す // 成功していない場合、ここまで処理が到達しない $classdef = $classdefs['classdef']; $classPath = $classdefs['classpath']; } else { // 読み込みに成功したクラス定義を変数に詰め直す $classPath = $pkConfXML->{$packageName}->link[$packagePathCnt]; } } else { // ファイルはインクルードで処理する if (FALSE === @(include_once $pkConfXML->{$packageName}->link[$packagePathCnt])) { // includeに場合、パッケージがハズレだったので_loadDefaultModuleでdefault定義パッケージを走査して貰ってみる事にする $subPackageName = $pkConfXML->{$packageName}->link[$packagePathCnt]; if (preg_match('/^default\\.(.+)/', $subPackageName, $matches)) { $subPackageName = $matches[1]; } // loadModuleの再帰処理による自動解決を試みる _loadDefaultModule($argHint, $argClassExistsCalled, $subPackageName); } else { // ジェネレート処理 if (TRUE === $autoGenerateFlag) { generateIncCache($generatedIncFileName, $pkConfXML->{$packageName}->link[$packagePathCnt]); } } } // methodの動的追加を実行 if (TRUE === $addmethod) { if (!isset($classBuffer)) { ob_start(); echo $classdef; $classBuffer = ob_get_clean(); } // 追加するメソッド定義を探す $addmethoddef = $pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->addmethod; if (FALSE !== strpos($addmethoddef, ',')) { $addmethoddefs = explode(',', $addmethoddef); for ($addmethoddefIndex = 0; count($addmethoddefs) > $addmethoddefIndex; $addmethoddefIndex++) { if (isset($pkConfXML->{$packageName}->{trim($addmethoddefs[$addmethoddefIndex])}) && isset($pkConfXML->{$packageName}->{trim($addmethoddefs[$addmethoddefIndex])}->attributes()->targetclass) && strlen($pkConfXML->{$packageName}->{trim($addmethoddefs[$addmethoddefIndex])}->attributes()->targetclass) > 0) { $targetClassName = $pkConfXML->{$packageName}->{trim($addmethoddefs[$addmethoddefIndex])}->attributes()->targetclass; $addmethod = (string) $pkConfXML->{$packageName}->{trim($addmethoddefs[$addmethoddefIndex])}; $classBuffer = preg_replace('/(class|abstract|interface)\\s+?' . trim($targetClassName) . '(.*)?\\{/', '$1 ' . trim($targetClassName) . '\\2 { ' . $addmethod, $classBuffer); } else { _systemError('class method add notfound node \'' . $packageName . '.' . trim($addmethoddefs[$addmethoddefIndex]) . ' or undefined attribute \'targetclass\''); } } } else { if (isset($pkConfXML->{$packageName}->{$addmethoddef}) && isset($pkConfXML->{$packageName}->{trim($addmethoddef)}->attributes()->targetclass) && strlen($pkConfXML->{$packageName}->{trim($addmethoddef)}->attributes()->targetclass) > 0) { $targetClassName = $pkConfXML->{$packageName}->{trim($addmethoddef)}->attributes()->targetclass; $addmethod = (string) $pkConfXML->{$packageName}->{trim($addmethoddef)}; $classBuffer = preg_replace('/(class|abstract|interface)\\s+?' . trim($targetClassName) . '(.*)?\\{/', '$1 ' . trim($targetClassName) . '\\2 { ' . $addmethod, $classBuffer); } else { _systemError('class method add notfound node \'' . $packageName . '.' . trim($addmethoddef) . ' or undefined attribute \'targetclass\''); } } } // クラス名リネームの実行 // XXX 処理の順番に注意!!先にaddmethodを処理。renameした後だとクラス名が変わっていてaddにしくじるので if (TRUE === $rename) { if (!isset($classBuffer)) { ob_start(); echo $classdef; $classBuffer = ob_get_clean(); } // リネーム $renametoClassName = $pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->renameto; $renamefromClassName = $pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->renamefrom; if (FALSE !== strpos($renametoClassName, ',')) { $renametoClassName = explode(',', $renametoClassName); $renamefromClassName = explode(',', $renamefromClassName); if (!(is_array($renametoClassName) && is_array($renamefromClassName) && count($renametoClassName) == count($renamefromClassName))) { _systemError('class rename error! renameto-from count missmatch renameto-count=' . count($renametoClassName) . ' renamefrom-count=' . count($renamefromClassName)); } for ($renameIndex = 0; count($renamefromClassName) > $renameIndex; $renameIndex++) { $classBuffer = preg_replace('/(class|abstract|interface)\\s+?' . trim($renamefromClassName[$renameIndex]) . '(\\s|\\{|\\r|\\n)/', '\\1 ' . trim($renametoClassName[$renameIndex]), $classBuffer); } } else { $classBuffer = preg_replace('/(class|abstract|interface)\\s+?' . trim($renamefromClassName) . '/', '\\1 ' . $renametoClassName, $classBuffer); } } // 定義の動的変更を実行 if (isset($classBuffer)) { // PHPの開始タグがあるとコケるので消す $classBuffer = preg_replace('/^<\\?(php){0,1}(\\s|\\t)*?(\\r\\n|\\r|\\n)/s', '', $classBuffer); // PHPの終了タグがあるとコケるので消す $classBuffer = preg_replace('/(\\r\\n|\\r|\\n)\\?>(\\r\\n|\\r|\\n){0,1}$/s', '', $classBuffer); eval($classBuffer); $classCheck = ''; $matches = NULL; if (preg_match('/(class|abstract|interface)\\s+?([^\\s\\t\\r\\n\\{]+)/', $classBuffer, $matches) && is_array($matches) && isset($matches[2]) && strlen($matches[2]) > 0) { $classCheck = $matches[2]; } // ジェネレート処理 if (TRUE === $autoGenerateFlag) { generateClassCache($generatedIncFileName, $classPath, $classBuffer, $classCheck); } unset($classdef); unset($classBuffer); } // クラス名をマッピングする処理 if (0 < @strlen($pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->mapto) && 0 < @strlen($pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->mapfrom)) { $classCheck = ''; $maptoClassName = $pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->mapto; $mapfromClassName = $pkConfXML->{$packageName}->link[$packagePathCnt]->attributes()->mapfrom; if (FALSE !== strpos($maptoClassName, ',')) { $maptoClassName = explode(',', $maptoClassName); $mapfromClassName = explode(',', $mapfromClassName); if (!(is_array($maptoClassName) && is_array($mapfromClassName) && count($maptoClassName) == count($mapfromClassName))) { _systemError('class map error! mapto-from count missmatch mapto-count=' . count($maptoClassName) . ' mapfrom-count=' . count($mapfromClassName)); } $mapClass = array(); for ($mapIndex = 0; count($maptoClassName) > $mapIndex; $mapIndex++) { $mapClass[] = 'class ' . $maptoClassName[$mapIndex] . ' extends ' . $mapfromClassName[$mapIndex] . '{}'; } $mapClass = implode('', $mapClass); $classCheck = ' && !class_exists(\'' . $maptoClassName[0] . '\', FALSE)'; } else { $mapClass = 'class ' . $maptoClassName . ' extends ' . $mapfromClassName . '{}'; $classCheck = ' && !class_exists(\'' . $maptoClassName . '\', FALSE)'; } // マップクラス生成 eval($mapClass); // ジェネレート処理 if (TRUE !== $fileget && TRUE === $autoGenerateFlag) { @file_put_contents_e($generatedIncFileName, '<?php' . PHP_EOL . 'if(FALSE === $unlink' . $classCheck . '){ ' . $mapClass . ' }' . PHP_EOL . '?>', FILE_APPEND); @chmod($generatedIncFileName, 0777); } unset($mapClass); } } // 代表クラス名が定義されているかどうか // パッケージ名と実際に利用しようとしているクラス名が異なる場合の定義はココを通る if (isset($pkConfXML->{$packageName}->class)) { $className = $pkConfXML->{$packageName}->class; // そのパッケージの代表クラスの読み込みが成功しているかどうかをチェック if (!class_exists($className, FALSE)) { if (FALSE === $argClassExistsCalled) { // クラスが存在しないエラー _systemError('not found class ' . $className . ' on ' . $pkConfXML->{$packageName}->link[$packagePathCnt] . '!! Please check default path config.' . PHP_EOL . str_replace(PATH_SEPARATOR, PHP_EOL, get_include_path())); } return FALSE; } } } // 代表クラス名を返して終了 return (string) $className; }
/** * 定義情報をチェックする * @param string XMLファイルのパス文字列 or XML文字列 */ public static function generate($argTarget, $argSection, $argBasePathTarget = '') { $filepathUsed = FALSE; $targetXML = $argTarget; if (1024 >= strlen($argTarget) && TRUE === file_exists_ip($argTarget)) { // ファイルパス指定の場合はファイルの中身を文字列として一旦取得 $filepathUsed = TRUE; $targetXML = file_get_contents($argTarget); } // クラス名を確定する $className = $argSection; if (TRUE === $filepathUsed) { // オートジェネレートフラグの取得(フレームワークのコアから貰う) $autoGenerateFlag = getAutoGenerateEnabled(); if (TRUE === $autoGenerateFlag) { $generatedClassPath = self::_getAutogenerateFilePath($argTarget); // unlinkされたか if (FALSE === resolveUnlinkAutoGeneratedFile($generatedClassPath)) { // クラス名を返して終了 return $className; } } } // XML文字列を、XMLオブジェクト化 libxml_use_internal_errors(TRUE); $FlowXML = simplexml_load_string($targetXML); if (FALSE === $FlowXML) { throw new LibXMLException(libxml_get_errors()); } // FlowXMLとして正しいかどうか if (TRUE === self::validate($FlowXML)) { // $argSectionがIndex_authedだったとして、index-authedに変換される //$targetSection = str_replace('_', '-', ucfirst($argSection)); // FlowXMLに基いてコントローラクラスを自動生成する $classDef = ''; foreach ($FlowXML->children() as $firstNode) { // firstNodeのid属性から暮らす名を決める // idがindex-authedだったとして、Index_authedに変換される $tmpAttr = $firstNode->attributes(); $sectionClassName = str_replace('-', '_', ucfirst($tmpAttr['id'])); // 上位クラスは暫定でWebコントローラと言う事にする $extends = ' extends WebFlowControllerBase'; // 上位クラスが指定されているかどうか if (isset($tmpAttr['extends']) && strlen($tmpAttr['extends']) > 0) { // そのまま採用 $extends = ' extends ' . $tmpAttr['extends']; } elseif (isset($tmpAttr['type']) && strlen($tmpAttr['type']) > 0) { $typeStr = $tmpAttr['type']; // XXX $typeStrはSimpleXMLElementなので===の完全一致では動作しない! if ('web' == $typeStr) { $extends = ' extends WebFlowControllerBase'; } elseif ('api' == $typeStr) { $extends = ' extends APIControllerBase'; } elseif ('image' == $typeStr) { $extends = ' extends ImageControllerBase'; } } $methods = array(); // メソッド定義 foreach ($firstNode->children() as $methodNode) { // 2次元目はメソッド定義 $methodName = $methodNode->getName(); // constructとdestructだけをマジックメソッドのマップとしてサポートする if ('construct' == $methodName || 'destruct' == $methodName) { $methodName = '__' . $methodName; } $method = PHP_TAB . 'function ' . $methodName . '(%s){' . PHP_EOL . '%s' . PHP_EOL . PHP_TAB . PHP_TAB . 'return TRUE;' . PHP_EOL . PHP_TAB . '}' . PHP_EOL; $methodArg = ''; $methodArgs = $methodNode->attributes(); if (count($methodArgs) > 0) { $methodArg = array(); foreach ($methodArgs as $key => $val) { $arg = ''; $arg .= '$' . $key . ' = ' . self::_resolveValue($val); $methodArg[] = $arg; } $methodArg = implode(', ', $methodArg); } // 3次元目は以降はネスト構造のソースコード定義 $code = ''; if (isset($extends) && strlen($extends) > 0 && ' extends WebFlowControllerBase' == $extends) { // WebFlowBaseのinitをメソッドの頭で必ず呼ぶ $code .= PHP_TAB . PHP_TAB . '$autoValidated = parent::_initWebFlow();' . PHP_EOL; } foreach ($methodNode->children() as $codeNode) { $code .= self::_generateCode($codeNode, $argBasePathTarget); } $method = sprintf($method, $methodArg, $code); $methods[] = $method; } // クラス定義つなぎ合わせ $classDef .= PHP_EOL . 'class ' . $sectionClassName . $extends . PHP_EOL; $classDef .= '{' . PHP_EOL . PHP_EOL; $classDef .= PHP_TAB . 'public $section=\'' . basename($argSection) . '\';' . PHP_EOL; $classDef .= PHP_TAB . 'public $target=\'' . $argBasePathTarget . '\';' . PHP_EOL . PHP_EOL; for ($methodIdx = 0; count($methods) > $methodIdx; $methodIdx++) { $classDef .= $methods[$methodIdx]; } $classDef .= '}' . PHP_EOL; } // オートジェネレートチェック if (TRUE === $filepathUsed && TRUE === $autoGenerateFlag) { // 空でジェネレートファイルを生成 @file_put_contents($generatedClassPath, ''); // ジェネレート generateClassCache($generatedClassPath, $argTarget, $classDef, $className); // 静的ファイル化されたクラスファイルを読み込んで終了 // fatal errorがいいのでrequireする //require_once $generatedClassPath; } // オートジェネレートが有効だろうが無効だろうがココまで来たらクラス定義の実体化 eval($classDef); return $className; } return FALSE; }