Fixes #149
[akelos.git] / lib / AkClassExtender.php
blobbfc5717ad221b7527786f262341b861613a644d2
1 <?php
4 defined('AK_CLASS_EXTENDER_ENABLE_CACHE') ? null : define('AK_CLASS_EXTENDER_ENABLE_CACHE', !AK_DEV_MODE);
5 /**
6 * @ WARNING too experimental. This is a proof of concept. Do not use it for production.
7 *
8 * The AkClassExtender provides the means for extending core Akelos Framework
9 * functionality by chaining multiple objects and creating an extended
10 * composite class of the original.
12 * It is a good practice maintaining the level of parents low when writing OO
13 * code, but PHP classes are closed and do not allow runtime modification so
14 * this method will allow others to write core extensions that modify the default
15 * Akelos Framework behavior.
17 * This technique requires that the participants of the class chain are not included yet
18 * during execution, ass participants code will be joined, modified and cached for
19 * including it as a single source file.
21 class AkClassExtender
23 var $_extended_classes;
25 function extendClassWithSource($class_name_to_extend, $extension_path, $priority = 10)
27 $this->_extended_classes[$class_name_to_extend][$priority][] = $extension_path;
30 function _getExtensionFilePaths($class_name_to_extend)
32 $extension_files = array();
33 if(!empty($this->_extended_classes[$class_name_to_extend])){
34 $extensions = $this->_extended_classes[$class_name_to_extend];
35 ksort($extensions);
36 foreach ($extensions as $files){
37 $extension_files = array_merge($extension_files, $files);
40 return $extension_files;
43 function _getExtensionSourceAndChecksum($class_name_to_extend)
45 $file_contents = '';
46 $file_paths = $this->_getExtensionFilePaths($class_name_to_extend);
47 $checksum = md5(serialize($file_paths));
48 if(!$this->_canIncludeMergedFile($class_name_to_extend, $checksum)){
49 foreach ($file_paths as $file_path){
50 if(is_file($file_path)){
51 $file_contents .= Ak::file_get_contents($file_path);
54 return array($checksum, $file_contents);
55 }else{
56 return false;
60 function extendClasses()
62 foreach (array_keys($this->_extended_classes) as $class_name_to_extend){
63 $this->makeClassExtensible($class_name_to_extend);
67 function makeClassExtensible($class_name_to_extend)
69 list($checksum, $source) = $this->_getExtensionSourceAndChecksum($class_name_to_extend);
70 $merge_path = AK_TMP_DIR.DS.'.lib';
71 if($source){
72 if(preg_match_all('/[ \n\t]*([a-z]+)[ \n\t]*extends[ \n\t]*('.$class_name_to_extend.')[ \n\t]*[ \n\t]*{/i', $source, $matches)){
73 $replacements = array();
74 $extended_by = array();
76 foreach ($matches[2] as $k => $class_to_extend){
77 if(empty($last_method) && class_exists($class_to_extend)){
78 $last_method = $class_to_extend;
80 if($class_to_extend == $last_method || !empty($extended_by[$class_to_extend]) && in_array($last_method,$extended_by[$class_to_extend])){
81 if(!class_exists($matches[1][$k])){
82 $replacements[trim($matches[0][$k],"\n\t {")] = $matches[1][$k].' extends '.$last_method;
83 $last_method = $matches[1][$k];
84 $extended_by[$class_to_extend][] = $last_method;
85 } else {
86 trigger_error(Ak::t('The class %class is already defined and can\'t be used for extending %parent_class', array('%class' => $matches[1][$k], '%parent_class' => $class_name_to_extend)), E_NOTICE);
90 $source = str_replace(array_keys($replacements), array_values($replacements), $source);
92 $source = "$source<?php class Extensible$class_name_to_extend extends $last_method{} ?>";
93 if(md5($source) != @md5_file($merge_path.DS.'Extensible'.$class_name_to_extend.'.php')){
94 Ak::file_put_contents($merge_path.DS.'Extensible'.$class_name_to_extend.'.php', $source);
95 Ak::file_put_contents($merge_path.DS.'checksums'.DS.'Extensible'.$class_name_to_extend, $checksum);
99 include_once($merge_path.DS.'Extensible'.$class_name_to_extend.'.php');
102 function _canIncludeMergedFile($class_name_to_extend, $checksum)
104 $merge_path = AK_TMP_DIR.DS.'.lib';
105 if(AK_CLASS_EXTENDER_ENABLE_CACHE && file_exists($merge_path.DS.'Extensible'.$class_name_to_extend.'.php') &&
106 Ak::file_get_contents($merge_path.DS.'checksums'.DS.'Extensible'.$class_name_to_extend) == $checksum){
107 return true;
109 return false;