Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > PHP: Общие вопросы > Правильный Include


Автор: WolfON 2.6.2007, 00:24
Есть структура проекта:
Код

/
  /classes
    class.Test.php
    class.DB.php
  /config
    DB.inc.php
  /tests
    testTest.php
  index.php


Для модульного тестирования я использую пакет PEAR\PHPUnit, чтобы все это добро заработало в конфиг пришлось добавить
Код

include_path = ".;.\;c:\php\pear"


В class.DB.php:
Код

require_once('../config/DB.inc.php');


В class.Test.php:
Код

require_once('class.DB.php');


В testTest.php:
Код

require_once('../classes/class.Test.php');    
require_once('PHPunit/Framework.php');
require_once('PHPUnit/TextUI/TestRunner.php');


в index.php:
Код

require_once('classes\class.Test.php');


роде все просто и очевидно, но после того, как я изменил конфиг php class.Test.php работает вполне себе спокойно, а вот index.php ругается на 
Fatal error: require_once() [function.require]: Failed opening required '../config/DB.conf.php' (include_path='.;.\;c:\php\pear') in E:\Projects\Test\classes\class.Database.php on line 2

E:\Projects\Test в конфиге апача соответствует /test на сервере, а главная папка c:\www

Что-то я тут недопонимаю >_>

Автор: sTa1kEr 2.6.2007, 00:54
http://ru2.php.net/manual/ru/function.include.php
Цитата

Files for including are first looked in include_path relative to the current working directory and then in include_path relative to the directory of current script. E.g. if your include_path is ., current working directory is /www/, you included include/a.php and there is include "b.php"  in that file, b.php is first looked in /www/  and then in /www/include/. If filename begins with ./ or ../, it is looked only in include_path relative to the current working directory.

Я бы рекомендовал использовать абсолютные пути. Если вы придерживаетесь ООП, то могу предложить использовать класс, который я как-то написал, что бы забыть про инклюды как страшный сон.
Код

<?php
 
// Абсолютный путь к корню сайта
if (!defined("AL_ROOT"))
   define("AL_ROOT", "/home/autoload/www");
 
// Путь к каталогу с классами относительно AL_ROOT
if (!defined("AL_CLASSDIR"))
   define("AL_CLASSDIR", "/classes");
 
// Глубина сканирования директорий
if (!defined("AL_LEVEL"))
   define("AL_LEVEL", 5);
 
// Режим отладки
if (!defined("AL_DEBUG"))
   define("AL_DEBUG", true);
 
function __autoload($className)
{
   Autoload::Instance()->LoadClass($className);
}
 
function Debug($msg)
{
   if (AL_DEBUG)
   {
      if (defined("STDOUT"))
      {
         fwrite(STDOUT, $msg);
      }
      else
      {
         echo nl2br($msg);
         flush();
      }
   }
}
 
class Autoload
{
   private static $_thisInstance = null;
 
   private $Pattern = null;
 
   private $ClassMap = null;
 
   private $IsParsed = false;
 
   private function __construct()
   {
      if (!file_exists("classes.map"))
      {
         $this->ClassMap = array(array(), array());
         $this->ParseSources();
         $this->Serialize();
      }
      else
      {
         $this->ClassMap = unserialize(file_get_contents("classes.map"));
      }
   }
 
   public function ParseSources()
   {
      Debug("Start parsing classes.\n");
      ini_set("max_execution_time", 900);
      set_time_limit(900);
      ignore_user_abort();
 
      if ($this->Pattern === null)
      {
         for ($i = 0; $i < AL_LEVEL + 1; $i++)
         {
            $level[$i] = implode("*", array_fill(0, $i + 1, "/"));
         }
         $this->Pattern = "{".implode(",", $level)."}*.php";
      }
 
      Debug("Get list php files.\n");
      foreach (glob(AL_ROOT.AL_CLASSDIR.$this->Pattern, GLOB_BRACE) as $fileName)
      {
         $this->ParseFile(substr($fileName, strlen(AL_ROOT)));
      }
 
      Debug("Parse successfull finished.\n");
      $this->IsParsed = true;
   }
 
   private function Serialize()
   {
      if (count($this->ClassMap[0]) > 0 && count($this->ClassMap[1]) > 0)
      {
         file_put_contents("classes.map", serialize($this->ClassMap));
      }
   }
 
   private function ParseFile($fileName)
   {
      Debug("Parse file $fileName: ");
      $lastModified = filemtime(AL_ROOT.$fileName);
      if (isset($this->ClassMap[1][$fileName]))
      {
         if ($lastModified <= $this->ClassMap[1][$fileName])
         {
            Debug("File not modified.\n");
            return;
         }
         else
         {
            $this->UnsetFile($fileName);
         }
      }
      $source = file_get_contents(AL_ROOT.$fileName);
      $tokens = token_get_all($source);
      while (($token = array_shift($tokens)) !== null)
      {
         if (is_array($token) && ($token[0] == T_CLASS || $token[0] == T_INTERFACE) && $tokens[1][0] == T_STRING)
         {
            if (isset($this->ClassMap[0][$tokens[1][1]]))
            {
               Debug("\nWarning! Class {$tokens[1][1]} alrady declared in {$this->ClassMap[0][$tokens[1][1]]}\n");
            }
            else
            {
               $this->ClassMap[0][$tokens[1][1]] = $fileName;
            }
            Debug("{$tokens[1][1]}; ");
         }
      }
      Debug("\n");
      $this->ClassMap[1][$fileName] = $lastModified;
   }
 
   private function UnsetFile($fileName)
   {
      unset($this->ClassMap[1][$fileName]);
      while(($key = array_search($fileName, $this->ClassMap[0])) !== false)
      {
         unset($this->ClassMap[0][$key]);
      }
      if ($this->IsParsed)
      {
         $this->Serialize();
      }
   }
 
   private function UnsetClass($className)
   {
      $file = $this->ClassMap[0][$className];
      $this->UnsetFile($file);
   }
 
   /**
    * @return Autoload
    */
   public static function Instance()
   {
      if (Autoload::$_thisInstance === null)
      {
         Autoload::$_thisInstance = new Autoload();
      }
      return Autoload::$_thisInstance;
   }
 
   public function LoadClass($className)
   {
      if (isset($this->ClassMap[0][$className]))
      {
         @include_once(AL_ROOT.$this->ClassMap[0][$className]);
         if (!class_exists($className, false) && !interface_exists($className, false))
         {
            $this->UnsetClass($className);
            $this->Serialize();
            $this->LoadClass($className);
         }
      }
      else
      {
         if (!$this->IsParsed)
         {
            $this->ParseSources();
            $this->Serialize();
            $this->LoadClass($className);
         }
      }
   }
}

 ?>

Автор: WolfON 2.6.2007, 13:09
Цитата
If filename begins with ./ or ../, it is looked only in include_path relative to the current working directory.

Хех, а ларчик просто открывался %)

Как я понял, у вас в коде есть карта классов, которая загружается в память и потом через нее все инклудит - хорошее решение, но мне пока, учитывая размеры этого проекта - такое не надо.

Автор: Golda 2.6.2007, 14:12
WolfON, 
чтобы измбежать проблем с инклюдами, - только абсолютные пути.

include('file.php') => include(dirname(__FILE__) . '/file.php');  и. т.д.

А если существенно еще и require_once или include_once, то лучше еще и realpath(). 

Вполне вероятная ситуация, когда 

Код

require_once('../config/DB.inc.php');
require_once('.DB.inc.php');


будут как раз восприняты, как разные файлы и подключатся дважды. 

Вы не уследите, когда какая-то функция вдруг начнет использоваться не только из web-интерфейса, но и через cli. В общем, абсолютные пути - меньше проблем

Автор: sTa1kEr 2.6.2007, 15:26
Цитата(WolfON @  2.6.2007,  13:09 Найти цитируемый пост)
Как я понял, у вас в коде есть карта классов, которая загружается в память и потом через нее все инклудит - хорошее решение, но мне пока, учитывая размеры этого проекта - такое не надо. 

Верно, но эта карта составляется автоматически (путем парсинга скриптов через http://ru2.php.net/manual/ru/function.token-get-all.php) первый раз и обновляется каждый раз когда не найден нужный класс, по этому прекрасно работает с проектами любых размеров.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)