Pull to refresh

Работа с модулями

Reading time12 min
Views3K
Задача:
Использовать класс, в который можно добавлять новые методы воздействия, чтобы в последующем можно было ими пользоваться. При этом отделить эти методы по разным файлам.
Представим космический корабль, в котором используются:
a) методы перемещения по пространству, связанные с двигателем
б) методы зарядки энергии, связанные с его солнечными батареями
в) итд

Есть варианты:
1) Создание в объекте переменных и инициализация их через __construct, как новые классы.
Но, при создании каждого нового объекта — мы получаем снижение производительности и постоянную модификацию класса (что может осложнять работу нескольких программистов).
2) Работа через функции __call, __get, __set.
В 3-10 раз более медленная работа этих функций. Особенно при вызове call_user_func_array с параметрами.

Я предлагаю свой вариант.
Быстрый, оптимизированный, но при этом есть небольшая сложность в отладке.

В этом хабратопике я затрону темы:
1. Overloading
2. Ускорение загрузки модулей. Объединение модулей.
3. Оптимизация

Альтернативный подход


Мы можем объединить все модули в один файл, при этом одинаковые классы сливая в один.

Для этого я написал модуль, который разберу чуть попозже.

Для работы с модулем, используется (похожая на Singleton pattern) — функция mods. Она инициализирует модуль и выглядит так:
/**
* Синглетон паттерн для modules
*
* @return <modules> - класс
*/

function mods(){
  static $mod;
  if (!isset($mod))
    $mod=new modules;
  return $mod;
}

Для работы объединяющего модуля, используется два кеш файла.

Один из них необходим для того, чтобы хранить все подключаемые классы (в serialize виде). Из них, с помощью функции __autoload, выбираются нужные, что необходимы для работы.
Второй файл — все необходимые модули, слитые воедино в php-файле. Он как раз подгружается, вместо всех модулей.

Инициализация этих двух файлов происходит в начале, при инициализации модуля:
mods()->cache('cache.data')
      ->phpcache('cache.php');
Файлы можно задавать разные. Я, например на разных страницах сайта (на которых используются разные модули), использую разные phpcache файлы.

За счёт объединения всех файлов в один, загрузка файлов идёт быстрее. (плюс, советую установить себе eAccelerator, для того, чтобы файлы загружались мгновенно)

Но, необходимо ещё добавить модули, которые подгружаются в систему (это работает, как простой include):
mods()->include_php('modules/mod1.php');
И в конце сконвертировать модули в один файл, подгружая его:
mods()->include_modules();

Пример работы с модулями

//Режим проверки всех подключаемых файлов (когда идёт редактирование модулей)
define ('PHP_DEBUG_MODE',1);

//Инициализация файлов кеша для всех модулей и объединенного php-кода
mods()->cache('cache.file')
      ->phpcache('cache.php');

//Добавляем модули
if (PHP_DEBUG_MODE==1){
    $files = glob($moddir.'/*.inc');
    foreach ($files as $file){
        mods()->include_php($file);
}

//Загружаем все модули
mods()->include_modules();

Хочу обратить внимание на define PHP_DEBUG_MODE. Штудировать все модули на проверку изменений каждый раз — нет нужды. По этому, когда внтури модули не будут меняться, можно эту проверку отключить (приравняв PHP_DEBUG_MODE к 0), оставляя лишь подключение готового cache-php-модуля (include_modules), содержащего все необходимые классы.

Что получится

1) Слияние классов.
//file_A.php
class A{
  public $start;

  function start(){
    $this->start=1;
  }
}
//file_B.php
class A{
  private $stop;

  function stop(){
    $this->stop=1;
  }
}
Результат:
//result_cache.php
class A{
  public $start;
  private $stop;

  function start(){
    $this->start=1;
  }

  function stop(){
    $this->stop=1;
  }
}


2) Слияние функций внутри классов
//file_A.php
class A{
  public $start;

  function __construct(){
    $this->start=1;
  }
}
//file_B.php
class A{
  public $stop;

  function __construct(){
    $this->stop=1;
  }
}
Результат:
//result_cache.php
class A{
  public $stop;
  public $start;

  function __construct(){
    $this->stop=1;
    $this->start=1;
  }
}


Реализация


/**
* Автоподгрузка неизвестного класса
*
* @param <string> $class - имя неизвестного класса
*/

function __autoload($class){
  mods()->putclass($class);
}

/**
* Синглетон паттерн для modules
*
* @return <modules> - класс
*/

function mods(){
  static $mod;
  if (!isset($mod))
    $mod=new modules;
  return $mod;
}

class modules{
  private $dataclass=array();
  private $datacache_date=0;
  private $datacache_name=null;
  private $classesfile=null;
  private $classesfiledate=0;
  private $classesincluded=false;

  /**
  * Устанавливает бинарный файл для кеша
  *
  * @param <string> $file - путь+имя файла
  */

  public function cache($file){
    $this->datacache_name=$file;
    if (file_exists($file)){
      $this->datacache_date=filemtime($file);
    }
    return $this;
  }

  /**
  * Устанавливает php-файл для кеша
  *
  * @param <string> $file - путь+имя файла
  */

  public function phpcache($file){
    $this->classesfile=$file;
    if (file_exists($file))
      $this->classesfiledate=filemtime($file);
  }

  /**
  * Добавляет модуль в базу
  *
  * @param <string> $file - название файла модуля
  */

  public function include_php($file){
    if (!file_exists($file)){
      $this->fatalerror('no include file \'<b>'.$file.'</b>\'');
    }

    if ($this->datacache_date<filemtime($file)){
      $this->loadcachedata();
      $this->removefile($file);
      $this->parsephp(file_get_contents($file),$file);
      file_put_contents($this->datacache_name,serialize($this->dataclass));
      if ($this->classesfiledate!=0){
        unlink($this->classesfile);
        $this->classesfiledate=0;
      }
    }elseif ($this->classesfiledate!=0 && $this->classesfiledate<filemtime($file)){
      unlink($this->classesfile);
      $this->classesfiledate=0;
    }

    return $this;
  }

  /**
  * Загружает все обозначенные модули в систему
  */

  public function include_modules(){
    if (!$this->classesincluded){
      if (!file_exists($this->classesfile)){
        $this->loadcachedata();
        file_put_contents($this->classesfile,"<?\n".$this->printclass('')."\n?>");
      }
      include_once($this->classesfile);
      $this->classesincluded=true;
    }
  }

  /**
  * Удаляет модуль из базы
  *
  * @param <string> $file - файл модуля
  */

  private function removefile($file){
    foreach($this->dataclass as $key=>&$class){
      foreach ($class[0] as $name=>$vars)
        if ($name==$file)
          unset($class[0][$name]);
      foreach ($class[1] as $fk=>&$function){
        foreach ($function as $name=>$vars)
          if ($name==$file)
            unset($function[$name]);
        if ($function==array())
          unset($class[1][$fk]);
      }

      if ($class[0]==array() && $class[1]==array())
        unset($this->dataclass[$key]);
    }
  }

  /**
  * Печатает PHP-классы
  *
  * @param <string> $class - название класса
  * @return <string> - результат
  */

  private function printclass($class=''){
    if ($class==''){
      $string=implode("\n",$this->dataclass[''][0]);
      foreach($this->dataclass[''][1] as $fname=>$function)
        $string.="\nfunction ".$fname."{\n".implode("\n",$function)."\n}";
      return $string;
    }
   
    foreach ($this->dataclass as $classname=>$data){
      $name=preg_split('/\s/', $classname);

      if ($name[0]==$class){
        $string='class '.$classname.'{'.implode("\n",$data[0]);
        foreach($data[1] as $fname=>$function)
          $string.="\nfunction ".$fname."{\n".implode("\n",$function)."\n}";
       
        return $string.'}';
      }
    }
    return false;
  }

  /**
  * Загружает базу из cache-файла
  */

  private function loadcachedata(){
    if ($this->dataclass!=array())
      return;

    if (file_exists($this->datacache_name))
      $this->dataclass=unserialize(file_get_contents($this->datacache_name));
  }

  private function classfiles($class){
    foreach ($this->dataclass as $classname=>$data){
      $name=preg_split('/\s/', $classname);

      if ($name[0]==$class){
        $keys=array();
        foreach ($data[1] as $dat)
          $keys=array_merge(array_keys($data[0]),$keys);
        return implode(", ",array_unique(array_merge(array_keys($data[0]),$keys)));
      }
    }
  }
 
 
  /**
  * Подгружает необходимые модули.
  * Сохраняет их в php-cache файле.
  *
  * @param <string> $class - имя класса
  */

  public function putclass($class){
    if (!$this->classesincluded){
      $this->includemodules();
      if (class_exists($class,false))
        return;
    }

    $this->loadcachedata();

    if (($evclass=$this->printclass($class))!==false){
      file_put_contents($this->classesfile,"<?\n".$evclass."\n?>",FILE_APPEND);
      chmod($this->classesfile,0777);
      if (@eval($evclass)===false){
        $this->fatalerror('Error in class <b>'.$class.'</b> in file <b> '.$this->classfiles($class).'</b>');
      }
    }
  }

  /**
  * Добавить класс в базу
  *
  * @param <string> $classname - имя класса
  * @param <array> $class - Составляющие класса
  * @param <string> $filename - имя файла, откуда этот класс
  */

  private function mergeclass($classname,$class,$filename){
    if (!isset($this->dataclass[$classname][0][$filename])){
      $this->dataclass[$classname][0][$filename]='';
    }
     
    $this->dataclass[$classname][0][$filename].=$class[0];
    if (!isset($this->dataclass[$classname][1]))
      $this->dataclass[$classname][1]=array();

    foreach ($class[1] as $fname=>$function){
      if (!isset($this->dataclass[$classname][1][$fname][$filename]))
        $this->dataclass[$classname][1][$fname][$filename]='';
      $this->dataclass[$classname][1][$fname][$filename].=$function;
    }
  }

  /**
  * Парсит php файл, выдирая из него классы и функции
  *
  * @param <string> $php - внутренность файла
  * @param <string> $filename - название файла
  */

  private function parsephp($php,$filename){
    if (($pos=strpos($php,'<?'))!==false){
      $pos+=2;
      if (strtolower(substr($php,$pos,3))=='php')
        $pos+=3;
    }
    if (!preg_match_all('/((?:[^\'"\/$]|([\'"])(?:\\\\[\s\S]|[\s\S])*\2|\/[^*]|\/\*[\s\S]*\*\/|\$[^;\'"(){}\s]++)*)([{};]|\?>|$|\s(function)\s|\s(class)\s)/DSU',$php,$scobe,PREG_SET_ORDER|PREG_OFFSET_CAPTURE,$pos))
      return;

    for ($i=0,$j=count($scobe);$i<$j;$i++){
      if (isset($scobe[$i][5]) && $scobe[$i][5][0]=='class' && $i<$j-1 && $scobe[$i+1][3][0]=='{'){
        //is class
        $newclass=array('',array());

        for ($x=$i+2;$x<$j;$x++){
          $test=&$scobe[$x][3][0];

          if (isset($scobe[$x][4][0]) && $scobe[$x][4][0]=='function' && $x<$j-1 && $scobe[$x+1][3][0]=='{'){

            $name=$scobe[$x+1][1][0];
            $function='';
            for ($x+=2,$sco=1;$x<$j;$x++){
              $test=&$scobe[$x][3][0];
              if ($test=='{')
                ++$sco;
              elseif ($test=='}')
                if (--$sco==0)
                  break;
              $function.=$scobe[$x][0][0];
            }
            if ($x==$j)
              $this->fatalerror('error parsing function'.$name.' in class <b>'.$scobe[$i+1][1][0].'</b>, not found }');

            $newclass[1][$name]=$function;
          }elseif ($test==';')
            $newclass[0].=$scobe[$x][0][0];
          elseif ($test=='}')
            break;
          else $this->fatalerror('error parsing '.$test.' in class <b>'.$scobe[$i+1][1][0].'</b>');
        }

        $this->mergeclass($scobe[$i+1][1][0],$newclass,$filename);

        $i=$x;
      }else{
        if ($scobe[$i][3][0]=='?>')
          $this->mergeclass('',array($scobe[$i][1][0],array()),$filename);
        else
          $this->mergeclass('',array($scobe[$i][0][0],array()),$filename);
      }
    }
  }

  /**
  * Сообщает об ошибке
  *
  * @param <string> $error - расшифровка ошибки
  */

  private function fatalerror($error){
    trigger_error($error."<br/>\n", E_USER_ERROR);
    die();
  }
}


* This source code was highlighted with Source Code Highlighter.

Пользуйтесь на здоровье.
Tags:
Hubs:
Total votes 22: ↑9 and ↓13-4
Comments73

Articles