Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Włączanie plików + autoloader
Forum PHP.pl > Forum > PHP > Pro
Stron: 1, 2, 3, 4
LBO
Ten exit() to tylko przykład, nie mający zastosowania w prawdziwych aplikacjach. Zastąp to sobie czymkolwiek zechcesz. Chodzi o sam fakt, że model obiektowy php dopuszcza do takiego czegoś.
NuLL
Cytat
Chodzi o sam fakt, że model obiektowy php dopuszcza do takiego czegoś.

Tzn do czego ? Co ma ten exit do OOP ?
athabus
Ja napisałem sobie prosty autoloader - szczerze mówiąc jest tak prosty, że aż boli, ale nie wiem czego jeszcze mógłbym oczekiwać od tej klasy

Składają się na niego 2 klasy.

->Klasa główna korzysta z mapy plików (serializowana tablica klasa=>plik). Użyłem singletona dlatego mapa jest odczytywana tylko raz na początku skryptu. Jeśli plik znajduje się w mapie to wiadomo - odczyt jest banalnie prosty. Jeśli natomiast plik jest nowy/zmieniła się jego lokalizacja itp, to autoloader sam go wyszukuje. W tym celu autoloader ma scieżki, w których ma szukać oraz dodatkowo głębokość katalogów na jakiem ja szukać czyli np.

$autoloader->addLocalization('sciezka', 5) oznacz ze ma szukac w katalogu 'sciezka' oraz rekurencyjnie do 5 katalogów niżej. Po pierwszym odnalezieniu pliku sciezka jest zapisywana do mapy i przy następnych wywołaniach przeszukiwania katalogów nie jest już potrzebne.

->klasa pomocnicza wykonuje operacje przeszukiwania katalogów itp.


Oczywiście autoloader może mieć wskazanych kilka lokalizacji, w których ma szukać plików itd.

Rozwiązania nigdy nie testowałem pod względem wydajności (piszę amatorsko więc nie zakładam, abym kiedyś popełił serwis o dużym obciążeniu), ale na logikę to rozwiązanie jest bardzo szybkie i nie powoduje zbędnych obciążeń.

Jedno ograniczenie, to sposób wyszukiwania -> każda klasa musi być w osobnym pliku.

To rozwiązania wydaje mi się najlepsze i na pewno jest bardzo wygodne.
bigZbig
Problem z mapą obrazków jest tylko taki, że za każdym razem trzeba ją całą załadować nawet jeśli żadnej nie użyjemy. Było już tutaj o tym, że można taką mapę podzielić na mniejsze zbiory, ale jest z tym troszkę zachodu. Lepszym rozwiązaniem jest sposób zastosowany w Zend Frameworku kiedy to nazwa klasy wskazuje na jej lokalizację w systemie. Wady tego podejścia są zasadniczo dwie. Po pierwsze dla każdej klasy trzeba wykonać parsowanie nazwy na ścieżkę, ale to pikuś, a po drugie jest to rozwiązanie mało elastyczne, które sprawdzi się tylko dla klas o odpowiedniej nazwie.
athabus
W zf jest to bardzo fajnie rozwiązane - tu się zgodzę w 100%, ale bynajmniej nie z powodu wydajności, a z powodu przejżystości. Nie wiem jak u was, ale dla mnie to jest bardzo intuicyjne rozwiązanie jeśli chodzi o użytkowanie. Już sie nie zastanawiam czy tworzyć obiekt zend_db_table czy zend_table_db. Zamierzam w przyszłości przerobić swój zestaw klas na taką strukturę.

Ale wracając do mapy, to szczerze wątpię czy może ona stanowić realne obciążenie dla aplikacji (oczywiście zakładając, że nie składa się ona z tysięcy plików). Obecnie piszę mały projekcik w których korzystam właśnie z ZF + +/- 40 klas (przy includowaniu klas ZF korzystam z mechanizmu dostarczonego przez ZF i nie zapisuje scieżek do plików). Mapa zwiera zatem 40 scieżek. Myślę, że odczyt plus deserializacja to jest chwila.
Pytanie co by było gdybym includował wszystkie klasy ZF poprzez mój mechanizm (czyli 383 pliki)... Na to pytanie nie potrafię jednak odpowiedzieć bo nie wiem jak kosztowna jest deserializacja.
Jarod
Przeczytałem cały wątek (trochę czasu to zajęło) i mam kilka pytań dla bardziej wtajemniczonych:

1. Zastanawiam się po co korzystać z autoloadera? Tylko po to, żeby nie trzeba było ręcznie dołączać plików z klasami?
Załóżmy, że mamy klasę A i klasę B. Klasa B dziedziczy po klasie A. Czy nie lepiej już w pliku z klasą B dodać require_once('klasa A')? Po co do tego wykorzystywać autoloader?

2. Rozumiem, że po wygenerowaniu mapy zapisujecie ją do pliku. Jak serializacja wpływa na wydajność? Czy warto jeszcze kodować jakimś algorytmem zserializowane wartości?

3. Dlaczego tak bardzo zależy Wam na uniwersalności. Przecież tworząc sobie framework czy coś innego, mamy pewien zarys jak to ma wyglądać. Jeśli będę korzystał ze swojego frameworka i jakiś innych bibliotek, to swoje klasy mogę ładować swoim autoloaderem a klasy innych bibliotek innym sposobem czyli najlepiej ręcznie (?)

4. Jaki macie sposób nazywania klas? Ja stosuję Nazwaprojektu_Nazwaklasy.class.php, np.
Cube_Mysql.class.php
Cube_Config.class.php
Cube_ConfigException.class.php
Cube_Exception.class.php

5. Czy ktoś z Was oprócz @squid'a testował jak ma się sprawa z wydajnością? Zastanawiam się czy warto go pisać, ale jeśli spróbuję to na pewno wykorzystam mapy i cache.
dr_bonzo
Cytat
Czy warto jeszcze kodować jakimś algorytmem zserializowane wartości?

Nie. Bo to nic ci nie da poza spowolnieniem dzialania przez wykonanie dodatkowych obliczen a rezultat bedzie taki sam.

Cytat
Jaki macie sposób nazywania klas? Ja stosuję Nazwaprojektu_Nazwaklasy.class.php, np.

I jak piszesz nowy projekt i wykorzystujesz ponownie stare klasy to musisz zmieniac ich nazwy? Bez sensu.

Cytat
Rozumiem, że po wygenerowaniu mapy zapisujecie ją do pliku.
Tak
Jarod
Cytat(dr_bonzo @ 18.01.2007, 01:06:33 ) *
I jak piszesz nowy projekt i wykorzystujesz ponownie stare klasy to musisz zmieniac ich nazwy? Bez sensu.

W sumie masz raje. Ale taki sposób przyjąłem pisząc sobie coś na styl frameworka. Zbór przydatnych klas, które będę wykorzystywał zawsze.
Cytat(dr_bonzo @ 18.01.2007, 01:06:33 ) *
Tak

To było stwierdzenie nie pytanie :]

Dalej zastanawiam się czy nie lepiej ładować ręcznie przez require_once()..
dr_bonzo
Cytat
Dalej zastanawiam się czy nie lepiej ładować ręcznie przez require_once()..

JEsli nie meczy cie ciagle pisanie: require( '/gdzie/ja/podzialem/ten/plik.php' ); to mozesz przy tym pozostac.
cadavre
Ja nazywam klasy nazwa_klasy.class.php - to chyba najlepsze rozwiązanie. Klasy narzędziowe trzymam w jednym folderze i ładuję je autoloaderem, który tylko sprawdza czy dany plik istnieje (j.w.) - jeśli tak - ładuje go; nie - błąd. Co do ładowania klas z modułami etc. chyba zastanowię się nad metodą, którą opisał athabus - brzmi ona ciekawie. winksmiley.jpg
DjKermit
Witam.
Ja wykombinowałem coś takiego:
Klasa:
  1. <?php
  2. class ClassLoader {
  3.  
  4. /**
  5.  * Class file name sufix
  6.  *
  7.  * @var string
  8.  */
  9. private $file_sufix = '.class.php';
  10.  
  11. /**
  12.  * Class map cache file name
  13.  *
  14.  * @var string
  15.  */
  16. private $cache_file_name = 'class_map_cache.ini.php';
  17.  
  18. /**
  19.  * Path to cache file
  20.  *
  21.  * @var string
  22.  */
  23. private $cache_file_path = CONFIG_PATH;
  24.  
  25. /**
  26.  * Root path of the project
  27.  *
  28.  * @var string
  29.  */
  30. private $base_path = BASE_PATH;
  31.  
  32. /**
  33.  * Path to class files & packages
  34.  *
  35.  * @var string
  36.  */
  37. private $class_path = CLASS_PATH;
  38.  
  39. /**
  40.  * All files paths from readed dir
  41.  *
  42.  * @var array
  43.  */
  44. private $readed_dir;
  45.  
  46. /**
  47.  * Class map content
  48.  *
  49.  * @var array
  50.  */
  51. private $class_map;
  52.  
  53. /**
  54.  * ClassLoader class instance
  55.  * 
  56.  * @var ClassLoader
  57.  */
  58. private static $instance;
  59.  
  60.  
  61.  
  62.  
  63. /**
  64.  * Constructor
  65.  * 
  66.  * @access private
  67.  */
  68. private function __construct() {
  69. $this->getClassMap();
  70. }
  71.  
  72. /**
  73.  * Gets singleton instance of ClassLoader class
  74.  *
  75.  * @return ClassLoader
  76.  * @access public
  77.  */
  78. public static function getInstance() {
  79. if (is_null(self::$instance)) {
  80. self::$instance = new self;
  81. }//end if
  82. return self::$instance;
  83. }
  84.  
  85.  
  86.  
  87.  
  88. /**
  89.  * Loads single class or all class package 
  90.  *
  91.  * @param string $class_name - name of class to load
  92.  * @access public
  93.  */
  94. public function load($class_name) {
  95. $class_name = str_replace('.', DIRECTORY_SEPARATOR, $class_name);
  96. if (strstr($class_name, '*')) { // load all class package
  97. $package_path = str_replace('*','', $class_name);
  98. $classes_arr = $this->getDirContents($this->class_path . $package_path);
  99. $this->loadClass($classes_arr);
  100. }else{ // load single class
  101. $path = $this->class_path . $class_name . $this->file_sufix;
  102. $this->loadClass(array($class_name => $path));
  103. }//end if
  104. }
  105.  
  106.  
  107. /**
  108.  * Serches for requested class in entire project and loads automaticly if found
  109.  *
  110.  * @param string $class_name
  111.  * @access public
  112.  */
  113. public function autoLoad($class_name) {
  114. if (array_key_exists($class_name, $this->class_map) && file_exists($this->class_map[$class_name])) {
  115. require_once($this->class_map[$class_name]);
  116. }else{
  117. $this->generateClassMap();
  118. if (array_key_exists($class_name, $this->class_map) && file_exists($this->class_map[$class_name])) {
  119. require_once($this->class_map[$class_name]);
  120. }else{
  121. throw new Exception('Class AutoLoad failed, file '. $class_name . $this->file_sufix .' not found');
  122. }//end if
  123. }//end if
  124. }
  125.  
  126.  
  127. /**
  128.  * Loads class
  129.  *
  130.  * @param array $path
  131.  * @access private
  132.  */
  133. private function loadClass($path) {
  134. foreach($path as $key => $val) {
  135. if (file_exists($val)) {
  136. require_once($val);
  137. }else{
  138. throw new Exception('Load class failed, file '. $key . $this->file_sufix .' not found');
  139. }//end if
  140. }//end foreach
  141. }
  142.  
  143.  
  144. /**
  145.  * Reads class map from cache
  146.  *
  147.  * @access private
  148.  */
  149. private function getClassMap() {
  150. if (file_exists($this->cache_file_path . $this->cache_file_name)) {
  151. $this->class_map = parse_ini_file($this->cache_file_path . $this->cache_file_name);
  152. }else{
  153. if (!is_dir($this->cache_file_path)) mkdir($this->cache_file_path);
  154. }//end if
  155. if (empty($this->class_map)) $this->generateClassMap();
  156. }
  157.  
  158.  
  159. /**
  160.  * Generates new class map
  161.  *
  162.  * @access private
  163.  */
  164. private function generateClassMap() {
  165. $this->getDirContents($this->base_path);
  166. $this->class_map = $this->readed_dir;
  167. $this->saveClassMap();
  168. }
  169.  
  170.  
  171.  
  172. /**
  173.  * Saves class map into a cache file
  174.  *
  175.  * @access private
  176.  */
  177. private function saveClassMap() {
  178. $str = ";<?php die('Configuration file, all data confidential'); ?>rnrn";
  179. foreach($this->class_map as $key => $val) {
  180. $str .= $key .' = '. $val . "rn";
  181. }//end foreach
  182. file_put_contents($this->cache_file_path . $this->cache_file_name, $str);
  183. }
  184.  
  185.  
  186. /**
  187.  * Gets specified directory contents recursively
  188.  *
  189.  * @param string $path
  190.  * @return array
  191.  * @access private
  192.  */
  193. private function getDirContents($path) {
  194. $this->readed_dir = array();
  195. $this->readDir($path);
  196. return $this->readed_dir;
  197. }
  198.  
  199.  
  200. /**
  201.  * Reads specified directory & if file name contains "class" substring adds path to $readed_dir array
  202.  *
  203.  * @param string $path
  204.  * @access private
  205.  */
  206. private function readDir($path) {
  207. if ($handle = opendir($path)) {
  208. while (($file = readdir($handle)) !== false) { 
  209. if (is_file($path . $file) && strstr($file, 'class') && !strstr($file, 'ClassLoader')) {
  210. $this->readed_dir[str_replace('.class.php', '', $file)] = $path . $file;
  211. }else if (is_dir($path . $file) && $file != '.' && $file != '..') {
  212. $this->readDir($path . $file . DIRECTORY_SEPARATOR);
  213. }//end if
  214. }//end while
  215. closedir($handle);
  216. }//end if
  217. }
  218.  
  219. }
  220.  
  221.  
  222.  
  223. /**
  224.  * Class auto loader
  225.  * 
  226.  * @param string $class_name
  227.  */
  228. function __autoload($class_name) {
  229. $cloader = ClassLoader::getInstance();
  230. $cloader->autoLoad($class_name);
  231. }
  232.  
  233. ?>

config.php
  1. <?php
  2. /** Base path */
  3. define('BASE_PATH', dirname(__FILE__) .'/');
  4. /** Path to config files */
  5. define('CONFIG_PATH', BASE_PATH . 'config/');
  6. /** Path to class and interface files */
  7. define('CLASS_PATH', BASE_PATH .'classes/');
  8. ?>


możliwość ładowania klas trochę IMO przyjemniej niż require_once i klepanie ścieżki
możliwość ładowania paczek klas
autoloader
mapa klas keszowana w pliku ini
gdy w keszu nie ma żądanej klasy jest odświeżany i ponowna próba załadowania klasy, jeśli brak - wyjątek

Przykładowe wywołanie:
  1. <?php
  2. require_once('config.php');
  3. require_once(CLASS_PATH .'core/ClassLoader.class.php');
  4. $c_loader = ClassLoader::getInstance();
  5. $c_loader->load('core.JakasKlasa'); // ładuje wskazaną klasę w "paczce" core
  6. $c_loader->load('core.*'); // ładuje całą "paczkę" core
  7. $c_loader->autoLoad('Smarty'); // odszukuje w całym projekcie wskazanej klasy i ładuje jeśli znajdzie
  8. ?>

metoda "load" ładduje klasy tylko ze zdefiniowanego w klasie ClassLoader katalogu i podżędnych
metoda "autoLoad" odszukuje żądaną klasę w całym projekcie

Ciekawi mnie co mądrzejsi będą mieli do powiedzenia.
Jeśli będzie się to do czegoś nadawało to można wrzucić do gotowych skryptów.

PS
Różnic przy tej klasie w porównaniu do ręcznego klepania require nie zauważyłem (ładowanie ok 20 klas).
//edit, no dobra tu troche przesadziłem, różnica jest
PS2
Jedyne wymaganie to nazwa pliku klasy powinna kończyć się ".class.php"

I co, nikt nic ?
Czyżby to było tak beznadziejne że nie warto się wypowiadać ?
hwao
Bardzo ładna klasa, mi się podoba - jakbym stosował autoload to bym mógł ją spokojnie użyć.

Paser ini jest szybszy od serialize to też dodatkowy plus.

  1. <?php
  2. if (strstr($class_name, '*')) { // load all class package
  3. $package_path = str_replace('*','', $class_name);
  4. $classes_arr = $this->getDirContents($this->class_path . $package_path);
  5. ?>


Czy w tym momencie nie było by lepiej użyć glob" title="Zobacz w manualu php" target="_manual() ?
DjKermit
Dzięki bardzo.
Cytat(hwao @ 23.01.2007, 18:14:47 ) *
Czy w tym momencie nie było by lepiej użyć glob" title="Zobacz w manualu php" target="_manual() ?

Gdybym ładował pliki tylko ze wskazanego folderu to napewno tak, natomiast moim zamiarem było załadowanie plików/klas ze wskazanego folderu i wszystkich w nim zagłębionych.
Turgon
Hmm... Klasa ciekawa i sporo wyjaśnia smile.gif Dzięki. ale ja mam odmienny problem. Jak się ma sprawa z autoloadem interfejsów? Jak nie ma jest uruchamiana funkcja autoload?
bigZbig
Ja nazywam swoje klasy zgodnie z konwencją przyjętą w Zend Frameworku. Oczywiście zamiast prefixu Zend daje swój. DjKermit - narzucając obowiązek nadawania klasom sufixu class.php ograniczasz swojego autoloadera jedynie do klas swojego autorstwa i klas, które zawarte są w plikach o nazwach stosujących tę konwencję. Gdzieś na początku tego tematu jest przykład bardziej uniwersalnego skanera.

Dlaczego serializujecie swoje mapy albo tez parsujecie do postaci pliku ini? Nie lepiej zapisywać je od razu jako tablicę i na dzień dobry tę tablicę includować?
Turgon
bigZbig: Podobno szybsze jest winksmiley.jpg Ale zastanowię się nad tabliczką smile.gif .
DjKermit
Cytat(bigZbig @ 24.01.2007, 16:19:57 ) *
DjKermit - narzucając obowiązek nadawania klasom sufixu class.php ograniczasz swojego autoloadera jedynie do klas swojego autorstwa i klas, które zawarte są w plikach o nazwach stosujących tę konwencję. Gdzieś na początku tego tematu jest przykład bardziej uniwersalnego skanera.


No tak, ale nie piszę publicznego FW tylko pewien systemik, którego częścią jest ten loader, i nawet jeśli korzystam z cudzych klas to przerabiam je do swoich konwencji.

Cytat(bigZbig @ 24.01.2007, 16:19:57 ) *
Dlaczego serializujecie swoje mapy albo tez parsujecie do postaci pliku ini? Nie lepiej zapisywać je od razu jako tablicę i na dzień dobry tę tablicę includować?


IMO gryzie się to z ideą OOP bo inkludująć tablicę masz ją w globalu a nie w klasie czy metodzie klasy, pozatym podobno ini jest najszybsze, no i jakoś tak czytelniej.
bigZbig
Jak init może być szybsze od tablicy skoro parsując plik init zmieniasz go właśnie w tablicę. Tablicę możesz wczytywać do klasy poprzez pobranie zawartości pliku, a nie poprzez includa. Możesz też wygenerować mapę w postaci instancji klasy implementującego interfejs ArrayAccess i wtedy includujesz gotowy obiekt. Myślę, że powinno zadziałać choć nie testowałem.
LBO
hmmm, parsowanie to parsowanie... i "na oko" parsowanie kodu php jest bardziej czasochłonne od pliku ini, który jest prostszy
Turgon
Ja zapisuje zserializowaną tablicę i działa sprawnie.
Strzałek
O autoloadzie oraz przykładowa klasa generująca mapę opublikowałem post na moim blogu: http://strzalek.net/blog/8/autoload-automa...-ladowanie-klas
Sh4dow
Mnie zastanawia ile samo odwolanie sie do funkcji autoload zajmuje czasu, Bo to jak zbudujesz ta funkcje tak bedzie ona sprawna.
Jesli autoload ma szukac po wszystkich katalogach to tak, bedzie to wolne. Ale jesli podzielic pliki na grupy, uzyc odpowiednich schematow w budowaniu nazw klas to powinno byc to sprawne.
Chyba ciekawszym sposobem to kozystanie z magazynu obiektow polaczonego z autoloaderem. Odwolujesz sie do magazynu po obiekt jakiegos modelu, ktory ma miec zawsze jedna instancje aby powiedzmy nie powielac polaczen z baza danych. Magazyn sprawdza czy istnieje juz taka instancja, jesli nie sprawsza czy istnieje taka klasa, jesli nie szuka w odpowiednim katalogu pliku o odpowiedniej nazwie, po czym ładuje plik, tworzy instancje i ja zwraca.
Czy to jest wygodne czy nie to juz inna sprawa ale mozna takie rozwiazanie zmodyfikowac i poszerzyc o inne funkcjonalnosci dostosowane do swoich potrzeb.
Jarod
Cytat(Sh4dow @ 1.03.2007, 12:58:38 ) *
Mnie zastanawia ile samo odwolanie sie do funkcji autoload zajmuje czasu,

Przyłączam się do pytania. Dzisiaj zauważyłem (i nie wiem dlaczego), że zaincludowanie klasy w głównym pliku/kontrolerze jest prawie 2x szybsze niż zainkludowanie w klasie statycznej. To tak przy okazji..
athabus
Wydaje mi się, że troszeczkę dochodzimy do granic abstrakcji :-)

90% wykonania funkcji autload to includowanie pliku i tego się nie ominie, niezależnie od tego czy includować będziemy za pomocą autoload czy ręcznie. Samo wywołanie tej funkcji zapewne "kosztuje" tyle co wywołanie każdej innej funkcji. Jest tak jak mówi Sh4adow - wszystko zależy od tego jak funkcja będzie napisana.

Ja osobiście robiłem testy na ZF - dopisałem do niego prosty autoloader dla własnych klas z mapą w pliku. Cóż mogę powiedzieć - funkcja zachowała się jak funkcja ;-) Wywołanie było dość kosztowne - ok 10-20% czasu wykonania skryptu jeśli dobrze pamiętam, ale to było spowodowane nie tyle samą funkcją co includowaniem samych plików. Wyników dokładnie nie pamiętam i nie mam ich już zapisanych, ale mój wniosek był mniej więcej taki:
- funkcja __autoload to dobre rozwiązanie i nie przynosi specjalnie dużych kosztów. Problem jaki się może pojawić to złe napisanie tej funkcji - np. brak mapy (czy jakiegoś jasnego układu) i każdorazowe przeszukiwanie folderów.

Ogólnie pytanie nie brzmi więc czy pisać autoloader tylko jak go napisać ;-) Ja z mojej metody jestem zadowolony. Dalsza optymalizacja, choć pewnie jest możliwa przy zwykłym projekcie nie ma większego sensu. Po prostu wzrost wydajności samego mechanizmu nawet o 100% nic konkretnego mi nie da, gdyż są to marginalne wartości.
grzegorzr
uuu, temat trochę się zakurzył postanowiłem go rozruszać poprzez YAML:
YAML = mapa
wymyśliłem sobie że w __autoloadzie mamy (fragmenty):
  1. <?php
  2. function __autoload($sClassName) {
  3. $Autoload = Autoload::getInstance();
  4. //podajemy sciezke do pliku .yml
  5. $Autoload->setMapFile($map_file);
  6. // importujemy mape z formatu YAML do przyjaznej tablicy
  7. $Autoload->importMapFile();
  8. [...]
  9. // w strategicznym miejscu pilnujemy swiezosc mapy
  10. // jako argument podajemy czas unixowy $t
  11. // jesli wartosc $t>$tobecny-$t_ostaniaAktualizacja->akutalizuj()
  12. if(!$Autoload->isFileMapFresh(100))
  13. {
  14. $Autoload->addDir($ib_dir);
  15. $Autoload->addDir($data_dir);
  16.  // ta metoda najpierw przeszukuje wskazane katalogi
  17.  // wybiera pliki z rozszerzeniem .php
  18.  // zapisuje do tablicy['map'][nazwaklasy] = sciezka pelna do klasy
  19.  // nastepnie robimy export do pliku .yml
  20. $Autoload->setClassMap();
  21. }
  22. [...]
  23. // require teraz to juz przyjemnosc
  24. if (!require_once $Autoload->getClassPath($sClassName);)
  25. {
  26. //jakis blad
  27. };
  28. }
  29. ?>

w YAML pieknie to wyglada
  1. map:
  2. [nazwaklasy]:
  3. path: [sciezka do klasy]
Ludvik
Chce Ci się pisać te mapy w YAML? Bo nie widzę sensu, żeby generować je automatycznie - parser YAML będzie wolniejszy od wczytania zserializowanej tablicy... A to, że ładniej wygląda, to drugorzędna sprawa w przypadku autoloadera. Z resztą, czy zaimportowanie folderów do przeszukania jest brzydkie? Moim zdaniem nie...
deirathe
  1. <?php
  2. function __autoload($classname){
  3. $path = "library".DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR.str_replace("_", DIRECTORY_SEPARATOR , $classname).".class.php";
  4. if(!file_exists($path)){
  5. exit("Brak pliku: ".$path);
  6. }
  7. require_once($path);
  8. if(!class_exists($classname)){
  9. exit("Brak klasy: ".$classname);
  10. }
  11. }
  12. ?>

A ja zawsze wykorzystywałem to:P kod nie jest długi, co o tym myślicie?
athabus
Ta funkcja wymaga aby wszystkie klasy były w jednym katalogu. Wydaje mi się, że w dyskusji chodzi raczej o rozwiązanie, które działa dla całej aplikacji. Gdy masz np. 20 katalogów z różnymi klasami to raczej nie ma sensu wykonywać tylu akcji plikowych i szukać w każdym katalogu danej klasy, także w średniej aplikacji to się raczej by nie sprawdziło.
Turgon
athabus: W takim przypadku mapy się wydają być jedynym sensownym wyjściem, z punktu estetyki i także wydajności.
athabus
wiem i sam właśnie map używam biggrin.gif
kaniagandzowski
W klasie ClassLoader autora DjKermit brakuje warunku, który by nie przeglądał katalogów powstałych przez stosowanie programów do kontroli wersji np subversion.
Ponieważ gdy używa się kontroli wersji w każdym katalogu tworzy katalog .svn i w nich pliki.

I gdy próbuje się ładować wyskakuje błąd iż jest drugi raz ładowany plik.
Fatal error: Cannot redeclare class nazwaKlasy

Rozwiązałem poprzez dodanie warunku nie pozwalający przeglądać katalogi .svn lecz nie jestem autorem i nie poświęcałem uwagi gdzie powinno w sumie być jakiś dany warunek, który by nie ładował plików programu subversion.

  1. <?php
  2. private function readDir($path) {
  3. if ($handle = opendir($path)) {
  4. while (($file = readdir($handle)) !== false) { 
  5.  
  6. if (is_file($path . $file) && strstr($file, 'class') && !strstr($file, 'ClassLoader')) {
  7. $this->readed_dir[str_replace('.class.php', '', $file)] = $path . $file;
  8. }else if (is_dir($path . $file) && $file != '.' && $file != '..' && $file != '.svn') {
  9. $this->readDir($path . $file . DIRECTORY_SEPARATOR);
  10. }//end if
  11. }//end while
  12. closedir($handle);
  13. }//end if
  14. }
  15. ?>


Nie wiem czy dobrze robie pisząc w tym temacie, zgłaszając taki błąd!!!.
Sagnitor
Być może uznacie mnie za archeologa, ale temat był wg. mnie bardzo ciekawy, a przez 4 lata od ostatniego postu mogło się wiele rzeczy zmienić.
Czytając tą dyskusję doszedłem do dwóch wniosków.

Są dwie najbardziej 'wydajne' metody autoloadu plików w projekcie:
- mapper generujący tablicę ze skojarzonymi nazwami klas wraz z ich ścieżkami + autoloader bazujący na tych mapach,
- konwencja nazewnictwa PSR-0, polegająca na używaniu przestrzeni nazw w taki sposób, aby wskazywały na plik klasy.

Oba sposoby posiadają zalety i wady. Pierwszy wykorzystujący mapy jest bardziej elastyczny w stosunku do drugiego. Właściwie niezależnie jaką strukturę katalogów przyjmiemy, mapper przeskanuje nam ją i zwróci odpowiedni plik. Głównym problemem jest wielkość wczytywanej mapy, która niekoniecznie musi być wykorzystana w całości (jednak z każdym żądaniem musi być wczytana do zmiennej klasy). Sugestiami co do tego problemu jest rozbicie mapy na mniejsze 'podkategorie'.

Drugi sposób wydaje się bardziej wydajny w większy aplikacjach, gdzie ilość plików przekracza kilka setek. W tym przypadku, aby załadować klasę nie musimy wczytywać nic z zewnątrz co mogło by obciążyć aplikację. Posługujemy się tutaj przestrzenią nazw. Obiekt w swojej nazwie zawiera ściężke do pliku, w którym znajduje się klasa. Wymogiem jest tutaj odpowiednie nazewnictwo oraz zorganizowana struktura katalogów.

Od tamtej pory wiele rzeczy mogło się zmienić, dlatego zachęcam do dalszej dyskusji. Mnie głównie nurtuje rozwiązanie sposobu pierwszego, aby był on wydajny nawet przy większych systemach.

Pozdrawiam
by_ikar
Cytat
Oba sposoby posiadają zalety i wady. Pierwszy wykorzystujący mapy jest bardziej elastyczny w stosunku do drugiego. Właściwie niezależnie jaką strukturę katalogów przyjmiemy, mapper przeskanuje nam ją i zwróci odpowiedni plik. Głównym problemem jest wielkość wczytywanej mapy, która niekoniecznie musi być wykorzystana w całości (jednak z każdym żądaniem musi być wczytana do zmiennej klasy). Sugestiami co do tego problemu jest rozbicie mapy na mniejsze 'podkategorie'.


Tak, tyle że w tym temacie, ktoś słusznie umieścił kod, przynajmniej moim zdaniem, żeby maper mapował jedynie pliki z odpowiednim zakończeniem, tj: *.class.php lub *.interface.php dzięki czemu pomijamy wiele plików które są ładowane już z poziomu samej biblioteki. U siebie coś takiego stosuje i serializowana tablica nie przekracza 10kb. A czy to jest dużo? Wątpię, ustawienia, czy i18n mają znacznie więcej. Większość bibliotek ma jakieś swoje loadery, lub też w jakiś swój sposób ładują inne klasy i te klasy są nie potrzebne nam w zasadzie bezpośrednio w mapie naszych klas, potrzebna nam jest klasa główna która pozostałe załaduje, jeżeli będzie taka potrzeba. I tak klasy które chcemy załadować poprzez loader dajemy z końcówką .class.php a tych których nie chcemy załadować dajemy bez tej końcówki, samo .php i nasza mapa plików znacznie się zmniejsza wink.gif
Sagnitor
Nie zaznaczyłem tego w swojej wypowiedzi, ale właśnie taki Autoloader miałem na myśli (wykorzystujący filtry suffixów). Zastanawiam się jednak, gdzie jest granica wielkości takiego wygenerowanego pliku i od jakiego rozmiaru zaczyna to wpływać na wydajność.

Co do innych propozycji, chodzi mi po głowie wyznaczenie Loaderowi tzw. "Core", czyli klas 'niskopoziomowych' systemu, które są zawsze ładowane przy każdym żądaniu. Klasy 'Core' byłyby przeparsowane do bytecodu, co przyspieszało by działanie aplikacji. Nie wiem jak dokładnie działają takie systemy cache (APC, MemCache). I tak końcowo by był:

Sparsowany core + autoload klas z mapy.

Pozdrawiam
Crozin
Zacznijmy od tego, że w niewielkich projektach wydajność autoloadera nie ma znaczenia. Albo inaczej, jest bardzo mało prawdopodobne, aby to właśnie autoloader był przyczyną problemów z wydajnością.

W przypadku tych większych aplikacji standardem są akceleratory, które umożliwiają jednokrotne skompilowanie kodu do bytecodu i jego późniejsze wykorzystanie. Trzeba tutaj jednak zaznaczyć, że domyślnie większość (wszystkie?) akceleratory mimo wszystko każdorazowo sprawdzają czy aby przypadkiem źródło pliku się nie zmieniło. Operacje z wykorzystaniem dysku, nawet tak trywialne jak sprawdzenie daty modyfikacji pliku, są generalnie powolne co w przypadku gdy każdorazowo mamy do sprawdzenia setki plików może być problemem. Tutaj rozwiązania są dwa:
1. Wyłączyć takie sprawdzanie w konfiguracji akceleratora, np. dla APC będzie to dyrektywa apc.stat.
2. Stworzyć cache'a plików, tj. zebrać wszystkie pliki i wrzucić ich zawartość do jednego pliku. Dzięki temu ograniczymy się do pojedynczego sprawdzenia daty modyfikacji pliku.
by_ikar
W tym temacie jeden z użytkowników zamieścił właśnie takiego loadera, który sprawdzał suffixy, ale co to za problem samemu napisać proste wyrażenie "/([\w]+).(class|interface).php$/is" przy każdym dodanym pliku do tablicy wstawić preg_match i wyrażenie które podałem i wszystko. Kluczami są nazwy klas, wartością jest ścieżka bezwzględna. U mnie loader pierwsze przeładowanie sam tworzy cache w miejscu w którym znajduje się plik loadera. Kolejnymi sprawdzaniami zajmuje się pasywne cache które najzwyczajniej ponownie mapuje podane katalogi i tworzy cache.

Wydaje mi się że jest to najbardziej optymalne rozwiązanie, bo prawdę mówiąc loadery z przestrzenią nazw, również przeszukują tablice którą wcześniej mu się poda. No chyba żeby zrobić to na sztywno, gdzie przestrzeń nazw jest identyczna jak struktura katalogów. Tyle że mi takie rozwiązanie wcale nie przypadło do gustu. jestem w wielu przypadkach uzależniony od tego jak sobie ktoś zaprojektuje tą przestrzeń i robi się niekiedy burdel w katalogach, wtedy trzeba ręcznie poprawiać, a wole kiedy loader wie gdzie ma szukać klas, jak nie znajdzie znaczy że nie ma i syfu jako takiego nie ma. Oczywiście każdy ma inne przyzwyczajenia i upodobania.
Sagnitor
@Crozin

Twój sposób numer dwa byłby bardzo dobry (sam tak kombinowałem), lecz jeszcze zanim wprowadzono namespace'y. Teraz próbując robić taki 'cache' (sklejke klas), jeżeli w pliku klasy jest oznaczona przestrzeń, a w kolejnej klasie tej przestrzeni nie ma to dojdzie do przypisania tej klasy do tego namespace'a. Zresztą opisywał już to Zyx w swoim wpisie o autoloaderze.

Zastanawiam się jednak czy jest narzędzie, które jest zdolne przetłumaczyć pliki PHP na bytecode generując plik z tym bytecodem. Póki co najwydajniejszym rozwiązaniem pozostaje autoloader oparty o mapy.
Crozin
@Sagnitor: Nie widzę większych problemów:
  1. <?php
  2.  
  3. namespace Project\NS1 {
  4. class NS1C1 { }
  5. class NS1C2 { }
  6. }
  7.  
  8. namespace Project\NS2 {
  9. use Project\NS1\NS1C1;
  10. use Project\NS1\NS1C2;
  11.  
  12. class NS2C1 { }
  13. class NS2C2 { }
  14. }
  15.  
  16. namespace {
  17. class GC1 { }
  18. class GC2 { }
  19. }
  20.  
  21. namespace Project\Test {
  22. use Project\NS1\NS1C1;
  23. use Project\NS1\NS1C2;
  24. use Project\NS2\NS2C1 as AbcNS2C1;
  25. use GC2;
  26.  
  27. class Test {
  28. public function __construct(NS1C1 $a, NS1C2 $b, AbcNS2C1 $c, GC2 $d) {
  29. var_dump($a, $b, $c, $d);
  30. }
  31. }
  32. }
  33.  
  34. namespace XYZ {
  35. use Project\Test\Test;
  36.  
  37. new Test(new \Project\NS1\NS1C1(), new \Project\NS1\NS1C2(), new \Project\NS2\NS2C1(), new \GC2());
  38. }
Taki mechanizm jest nawet wykorzystywany w Symfony2.

Cytat
Zastanawiam się jednak czy jest narzędzie, które jest zdolne przetłumaczyć pliki PHP na bytecode generując plik z tym bytecodem.
A w jakim celu chciałbyś ten bytecode zapisywać na dysku?
Sagnitor
Problem jest kiedy niektórzy piszą w ten sposób:

  1. namespace Project\NS1;
  2.  
  3. class Foo
  4. {
  5.  
  6. }


W sumie do końca kwestii bytecode'u nie przemyślałem. Podsumowując najlepsze rozwiązanie: sklejka + APC wink.gif
Zyx
Sagnitor -> mapa nie wyklucza stosowania konwencji PSR-0. Konwencja określa tylko, jak rozmieszczać klasy w plikach nazwanych w określony sposób i nie mówi nic o tym, że musi to być tłumaczone dynamicznie. Natomiast co do wydajności:

* Głównym parametrem jest obsługiwany ruch, a dopiero później wielkość serwisu. Nawet stosunkowo mały projekt przy dużym ruchu może znacząco zyskać, jeśli zmienimy strategię ładowania klas.
* Jest zauważalna różnica w czasie między korzystaniem z mapy klas, a dynamicznym tłumaczeniem. W tym drugim przypadku nawet w niezbyt dużych projektach tłumaczenie nazw klas na ścieżki potrafi zająć nawet do 40% łącznego czasu wykonania. Przynajmniej ja miałem takie wyniki przy zabawach profilerem, i mówię tu o naprawdę kompaktowej implementacji ładowarki. Jeśli mamy do czynienia z takimi kobyłami, jak Zend_Loader, to aż strach je mierzyć smile.gif.
* Wielkość mapy ma znaczenie, jeśli w każdym żądaniu wczytujesz ją z pliku zapisanego na dysku. Możemy to wyeliminować, używając pamięci współdzielonej.
* Gdy mapa jest już wczytana do pamięci, zapisana jest w postaci tablicy z haszowaniem. Takie tablice charakteryzują się bardzo dobrym średnim czasem dostępu rzędu O(h), gdzie "h" to średnia głębokość pojedynczego kubełka. W praktyce h jest równe 1 lub nieznacznie tylko większe od jedynki.
* Mapa klas nie podlega zbyt częstym zmianom, a jeśli już, to przebudowujemy ją w całości na nasze wyraźne żądanie. Wtedy możemy zastosować w tablicy haszowanie perfekcyjne charakteryzujące się stałym czasem dostępu, niezależnym od wielkości tablicy. Teraz klas możemy mieć i milion; poza zajęciem kilkudziesięciu megabajtów RAM-u wydajność nie ulegnie zmianie.
* Skąd wziąć tablicę z haszowaniem perfekcyjnym w PHP? Ano np. z rozszerzenia chdb, które niedawno odkopałem w PECL-u.

Nawiasem mówiąc, sklejarkę plików o której wspominasz, zaimplementowałem kilka dni temu i jest już dostępna w repozytorium Open Power Autoloadera. Działa dobrze i po prostu zamienia zapis namespace XYZ; na namespace XYZ{ ... }, poprawnie radząc sobie również z plikami, które nie korzystają z przestrzeni nazw. Ale takie sklejanie ma nieco inny cel:

- w praktycznie każdej aplikacji możemy wyróżnić zbiór klas, który musimy zawsze załadować bez względu na to, co dane żądanie HTTP robi.
- dla takiego zbioru klas możemy w ogóle pominąć automatyczne ładowanie i wczytywać je ręcznie.
- jeśli korzystamy z APC, zapisywanie kodu bajtowego na dysku jest nam niepotrzebne, gdyż jest on trzymany w pamięci RAM.
- ALE: nawet jeśli mamy kod bajtowy zapisany już w RAM-ie, APC domyślnie wykonuje na każdym pliku operację stat, aby sprawdzić czy w międzyczasie plik nie uległ zmianom. Czyli jeśli mamy nasze klasy główne porozbijane na 100 plików, wciąż PHP wciąż wykonuje 100 operacji dyskowych mimo, iż ma już wszystko w pamięci. Jeśli skleimy je w jeden plik, zrobi się z tego jedna operacja.
- Oczywiście apc.stat pozwala to sprawdzanie wyłączyć, ale wtedy przy najmniejszej zmianie kodu (nawet głupiej poprawce szablonu HTML) będziemy musieli zrestartować serwer/PHP lub czekać, aż się cache zdezaktualizuje.
- Jeśli nie mamy APC, zysk ze sklejenia też jest i też wiąże się ze zmniejszeniem liczby operacji dyskowych (konkretniej otwieranie i zamykanie plików oraz wiążące się z tym przeszukiwanie ścieżek na dysku).
wookieb
A to nie wystarczy już http://pl2.php.net/manual/pl/function.apc-store.php do przechowywania takiej "mapy" ?
Zyx
Jak najbardziej wystarczy, tylko trzeba pamiętać o paru bolączkach APC:

- pamięć APC nie jest współdzielona między procesami,
- w szczególności nie możemy ze skryptu konsolowego wysłać polecenia wyczyszczenia cache i załadowania nowej mapy,
- aby zrobić coś takiego, musimy zrestartować procesy FastCGI lub Apache'a, ew. dodać do autoloadera jakiś przełącznik, który przeładuje mapę "przy najbliższym żądaniu", co może jednak wygenerować efekt wyścigu szczurów przy dużym obciążeniu,
- APC nie posiada tablicy z perfekcyjnym haszowaniem.
Hellz
Dlaczego nie trzymać mapy autoloadera w memcached, bez problemu jest współdzielony przez wszystko na serwerze, ostatnio wykorzystywałem go nawet do pisania wielowątkowej aplikacji w PHP, jako warstwy komunikacji między procesami. Piekielnie wydajny, dobrze działa z PHP, widziałem nawet wirtualki, które go obsługują.

Przebudowanie mapy można zrzucić na na skrypt konsolowy odpalany w cronie np. co minutę, bądź w ogóle w momencie zmian na serwerze jako kolejne zadanie phinga.

Osobiście byłbym daleki od sklejania plików, przy odpowiedniej konfiguracji serwera najczęściej używane powinny być i tak trzymane w ramie.
Zyx
Bo jest kilka razy wolniejszy od APC*, a sama mapa ma na tyle mały rozmiar, że każdy serwer spokojnie może ją trzymać we własnym RAM-ie. Ponadto zauważ, że jeśli każdy serwer może być aktualizowany niezależnie, to ja bym nie chciał nawet, by mapa klas była współdzielona, bowiem potencjalnie straszne rzeczy mogłyby się wtedy wydarzyć.

W ogóle dziwię się trochę, że tak dużo programistów próbuje robić w temacie systemów cache itd. jakieś wojny religijne na zasadzie "a to jest lepsze niż tamto". Bzdura. Każdy system, tak samo jak każdy algorytm ładowania klas ma swoje określone przeznaczenie. Jedne są optymalizowane pod jeden scenariusz, inne pod inny. Ważne jest, by znać właściwości i umieć to wszystko podobierać.

* - co nie powinno dziwić, jeśli weźmiemy pod uwagę, że bezpośredni odczyt z RAM-u zawsze będzie szybszy niż komunikacja przez warstwę sieciową systemu operacyjnego.
Hellz
Cytat
co nie powinno dziwić, jeśli weźmiemy pod uwagę, że bezpośredni odczyt z RAM-u zawsze będzie szybszy niż komunikacja przez warstwę sieciową systemu operacyjnego.

Jasna sprawa, możesz zrobić jeszcze jeden test dodając połączenie z memcache nie przez host i port, a za pomocą socketa? Myślę, że można jeszcze trochę pokręcić gałkami, po weekendzie postaram się znaleźć chwilę i powtórzyć twoje badanie z optymalizacją memcache, wydaje mi się, że różnica się zmniejszy do max 2/3 razy, co przy odpalaniu 1 raz na request usera robi się IMHO w zasadzie pomijalne (moja wersja jest taka, że cała mapa jest przechowywana pod jedym kluczem i na początku wczytywana przez aplikację, później wszystko odbywa się już w samym PHP).

Cytat
bowiem potencjalnie straszne rzeczy mogłyby się wtedy wydarzyć.

Cytat
- pamięć APC nie jest współdzielona między procesami,

Chyba wybór mniejszego zła. Jeżeli dobrze Cię rozumiem, APC ma swoją własną wersję np. dla każdej instancji Apache i w momencie wprowadzania zmian konieczny jest restart? Mając na serwerze np. Redmine na mod_passanger chciałbym mocno tego unikać.
Zyx
Wykorzystanie gniazd uniksowych niewiele Ci da. Tracisz wtedy główną zaletę Memcached, a zyskujesz? W zasadzie nic, ponieważ zapytanie i odpowiedź wciąż musi przejść przez jądro systemu operacyjnego, gdzie są kilka razy kopiowane z jednego bufora do drugiego. Samo przełączanie się w tryb jądra również jest kosztowne. Żadne gniazda, żadne inne mechanizmy komunikacji międzyprocesowej nie są w stanie dorównać pamięci dzielonej.

Domyślnie w PHP każde żądanie traktowane jest jako zupełnie niezależny byt. Pojedynczy proces FastCGI lub moduł Apache'a może jednak przetwarzać wiele żądań jako osobne wątki. APC zezwala jedynie na wymianę informacji między tymi wątkami (kod bajtowy + dane), nie przekracza natomiast granicy procesu. Z tego powodu:
* W trybie modułu Apache'a dane są współdzielone jedynie między wątkami tego samego procesu Apache'a,
* W trybie FastCGI dane są współdzielone między wątkami tego samego procesu FastCGI.

Przy czym w FastCGI najczęściej uruchomionych jest równolegle kilka/kilkanaście procesów, które na dodatek mogą być co X żądań restartowane w celu eliminacji potencjalnych wycieków pamięci. APC nie pozwala ani na współdzielenie danych między tymi procesami (każdy proces jest odizolowany od drugiego), ani na manipulację nimi np. z poziomu skryptu konsolowego, ponieważ to jest jeszcze jeden proces. Podobnie, z tego co wiem, działa XCache. Z tego powodu jedynym 100%-pewnym sposobem wyczyszczenia cache jest tutaj restart Apache'a lub wszystkich procesów FastCGI.

Pamięć współdzielona to mechanizm pozwalający na zmapowanie pewnego fragmentu pamięci na przestrzeń adresową więcej niż jednego procesu. Oba te procesy widzą ten fragment jako "swój" i mają do niego pełne prawa odczytu oraz zapisu. Najczęściej współdzielony fragment reprezentuje się jako plik na dysku, który działa trochę jak pamięć wymiany - w momencie pierwszego użycia poszczególne jego bloki są importowane do pamięci i odpowiednio oznaczane. Gdy blok znajdzie się w pamięci, zasadniczo nie ma różnic wydajnościowych między dostępem do prywatnej pamięci procesu, a pamięcią współdzieloną. Wszystko wygląda fajnie, ale jest tutaj jeden problem, który tłumaczy dlaczego typowe akceleratory go nie wykorzystują: programista musi samodzielnie zaimplementować całe i w dodatku współbieżne zarządzanie takim obszarem pamięci, co nie jest zadaniem trywialnym. Jedynym znanym mi rozszerzeniem PHP, które wykorzystuje systemowy mechanizm pamięci dzielonej, jest wspomniany już chdb, który radzi sobie z powyższym problemem bardzo prosto: pamięć jest tylko do odczytu -> nie potrzeba żadnej synchronizacji -> nie trzeba implementować zarządzania taką pamięcią. Zawartość takiego cache można przebudować jedynie hurtem.

OK, ale chyba tyle na temat mechanizmów cache, ponieważ trochę zboczyliśmy z tematu. Mam nadzieję jednak, że rozwiałem tym wpisem wszelkie wątpliwości, dlaczego:
* nie powinno się stosować Memcached do obsługi map klas,
* dlaczego APC działa tak, jak działa.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.