Drukowana wersja tematu

Kliknij tu, aby zobaczyć temat w orginalnym formacie

Forum PHP.pl _ Pro _ Włączanie plików + autoloader

Napisany przez: hawk 10.02.2005, 17:23:52

Problem stary jak świat: system wymaga włączenia sporej liczby plików i zarządzanie tym jest upierdliwe. Do tego nie należy włączać więcej kodu niż potrzeba, a najlepiej zrobić jakieś lazy load.

Oczywiście, technik jest wiele:

1) require_once rozsiane po plikach, najlepiej poprzedzone jakąś stałą, np. require_once ROOT_DIR . '/foo/Foo.class.php';

2) Prado: deklarujemy namespacy - np. za pomocą funkcji using(), co dodaje nam ścieżki do include_path, a potem niech php znajdzie klasę.

3) Autoloader + mapa (nazwa klasy => ścieżka do pliku); autoloader wczytuje mapę i na jej podstawie jest w stanie znaleźć każdą klasę

Są jeszcze jakieś inteligentne sposoby? Dobry mechanizm powinien być odporny na "przemeblowanie" struktury plików (np. chcemy połączyć kilka klas w jeden plik).

BTW, włączanie plików bez klas (tylko funkcje i kod) jest gorsze, bo nie ma tego czegoś, czego można szukać po plikach... kolejna zaleta OOP? winksmiley.jpg

Napisany przez: DeyV 10.02.2005, 21:46:51

Aby sprawę jeszcze bardziej skomplikować - warto pamietać o tym - że require_once i inlude_once jest w rzeczywistości bardzo wolne = każde wywołanie tych funkcji, nawet jeśli tym razem NIE MUSIAŁA ona ładować tego pliku, bo już był załadowany wcześniej, trwa niemal tyle samo, co require()
proste porównanie:
http://www.phpinsider.com/smarty-forum/viewtopic.php?t=4323
I choć, jak ktoś to zauwazył w tym wątku, fizyczny zysk czasu jest niewielki, to jednak procentowo - różnica jest ogromna.

Napisany przez: Imperior 11.02.2005, 18:39:48

Myślę, że moje rozwiązanie pomoże Ci, jeśli wpadniesz na jakiś dobry pomysł, to daj znać:
(nawiasem mówiąc każda klasa przekłada się na lokalizację w ten sposób:
Projekt_Podgrupa_Costam -> /packages/projekt/podgrupa/projekt_podgrupa_costam.php
zdecydowałem się na to, aby każdy plik miał pełną nazwę klasy, a instalator tworzył pakiety klas.)
Plik poza_drzewkiem/packages/noname/noname_package.php

  1. <?php
  2. http://www.php.net/define('NN_SEP', DIRECTORY_SEPARATOR);
  3. http://www.php.net/define('NN_LIB', http://www.php.net/dirname(http://www.php.net/dirname(__FILE__)).NN_SEP);
  4. http://www.php.net/define('NN_PRIVATE', http://www.php.net/dirname(NN_LIB).NN_SEP);
  5.  
  6. function __autoload($sName) {
  7. if (!(CORE :: Import($sName) || CORE :: ImportEx($sName))) {
  8. http://www.php.net/echo 'Kaboooom! Reason? '.$sName.' not found!!!';
  9. }
  10. }
  11.  
  12. class CORE {
  13. public http://www.php.net/static function Import($sName, $sFind = null, $bSearch = true) {
  14. if (!http://www.php.net/isset($sFind)) {
  15. $sFind = $sName;
  16. }
  17. if ($bSearch && (class_exists($sFind, false) || interface_exists($sFind, false))) {
  18. return true;
  19. }
  20. $sName = http://www.php.net/strtolower($sName);
  21. $aArr = http://www.php.net/explode('_', $sName);
  22. http://www.php.net/array_pop($aArr);
  23. $sPath = NN_LIB.http://www.php.net/implode(NN_SEP, $aArr).NN_SEP.$sName.'.php';
  24. if (http://www.php.net/is_readable($sPath)) {
  25. include_once ($sPath);
  26. if ($bSearch && !(class_exists($sFind, false) || interface_exists($sFind, false)) ) {
  27. return false;
  28. }
  29. return true;
  30. }
  31. return false;
  32. }
  33.  
  34. public http://www.php.net/static function ImportEx($sName) {
  35. $aArr = http://www.php.net/explode('_', $sName);
  36. http://www.php.net/array_pop($aArr);
  37. http://www.php.net/array_push($aArr, 'package');
  38. if (CORE :: Import(http://www.php.net/implode('_', $aArr), $sName) || CORE :: Import($sName)) {
  39. return true;
  40. }
  41. return false;
  42. }
  43. }
  44. ?>


(zamiast kaboom w __autoload() będzie komunikat o tym, że strona nieczynna i próba poinformowania admina)

Pliki *_package.php zawierają zlepek klas najczęściej wykorzystywanych w danym pakiecie (noname_package.php zawiera noname_(core,timer,exception itp).php . )
ImportEx różni się tym, że najpierw spróbuje władować pakiet (czyli np. jak władowywyję jakąś usługę, dajmy SQL, to automatycznie mam wyjątki, klase do łączenia, klase do zapytań itp.)

Jeśli jestem pewien, że jakaś klasa/interfejs powinna być w systemie to poprostu z niej korzystam - jeśli nie jest władowana, to zrobi to autoload. (Klasa z pakietu nie musi sprawdzać innych z tego samego pakietu, najczęściej stosowana przeze mnie metoda)
Jeśli wgrywam usługę (auth, sql, cache...) to daję CORE::ImportEx, żeby wgrać pakiet.
Jeśli potrzebuję pojedynczą klasę to poprostu CORE::Import.

Obie funkcje Import i ImportEx zwracają true/false, czyli ustrzegają mnie przed wykorzystaniem nieistniejącej klasy, a co za tym idzie przed Fatal Error.
Przed próbą odczytania pliku oczywiście jest sprawdzenie, czy nie jest obecna już dana klasa/interfejs.

Napisany przez: hawk 18.02.2005, 14:39:33

@Imperior:

Dwie słabe strony tu widzę:

1) Wszystko musi siedzieć w 2 miejscach: NN_LIB i NN_PRIVATE, co utrudnia model działania typu "user ściąga pakiet, wrzuca gdzie mu się podoba i chce żeby to działało".

2) Nie da się tego połączyć z zewnętrznymi bibliotekami, tzn takimi, gdzie nie ty decydujesz o nazwach klas.

Za to podobają mi się dwie inne rzeczy:

1) Importowanie całych "pakietów".

2) Nazwa_Klasy niejako emuluje pakiety, których php nie ma (nie możesz zrobić $foo = new com.example.Foo).

A mi się coraz bardziej podoba mapa:

  1. <?php
  2. $map = http://www.php.net/array(
  3. 'MojaKlasa' => MY_DIR . 'foo/MojaKlasa.class.php',
  4. 'MojaInnaKlasa' => MY_DIR . 'bar/KilkaKlas.inc.php',
  5. 'MojaInnaKlasa2' => MY_DIR . 'bar/KilkaKlas.inc.php',
  6. 'Smarty' => SMARTY_DIR . 'Smarty.class.php',
  7. }
  8. ?>

Autoloader jest w tym momencie banalny. Takie mapy można nawet generować automatycznie przeszukując skryptem katalogi i parsując (tokenizując) pliki php. Można automatycznie łączyć mapy dostarczane z pakietami w jedną wspólną. Można dowolnie łączyć pliki w pakiety, wrzucać do jednego pliku, dzielić itd., a potem przegenerować mapę i dalej wszystko działa. Szczególnie podoba mi się to w kontekście trzymania "normalnej" hierarchii plików u siebie i dystrybuowania wszystkiego w jednym pliku po wycięci komentarzy i whitespaces.

Wada - trzeba ten głupi plik wczytać zanim autoloader cokolwiek zrobi sad.gif. I ciężko byłoby automatycznie wstawić stałe jak w przykładzie powyżej.

Napisany przez: bela_666 18.02.2005, 15:03:36

Ja mysle, zeby rozwiazac to na podobnej zasadzie jak w javie. Mianowicie :

Mamy metode import, ta zaś wczytuje plik/pliki jakie chcemy. Czyli robimy:

  1. <?php
  2. import('pl.bela.foo.bar.Bar');
  3. import('pl.bela.foo.*');
  4. ?>


W pierwszym przypadku wczyta klase Bar ktora jest w katalogu pl/bela/foo/bar/, a w drugim wczyta wszystkie klasy znajdujące się w katalogu pl/bela/foo, z wyjatkiem pl.bela.foo.bar.Bar bo informacja o tym ze taka klasa jest załadowa jest przetrzymywane w tablicy ( 2 posty wyzej jest wyjasnienie czemu smile.gif ).

Co do położenia katalogow pl/bela, net/php/smarty, nie przejmuje sie tym za bardzo, ponieważ są classpathy smile.gif

Aha, i nie definiuje package(); no bo po co winksmiley.jpg

Napisany przez: Imperior 18.02.2005, 20:17:07

Cytat(hawk @ 2005-02-18 13:39:33)
Dwie słabe strony tu widzę:

1) Wszystko musi siedzieć w 2 miejscach: NN_LIB i NN_PRIVATE, co utrudnia model działania typu "user ściąga pakiet, wrzuca gdzie mu się podoba i chce żeby to działało".

2) Nie da się tego połączyć z zewnętrznymi bibliotekami, tzn takimi, gdzie nie ty decydujesz o nazwach klas.

ad 1. NN_LIB jest podrzędne do NN_PRIVATE. W NN_LIB są trzymane właśnie pakiety, dla mnie nie do pomyślenia jest coś takiego, że user wrzuca jakiś pakiet gdzie mu się żywnie podoba, zamiast tego jest Instalator, przez którego może ściągnąć pakiet, uploadować, wskazać na serwerze i na podstawie plików konfiguracyjnych w pakiecie wszystko jest pięknie i ładnie instalowane razem z weryfikacją zależności.

ad 2. Z pozoru... Jeśli pliki będą w NN_LIB to zostaną załadowane, jedynie musi być spełniony warunek, żeby nie miały '_' w nazwach... (a jeśli tak, to żeby były prawidłowo umieszczone, ale wtedy probelmu nie ma biggrin.gif )

Ciekawe z tymi mapami... a gdyby rozszerzyć je o jakieś wyrażenia regularne?

Napisany przez: hawk 21.02.2005, 14:43:27

@bela_666: Takie rozwiązanie na pewno pasuje do javy, ale czy pasuje do php? Wszystko rozbija się o wydajność. Trzeba te importy przeczytać, pozamieniać na slashe, znaleźć katalogi, wczytać pliki... Owszem, mamy classpath (tzn. include_path winksmiley.jpg), ale w php jest to kosztowna (IMHO, nie robiłem testów) opcja, bo szuka po dysku przy każdym requeście. A przecież nie pozbędziemy się bibliotek, które nie stosują się do naszego standardu (smarty, adodb, ...). Czyli classpath się rozrasta.

Do tego, w php opłaca się wczytywać tylko te pliki, które są niezbędne. W javie nie ma tego problemu, bo wczytujesz raz i masz spokój. W php starasz się zminimalizować ilość operacji dyskowych na jedno żądanie.

W javie importy i w ogóle pakiety są potrzebne, bo 2 klasy w różnych pakietach mogą mieć tą samą nazwę i nie jest to problem. W php taka sytuacja jest niedozwolona, więc i pakiety są niepotrzebne (tak naprawdę jest to zabronione właśnie dlatego, że nie ma pakietów, ale skutek jest ten sam).

W javie z importami jest jeden problem: w pewnym momencie robi się ich zbyt dużo i upraszczamy sobie życie pisząc import java.util.*. W php jest to nie do przyjęcia - za dużo niepotrzebnie włączanego kodu. Więc nie muszę się martwić o unikalność com.example.hawk, bo to i tak nic mi nie daje.

@Imperior: sens regexpów zależy od tego, czy będą one szybsze niż wymienienie wszystkich klas. Czyli od wydajności.

Podsumowując, idealnie chciałbym mieć:
- nie muszę w swoich plikach php pisać require_once, przypominać sobie gdzie są potrzebne klasy, oraz upewniać się że nie pominąłem potrzebnego interfejsu
- mogę obsługiwać zewnętrzne (nie moje) biblioteki tak samo (tzn też bez pamiętania o require_once smarty)
- mogę w trakcie developmentu zmienić strukturę moich plików z klasami, przenieść do innych katalogów, itd. bez poprawiania mojej aplikacji
- mogę zmienić coś w n-tej dystrybucji pakietu bez obawy, że kod użytkowników nie znajdzie mojej klasy
- włączane są tylko te klasy, które są aktualnie potrzebne, i narzut czasy wykonania jest minimalny

Napisany przez: hawk 25.02.2005, 09:23:01

@serafin: Możesz napisać więcej? Mi się zawsze wydawało że php nie pozwala na kropki w nazwie klasy. Tak bardzo mi się wydawało, że nigdy nie sprawdziłem. I czy to oznacza, że pełna nazwa klasy rzeczywiście brzmi System.Console.Out? Żeby autoloader mógł znaleźć klasę.

Bo problem z brakiem namespaces jest taki, że jak napiszemy $foo = new System.Console.Out, to php nie wie, że ma szukać klasy Out w System.Console, tylko szuka System.Console.Out.

Napisany przez: Imperior 25.02.2005, 09:55:57

Cytat(hawk @ 2005-02-25 08:23:01)
Bo problem z brakiem namespaces jest taki, że jak napiszemy $foo = new System.Console.Out, to php nie wie, że ma szukać klasy Out w System.Console, tylko szuka System.Console.Out.

A ściślej mówiąc to stara się odczytać stałe i połączyć je w jeden string.

Ja to zrozumiałem w ten sposób, że pisze kod, który zawiera błędy z punktu widzenia parsera php, a który jest parsowany, aby poprawić te błędy i dopisać jakieś includy itp.

Alternatywnie poprostu w kodzie jest coś jak:
uses 'System.Console.Out';
i to jest odczytywane, żeby do pliku wynikowego dodać includy.

serafin: czy coś w tym stylu miałeś na myśli?

Napisany przez: Vengeance 25.02.2005, 11:42:46

A co Wam się stanie gdy zamiast System.Out będzie System_Out ?
Czy warto stosować te wszystkie parsery/tokenizery czy co tam jeszcze (opóźniające czas wykonywania) aby wygrać "walke" z kropką ? smile.gif

Napisany przez: hawk 25.02.2005, 12:06:36

Nic się nie stanie, poza utratą estetyki winksmiley.jpg. Chociaż kwestia "kropka czy kreska" nie jest tutaj podstawowa. Problem jest raczej w tym, że autoloader będzie szukał klasy System_Out, a nie klasy Out w pakiecie System. Czyli na braku pakietów. I na tym, że na pewno znajdą się biblioteki, gdzie kreska jest używana do czegoś innego, co może wprowadzić system w błąd.

Napisany przez: Vengeance 25.02.2005, 12:31:22

ale to jest php a nie java smile.gif

Co do autoloadera... co za problem zrobić jakiś explode po znaku _
i odpowiednio sprawdzić?

Napisany przez: hawk 25.02.2005, 12:48:42

Co za problem? Sprawdzanie po zrobieniu explode jest kosztowne. Odwołanie do dysku, przemnożone przez ileś tam włączanych klas. A ja zakładałem sobie idealnie, że autoloader, jako "jądro jądra", musi być super szybki.

Żeby nie było że się czepiam: to nie jest zła metoda, na razie nie zakodowałem samemu nic mądrego (stąd ten wątek). Zawsze są zady i walety, ja tylko wymieniam słabe strony.

Napisany przez: Seth 25.02.2005, 12:50:43

Troche to stary kod ale moze sie przyda:

  1. <?php
  2. http://www.php.net/define( 'INCLUDE_PATH', 'include/' );
  3.  
  4. /**
  5.  * Use this function to include classes in given namespace pattern
  6.  *
  7.  * @param $nsPattern string
  8.  * @return true if succed, false otherwise
  9.  */
  10. function Import( $nsPattern )
  11. {
  12. // Filter pattern namespace
  13. if ( http://www.php.net/preg_match( '/^[a-z0-9_]+(.[a-z0-9_]+){0,}(.*)?$/i', $nsPattern ) )
  14. {
  15. $nsPattern = http://www.php.net/str_replace( '.', '/', $nsPattern );
  16.  
  17. if ( http://www.php.net/preg_match( '/^(.*)/*$/', $nsPattern, $match ) )
  18. {
  19. $patch = INCLUDE_PATH.$match[1];
  20.  
  21. if ( $handle = http://www.php.net/opendir( $patch ) )
  22. {
  23. while ( false !== ( $file = http://www.php.net/readdir( $handle ) ) )
  24. {
  25. $i = 0;
  26.  
  27. if ( $file != '.' && $file != '..' && http://www.php.net/preg_match( '/^(.*).class.php$/', $file, $match ) )
  28. {
  29. $filePath = $patch.&#092;"/\".$file;
  30.  
  31. if ( http://www.php.net/file_exists( $filePath ) && http://www.php.net/is_file( $filePath ) && !class_exists( $match[1] ) )
  32. {
  33. require_once( $filePath );
  34.  
  35. $i++;
  36. }
  37. }
  38. }
  39.  
  40. http://www.php.net/closedir($handle);
  41. }
  42.  
  43. return ( $i ? 1 : 0 );
  44. }
  45. else
  46. {
  47. $className = http://www.php.net/substr( http://www.php.net/strrchr( $nsPattern, '/'), 1 );
  48. $filePath = INCLUDE_PATH.$nsPattern.&#092;".class.php\";
  49.  
  50. if ( http://www.php.net/file_exists( $filePath ) && http://www.php.net/is_file( $filePath ) && !class_exists( $className ) )
  51. {
  52. require_once( $filePath );
  53.  
  54. return 1;
  55. }
  56.  
  57. return 0;
  58. }
  59. }
  60.  
  61. // Wrong pattern
  62. return 0;
  63. }
  64. ?>


Wywolania:
Import( 'db.*' );
includuje wszystkie klasy z katalogu include_path.'db/'

Import( 'db.jakas_klasa' );
includuje klasy o nazwie jakas_klasa z katalogu db

Pliki z klasami musza miec w nazwie .class.php

Aha... pisany pod php 4, wiec bez autoloadera :/

Napisany przez: bela_666 25.02.2005, 12:55:30

Seth, a czy te pregi nie spowalniają bardzo przy dużej ilości plików ?

Napisany przez: Seth 25.02.2005, 13:07:03

Jakies spowolnienie napewno jest ale za to mozna latwo zaincludowac kilka klas winksmiley.jpg

Napisany przez: bela_666 26.02.2005, 00:02:23

Cytat(hawk)
Podsumowując, idealnie chciałbym mieć:
- nie muszę w swoich plikach php pisać require_once, przypominać sobie gdzie są potrzebne klasy, oraz upewniać się że nie pominąłem potrzebnego interfejsu
- mogę obsługiwać zewnętrzne (nie moje) biblioteki tak samo (tzn też bez pamiętania o require_once smarty)
- mogę w trakcie developmentu zmienić strukturę moich plików z klasami, przenieść do innych katalogów, itd. bez poprawiania mojej aplikacji
- mogę zmienić coś w n-tej dystrybucji pakietu bez obawy, że kod użytkowników nie znajdzie mojej klasy
- włączane są tylko te klasy, które są aktualnie potrzebne, i narzut czasy wykonania jest minimalny


No chyba udało mi się to osiągnąć, mianowicie: tokenizer przejezdza po wszystkich katalogach i załączą pliki .php, a przy okazji pomija katalogi .svn ( coś się zawieszało winksmiley.jpg ), wyciąga nazwy klas/interfejsów i to gdzie one się znajdują, wciska to do tablicy i generuje piękny pliczek autoload.php, wraz z funckją __autoload.

No, to by było na tyle biggrin.gif

Napisany przez: Nievinny 26.02.2005, 09:35:42

Jeśli mogę wtrącić trzy grosze, ja myślę, że można przejechać po katalogach czymś takim:

  1. <?php
  2.  
  3. $Dir = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( ROOT_PATH ), true );
  4. foreach( $Dir as $File ) {
  5. if( $File->getFilename() === $sFile ) {
  6. try {
  7. CORE::loadClass( $File->getPathname() );
  8. }
  9. catch( CoreExceptions $Error ) {
  10. http://www.php.net/print Debuger::errorDisplay( $Error );
  11. http://www.php.net/exit;
  12. }
  13. break;
  14. }
  15. }
  16.  
  17. ?>

Tylko trzeba zdefiniować stała ROOT_PATH i dodać do nazwy rozszerzenie i zdefiniować klasę CORE.
Warunki:Za jakiś czas dołącze dokładniejszy kod, może trochę wolnawe, ale samo znajdzie ścieżkę dostępu do plików smile.gif

Napisany przez: hawk 26.02.2005, 09:55:15

@bela_666: pokaż pokaż pokaż tongue.gif

Napisany przez: Vengeance 26.02.2005, 11:55:48

Nievinny: jakbyś zrobił cache jakiś to ok. Teraz uważam, że to jest najgorsze z najgorszych rozwiązań smile.gif

Napisany przez: bela_666 26.02.2005, 12:21:14

Cytat(hawk @ 2005-02-26 09:55:15)
@bela_666: pokaż pokaż pokaż tongue.gif

Heh, ale to dopiero brzydko wyglądający prototyp, ale ok biggrin.gif Pisałem, aby w ogóle działało winksmiley.jpg

  1. <?php
  2. http://www.php.net/error_reporting(E_ALL|E_STRICT);
  3. function get_microtime() {
  4. list($usec, $sec) = http://www.php.net/explode(&#092;" \",microtime());
  5. return ((float)$usec + (float)$sec);
  6. }
  7.  
  8. http://www.php.net/global $fileContext;
  9. $fileContext = http://www.php.net/array();
  10. $start = get_microtime();
  11. iterateDir('.'/*dirname(__FILE__)*/);
  12.  
  13. function iterateDir($path) {
  14. $di = new DirectoryIterator($path);
  15. foreach ($di as $k => $v) {
  16. // get extension
  17. $ext = http://www.php.net/end(http://www.php.net/explode('.', $v->getPathname()));
  18. // is svn dir
  19. $name = $v->getFilename();
  20. $first = http://www.php.net/substr($name, 0, 1);
  21. $first == '.' ? $dupa = false : $dupa = true;
  22.  
  23. // if is dir and not svn dir, iterate this dir;]
  24. if($v->isDir() && $dupa) {
  25. iterateDir($v->getPath() . '/' . $v->getFilename());
  26. }
  27. // print filename with path, with php extenstion
  28. if($dupa && $v->isFile() && $ext == 'php' && $v->getFilename() != 'tokenizer.php' && $v->getFilename() != 'test.php' && $v->getFilename() != 'config.php' && $v->getFilename() != 'dupa.php') {
  29. $GLOBALS['fileContext'][] = http://www.php.net/array($v->getPathname(), token_get_all(http://www.php.net/file_get_contents($v->getPathname())));
  30. //print $v->getPathname() . \"<br />n\";
  31. }
  32. }
  33.  
  34. }
  35.  
  36. //dump($fileContext);
  37. $classes = http://www.php.net/array();
  38. $key = 0;
  39. foreach($fileContext as $file) {
  40. http://www.php.net/static $i;
  41. foreach ($file as $c) {
  42. if(http://www.php.net/is_array($c)) {
  43. $a = false;
  44. foreach ($c as $k => $v) {
  45. if(http://www.php.net/is_array($v)) {
  46. if($v[0] == T_CLASS || $v[0] == T_INTERFACE) {
  47. $a = true;
  48. } elseif ($a && $v[0] == T_STRING) {
  49. $classes[] = http://www.php.net/array($v[1], http://www.php.net/substr($file[0], 2));
  50. $a = false;
  51. }
  52. }
  53. else
  54. {
  55. //print(\"$v<br/>n\");
  56. }
  57. }
  58. }
  59.  
  60. }
  61.  
  62. }
  63.  
  64. //dump($classes);
  65.  
  66. $phpCode = '<?php function __autoload($name) {' . &#092;"n\";
  67. $phpCode .= 'static $map = array(' . &#092;"n\";
  68. foreach ($classes as $k => $v) {
  69. $phpCode .= ''' . $v[0] . '' => '' . $v[1] . '','. &#092;"n\";
  70. }
  71. $phpCode .= ');' . &#092;"n\";
  72. $phpCode .= 'require_once($map[$name]);' . &#092;"n}n\";
  73. $phpCode .= '?>';
  74. file_put_contents('autoload.php', $phpCode);
  75. //dump($fileContext);
  76. function dump($dump) {
  77. print('<pre>');
  78. var_dump($dump);
  79. print('</pre>');
  80. }
  81. print ($end = get_microtime() - $start . '<br />');
  82. ?>

Napisany przez: Vengeance 26.02.2005, 12:36:56

Połączyłem kody: bela_666, Nievinny oraz Imperiora i osiągnołem taki efekt:

  1. <?php
  2.       public function getClassesMap($sDirName)
  3.       {
  4.          $oRecursiveDirectory = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $sDirName ), true );
  5.          foreach ($oRecursiveDirectory as $oFile)
  6.          {
  7.             if(!http://www.php.net/is_file($oFile->getPathname()))
  8.                continue;
  9.             $aTokenAll = token_get_all(http://www.php.net/file_get_contents($oFile->getPathname()));
  10.             $bIsClass = false;
  11.             foreach ($aTokenAll as $iToken)
  12.             {
  13.                if (!http://www.php.net/is_string($iToken))
  14.                {
  15.                   list($iTokenID, $sTokenValue) = $iToken;
  16.                   switch ($iTokenID)
  17.                   {
  18.                      case T_CLASS:
  19.                         $bIsClass = true;
  20.                         break;
  21.                      case T_INTERFACE:
  22.                         $bIsClass = true;
  23.                         break;
  24.                      case T_STRING:
  25.                         if ($bIsClass)
  26.                         {
  27.                            $aClassesMap[$sTokenValue] = $oFile->getPathname();
  28.                            $bIsClass = false;
  29.                         }
  30.                         break;
  31.                      case T_WHITESPACE:
  32.                         break;
  33.                      default:
  34.                         $bIsClass = false;
  35.                         break;
  36.                   }
  37.                }
  38.             }
  39.          }
  40.          return $aClassesMap;
  41.       }
  42. ?>

Napisany przez: Vengeance 26.02.2005, 12:41:48

Linia nr 9: zmieniasz ją na:

Kod
           if(!is_file($oFile->getPathname()) OR $oFile->getFileName == '.svn')


Powinno zadziałać

Napisany przez: Nievinny 26.02.2005, 17:03:09

@Vengeance -> Czy twoim zdaniem ładowanie z mapy powinno być zaimplementowane od razu w __autoload() czy dać jakąś funkcję pośrednią?

OK, a ja myślę, że można to jeszcze przerobić winksmiley.jpg i jeszcze jedno: kiedy należy a kiedy nie robić mapę plików?

I czy pozwolisz wykorzystam to w swoim CORE?

Zobaczcie coś takiego i oceńcie

  1. <?php
  2.  
  3. http://www.php.net/define( 'IN_CORE', true );
  4.  
  5. // ---- Definicja stałych
  6. http://www.php.net/define( 'ROOT_PATH', './' );
  7. http://www.php.net/define( 'PHPEX', '.php' );
  8.  
  9. // ---- Załadowanie systemów
  10. include_once( ROOT_PATH . 'lib/debuger/Debuger' . PHPEX );
  11. include_once( ROOT_PATH . 'lib/CORE' . PHPEX );
  12.  
  13. // ---- Mapa plików
  14. $aMap = CORE::getClassesMap( ROOT_PATH );
  15. // ---- Funkcja autoładowania
  16. function __autoload( $sFile ) {
  17. http://www.php.net/global $aMap;
  18. $bLoad = false;
  19. foreach( $aMap as $sName => $sValue ) {
  20. if( $sName === $sFile ) {
  21. try {
  22. CORE::loadClass( $sValue, $sFile );
  23. }
  24. catch( CoreExceptions $Error ) {
  25. http://www.php.net/print Debuger::errorDisplay( $Error );
  26. http://www.php.net/exit;
  27. }
  28. $bLoad = true;
  29. break;
  30. }
  31. }
  32. if( $bLoad === false ) {
  33. http://www.php.net/print Debuger::errorLiteDisplay( 'Nie można odnaleźć podanej klasy' );
  34. http://www.php.net/exit;
  35. }
  36. }
  37.  
  38. ?>


Mapa jest tworzona tylko raz, a potem porównywana w pentli foreach? Czy lepiej inaczej, np:
  1. <?php
  2.  
  3. function __autoload( $sFile ) {
  4. http://www.php.net/global $aMap;
  5. if( http://www.php.net/isset($aMap[$sFile] ) ) {
  6. try {
  7. CORE::loadClass( $aMap[$sFile], $sFile );
  8. }
  9. catch( CoreExceptions $Error ) {
  10. http://www.php.net/print Debuger::errorDisplay( $Error );
  11. http://www.php.net/exit;
  12. }
  13.  
  14. }
  15. else {
  16. http://www.php.net/print Debuger::errorLiteDisplay( 'Nie można odnaleźć podanej klasy' );
  17. http://www.php.net/exit;
  18. }
  19. }
  20.  
  21. ?>

Chyba tes sposób lepszy, czy nie?

Napisany przez: Vengeance 26.02.2005, 17:35:25

Nievinny: chodzi oto, aby mape generować tylko RAZ!! Potem jak coś dodasz/zmienisz dopiero robisz ją ponownie. W moim przypadku robienie mapy zabiera 95% czasu wykonywnia skryptu! Dlatego tak ważnym jest aby wynik był objęty jakimś cache. A czy zrobisz w __autoload() czy w czymkolwiek innym to już nie jest takie ważne smile.gif

Napisany przez: Nievinny 26.02.2005, 17:42:59

Cytat
Nievinny: chodzi oto, aby mape generować tylko RAZ!! Potem jak coś dodasz/zmienisz dopiero robisz ją ponownie. W moim przypadku robienie mapy zabiera 95% czasu wykonywnia skryptu! Dlatego tak ważnym jest aby wynik był objęty jakimś cache. A czy zrobisz w __autoload() czy w czymkolwiek innym to już nie jest takie ważne smile.gif


Źle mnie zrozumiałeś, to, że mapa powinna być robiona tylko raz to ja doskonale wiem....
Chodzi mi o samą funkcję __autoload() czy lepsza jest z foreach czy ta druga... winksmiley.jpg

Napisany przez: Vengeance 26.02.2005, 18:15:03

A po co foreach? Jeszcze opozniac ? To $map[$className] jest takie zle? W sumie to i tak kazdy robi jak mu wygodniej ;]

Napisany przez: hawk 26.02.2005, 18:42:06

@Vengeance et al: No, bardzo ładny kod biggrin.gif. Ja bym dodał parametryzację/konfigurację generatora mapy. Coś takiego jak ma phpdocumentor - jakie katalogi ma przeglądać, jakie pliki ma czytać. Wtedy można sobie zignorować .svn i nie trzeba tego zakodowywać na stałe w kodzie.

A potem tylko zrzucić mapę do jakiegoś pliku php i voila!

I na koniec coś takiego:

  1. <?php
  2. function __autoload($sClass) {
  3. ClassLoader::load($sClass);
  4. }
  5.  
  6. class ClassLoader {
  7. private http://www.php.net/static $aMap;
  8.  
  9. public http://www.php.net/static function addMap($aMap) {
  10. self::$aMap = self::$aMap == null ? $aMap : http://www.php.net/array_merge(self::$aMap, $aMap);
  11. }
  12.  
  13. public http://www.php.net/static function load($sClass) {
  14. if (http://www.php.net/array_key_exists($sClass, self::$aMap)) {
  15. require self::$aMap[$sClass];
  16. }
  17. }
  18. }
  19. ?>

Napisany przez: Vengeance 26.02.2005, 19:07:38

Ok userzy forum.php.pl "wymyslili" juz idee "routera" (a raczej po prostu to nazwali ladnie). Teraz czas nazwac to co tu razem stworzyliśmy snitch.gif

Napisany przez: Nievinny 27.02.2005, 12:32:23

Nazwa: Autoload Class System...

może być, a klasa niezła, tylko, że robi zrzuty przy każdym wywołaniu skryptu a nie ciągnie z cache winksmiley.jpg

Pozwolę sobie jeszcze dopisać, cos o systemie cache:
1) Jest absolutnie niezbędny:

Jak ktoś jest zainteresowany klasą cache to na PW

Napisany przez: chmolu 4.03.2005, 10:29:10

Ja to rozwiązałem podobnie, jak w nowym Mojavi:

  1. <?php
  2. function __autoload($class_name)
  3. {
  4. $filename = ConfigManager::getConfig('autoload', 'autoload', $class_name);
  5. if (!http://www.php.net/empty($filename))
  6. {
  7. /* include a file with the class definition */
  8. require_once $filename;
  9. }
  10. else 
  11. {
  12. /* unknown class */
  13. throw new Exception(http://www.php.net/sprintf('Autoloading of class \"%s\" failed', $class_name));
  14. }
  15. }
  16. ?>


Lista plików jest przechowywana w pliku konfiguracyjnym (ini). Bardzo elastyczne rozwiązanie. Trzeba tylko włączyć klasę ConfigManager - reszta jest ładowana automatycznie.

Napisany przez: Nievinny 4.03.2005, 10:52:36

A ja generujesz listę klas? Tu masz od razu generator

Napisany przez: chmolu 4.03.2005, 16:34:52

Lista klas jest w pliku konfiguracyjnym autoload.ini, który wygląda tak:

Cytat
[autoload]
Class1 = sciezkadopliku
Class2 = sciezkadopliku
Classn = sciezkadopliku


Plik ten jest parsowany do tablicy i następnie chache'owany jako plik .php

Muszę przemyśleć Twój sposób ze skanowaniem katalogów... ciekawe smile.gif

Napisany przez: hawk 4.03.2005, 17:26:32

@chmolu:
1) Z tego co pamietam, w __autoload nie mozna rzucac wlasnych wyjatkow, bo i tak nie beda dzialac. Czyzby bug w Mojavi?

2) Pliki ini sa w tym przypadku zdecydowanie gorsze od tablic php. W pliku ini nie mozna zrobic czegos takiego:

  1. <?php
  2. $map = http://www.php.net/array(
  3. 'FooClass' => FOO_DIR . '/Foo.class.php',
  4. );
  5. ?>

Kazdy, kto bedzie uzywal systemu z plikiem ini, bedzie musial zmienic wszystkie sciezki w tym pliku, bo domyslne nie beda u niego dzialaly.

3) Jaki jest sens kompilowac plik ini do pliku php? Jezeli pliki php sa zdecydowanie szybsze, to dlaczego nie zaczac wlasnie od nich? Dla mnie to jest komplikowanie sobie zycia.

Napisany przez: chmolu 4.03.2005, 17:49:43

Cytat
1) Z tego co pamietam, w __autoload nie mozna rzucac wlasnych wyjatkow, bo i tak nie beda dzialac. Czyzby bug w Mojavi?

Wyjątki można wyrzucać, tylko nie mozna ich łapać w catch. Wtedy są uznawane jako fatal error.

Cytat
Pliki ini sa w tym przypadku zdecydowanie gorsze od tablic php. W pliku ini nie mozna zrobic czegos takiego:
...

kazdy, kto bedzie uzywal systemu z plikiem ini, bedzie musial zmienic wszystkie sciezki w tym pliku, bo domyslne nie beda u niego dzialaly.

Można zrobić coś takiego. Po prostu trzeba dodawać do każdego wpisu pseudo-stałą, np. Action = %APP_DIR%/modules/actions/action.php. ConfigManager automatycznie zamienia takie 'stałe' na właściwą scieżkę.

Cytat
3) Jaki jest sens kompilowac plik ini do pliku php? Jezeli pliki php sa zdecydowanie szybsze, to dlaczego nie zaczac wlasnie od nich? Dla mnie to jest komplikowanie sobie zycia.

Wiesz, to jest indywidualna sprawa. Ja przyjąłem, że taka rzecz należy do konfiguracji, a nie do kodu aplikacji. Konfigurację zapisuję w plikach ini. Mogę oczywiście zapisywac od razu w plikach php. Dla mnie jest wygodniej to oddzielić na właściwy kod aplikacji i dane konfiguracji. Wcale nie jest to komplikowanie sobie życia. Całość ogranicza się do funkcji parse_ini_file(), var_export() i fputs().

Zależy, jak kto lubi smile.gif

Napisany przez: Nievinny 4.03.2005, 18:05:53

W sumie, skrypt po raz pierwszy zaczyna działanie więc generuje mapę, która jest potem cachowana. Gdy odpalimy skrypt po raz drugi odczyta mapę z cache (zserializowany ciąg + <?php die(); ?> ), usunie te ostatnie i przyśpieszenie jest widoczne winksmiley.jpg
to sprowadza się ogólnie do:

  1. <?php
  2.  
  3. if( !CORE::addMap( $Cache->loadCacheArray( 'aMap' ) ) ) {
  4. $aMap = CORE::getMap( './' );
  5. CORE::addMap( $aMap );
  6. $cache->saveCacheArray( 'aMap', $aMap );
  7. }
  8. // 'aMap' - uchwyt do pliku cache .php w ./cache/arrays/
  9.  
  10. ?>

I wg mnie to najprostszy sposób winksmiley.jpg

Napisany przez: Imperior 5.03.2005, 11:12:31

Cytat(hawk @ 2005-03-04 16:26:32)
1) Z tego co pamietam, w __autoload nie mozna rzucac wlasnych wyjatkow, bo i tak nie beda dzialac. Czyzby bug w Mojavi?

Zgadza się, w __autoload nie rzuca się wyjątków, ale mojavi3 nie rzuca go, tylko tworzy (bo tam ma zaimplementowane metody dot. informowania o bledach).

PS. Na początku też myślałem, że mają błąd, ale tam throw nie ma smile.gif

Napisany przez: hawk 7.03.2005, 03:36:42

@chmolu: Faktycznie, obsługa plików ini jest bardzo prosta. Co ciekawe, na początku próbowałem właśnie podejścia z "%APP_DIR%/...". Pliki ini wyglądają lepiej, prościej się je edytuje, trudniej popełnić błąd lub wrzucić niedozwolony kod. Z drugiej strony, trzeba wygenerować z nich pliki php, co samo w sobie jest banalne, ale wymaga sprawdzenia daty modyfikacji, czyli dodatkowego kroku. Wpływ na wydajność jest minimalny i to już chyba kwestia gustu.

Napisany przez: Bora 7.03.2005, 15:06:38

Pliki ini są wczytywane szybciej niż php gdyż mają specjalny prostszy parser.
Porównajcie soebie a zobaczycie że czasami nawet cechowanie w php trwa dłużej.

Napisany przez: Vengeance 7.03.2005, 16:07:14

A czy są szybsze od zserializowanej tablicy, zapisanej w pliku ? smile.gif

Napisany przez: Imperior 7.03.2005, 17:09:23

Cytat(Vengeance @ 2005-03-07 15:07:14)
A czy są szybsze od zserializowanej tablicy, zapisanej w pliku ? smile.gif

Jeśli to nie jest pytanie retoryczne, to: Nie.

Napisany przez: hawk 7.03.2005, 17:19:21

@Bora: Pliki ini moze i sa wczytywane szybciej, ale zamiana %APP_DIR% na odpowiednia sciezke niestety zepsuje tutaj wydajnosc.

Napisany przez: orson 7.03.2005, 22:59:24

witam ...

a moze w ini zrobic 2 sekcje ...

Kod
[path]
app_dir = "./jakis/dir/"
trans_dir = "./jakis/dir"
jakis_dir = "./jakis/dir"
[clases]
class = file_name
class = file_name
class = file_name

i potem parse ini ze znacznikiem true i mamy sklejanie stringow ...
  1. <?php
  2. $array['path']['app_dir'].$array['path']['class']
  3. ?>

i nie trzeba nic zamieniac ... i da sie latwo konfigurowac w tym samym ini ...

pozdrawiam

Napisany przez: hawk 8.03.2005, 00:10:53

A w jaki sposob z gory okreslisz, ktora sciezke masz dokleic do konkretnej klasy? Nie zrobisz tego, chyba ze narzucisz sobie jakies bardzo scisle reguly, a tego wole uniknac.

Napisany przez: Nievinny 8.03.2005, 08:01:25

A ja mam jeszcze pytanie dot. __autoload(). Czy jeśli wywołam w niej statyczną metodę która w razie niepowodzenia wywala wyjątek, to trzeb go obsłużyć w __autoload() czy przejdzie poziom wyżej?
Przykład:

  1. <?php
  2.  
  3. function __autoload( $sClassName ) {
  4. Application::loadClass( $sClassName );
  5. }
  6.  
  7. //czy
  8.  
  9. function __autoload( $sClassName ) {
  10. try {
  11. Application::loadClass( $sClassName );
  12. }
  13. catch( NotFoundClassException $e ) {
  14. //coscos
  15. }
  16. }
  17.  
  18. ?>


Czy 1 sposób też może być? Czy wyjątek zostanie obsłużony?

Napisany przez: orson 8.03.2005, 20:18:36

Cytat(hawk @ 2005-03-08 00:10:53)
A w jaki sposob z gory okreslisz, ktora sciezke masz dokleic do konkretnej klasy? Nie zrobisz tego, chyba ze narzucisz sobie jakies bardzo scisle reguly, a tego wole uniknac.

witam ..

no przeciez normalnie ...
Kod
[path]
mainAppPath = '/home/user/public_hmtl/site/'
mainImagesPath = '/home/user/public_hmtl/site/images/'
cos tam
cos tam
default = 'clases/'
[clases]
dbclass = 'clases/db.class.php'
session = 'clases/session.class.php'
mail = '3rdpart/clases/mail/mail.class.php'
;z defaultem
dbclass = 'db.class.php'
session = 'session.class.php'

i potem:
  1. <?php
  2.  
  3. http://www.php.net/print $iniFile['path']['mainAppPath'].$iniFile['clases']['dbclass'];
  4. http://www.php.net/print $iniFile['path']['mainAppPath'].$iniFile['clases']['session'];
  5. //defaultem
  6. http://www.php.net/print $iniFile['path']['mainAppPath'].$iniFile['path']['default'].$iniFile['clases']['dbclass'];
  7.  
  8. ?>

w sekcji clases tez moga byc sciezki podane, przeciez to lokalizacja pliku a nie nazwa klasy ... czyli dajesz najpierw najbardziej ogolna a potem w poszczegolnych klasach dodajesz, podkatalogi ... mozna na sztywno dodac default do kodu strony a jak cos sie zmieni [ale znowu bez przesady ... tak czesto sie nie zmienia i klasy i tak sa w 1 miejscu] to zmienic albo nawet wyzerowac default

pozdrawiam

Napisany przez: hawk 9.03.2005, 03:16:17

@Orson: nie o to mi chodzi. Masz np. (a raczej użytkownik systemu ma) Smarty, kawałek WACT i w ogóle przynajmniej kilka bibliotek, których używa. Przecież nie będzie tak, że jeden super framework robi wszystko najlepiej, więc autoloader nie powinien być ograniczony do tego, co sami sobie wymyślimy, tylko po prostu działać tak jak chce użytkownik.

Nie powiesz teraz użytkownikowi, że ma to wszystko umieścić w innym katalogu. Będzie miał Smarty w jakimś niestandardowym miejscu, będzie chciał użyć razem z super-autoloaderem i będzie oczekiwał, że autoloader załaduje mu Smarty - od tego przecież jest.

I co wtedy? Skąd autoloader ma wiedzieć, że Smarty akurat nie jest pod mainAppPath?

PS

Cytat
...mozna na sztywno dodac default do kodu strony a jak cos sie zmieni...

Nie można. Autoloader jest po to, żeby tego kodu w ogóle nie było. Nie ma gdzie dodawać defaulta.

Napisany przez: orson 9.03.2005, 07:31:00

witam ...

hmm ... no to w pliku ini w sekcji clases podajesz

Kod
[clases]
smarty = /full/path/to/smarty/lib/smarty.php

a zeby bylo latwiej mozna np. wprowadzic ifa przy ladowaniu i np. jak nazwa klasy zaczyna sie od 2 // a nie od 1 to laduj bez patha .... czyli jak dopiszemy // przed smarty to oleje sekcje path i biblioteka moze byc gdziekolwiek ...
rozwiazanie z plikiem ini coraz bardziej mi sie podoba ... chyba zaczne go stosowac ...

pozdrawiam

Napisany przez: dr_bonzo 9.03.2005, 10:25:30

@Nievinny: wyrzuc wyjatek w tej metodzie i sprawdz. Wyjatek wedruje w gore do ekranu (error) dopuki go nie zlapiesz gdzies po drodze.

Napisany przez: Nievinny 9.03.2005, 17:20:55

Owszem wywala wyjątek, ale nie obsługuje go w zwykły sposób, a skrypt wychwytuje wyjątek "po drodze", tylko wywala fatal errora.

@dr_bonzo: To co mówisz byłoby dobre, gdyby funkcję wywoływać w ten sposób:

  1. <?php
  2.  
  3. __autoload( $zmienna );
  4.  
  5. ?>

A nie jeśli zostaje ona wykonana automatycznie... :/
Zostaje obsługa błędów na poziomie funkcji. Jeśli obsługujemy przez funkcję to pojawiają się problemy:
1) Jeśli klasą którą ładuje system jest klasa obsługi błędów?
  1. <?php
  2.  
  3. //$zmienna = 'Debug';
  4. function __autoload( $zmienna ) {
  5. try {
  6. CORE::zaladuj( $zmienna )
  7. } catch( Exception $e ) {
  8. http://www.php.net/print Debug::display( $e );
  9. }
  10. }
  11.  
  12. ?>


Znajdą się też inne problemy... :/
Macie jakieś pomysły jak ładnie obsłużyć ten wyjątek?

Napisany przez: chmolu 12.03.2005, 23:12:26

Rozwiązanie z generowaniem mapy przez skanowanie katalogów wydaje się najlepsze. Jednak sposób Vengeance'a nie jest zbyt efektywny. Wymaga wczytania zawartości każdego pliku z osobna i sprawdzania, czy zawiera on jakieś klasy. Może jest to bardziej elastyczne, gdy mamy po kilka klas w jednym pliku, ale ja to zrobiłem inaczej.

U mnie każda klasa jest w osobnym pliku. Nazwa pliku zawierającego definicję klasy lub interfejsu kończy się na .class.php lub interface.php. I tylko takie pliki są ładowany do mapy.

Wycinki kodu z mojego frameworka:

  1. <?
  2. /**
  3.    * Checks whether the specified path exists.
  4.    * If the directory is not found, it'll try to create one.
  5.    *
  6.    * @param string $dirname 
  7.    * @return void
  8.    */
  9. function ensure_directory_exists($dirname)
  10. {
  11. $dirname = http://www.php.net/substr($dirname, http://www.php.net/strlen(APP_DIR));
  12.  
  13. $sub_dirs = http://www.php.net/explode('/', $dirname);
  14. $path = '';
  15. foreach ($sub_dirs as $dir)
  16. {
  17. $path .= $dir . '/';
  18. if (!http://www.php.net/file_exists($path))
  19. {
  20. if (!@http://www.php.net/mkdir($path, 0777))
  21. {
  22. throw new DaeronException('Could not create a directory \"' . $path . '\"');
  23. }  
  24. }
  25. }
  26. }
  27.  
  28. /**
  29.    * Writes the supplied content to a file.
  30.    *
  31.    * @param string $filename Name of a file to write to.
  32.    * @param mixed $content Data to write to a file.
  33.    * @return void
  34.    */
  35. function file_write($filename, $content)
  36. {
  37. ensure_directory_exists(http://www.php.net/dirname($filename));
  38. $fp = @http://www.php.net/fopen($filename, 'w');
  39. if($fp)
  40. {
  41. @http://www.php.net/fputs($fp, $content, http://www.php.net/strlen($content));
  42. }
  43. @http://www.php.net/fclose($fp);
  44. }
  45.  
  46. class Autoloader
  47. {
  48. /**
  49.  * Map of class names and their localization. 
  50.  *
  51.  * @var array
  52.  * @static 
  53.  */
  54. private http://www.php.net/static $map = null;
  55.  
  56. /**
  57.  * Loads specified class.
  58.  *
  59.  * @param string $class_name
  60.  * @return void 
  61.  */
  62. public http://www.php.net/static function load($class_name)
  63. {
  64. $cached_map = APP_DIR . '/var/cache/autoload.php';
  65. if (!http://www.php.net/is_readable($cached_map))
  66. {
  67. self::generateMap();
  68. }
  69.  
  70. if (!http://www.php.net/isset(self::$map)) require_once $cached_map;
  71.  
  72. $class_name = http://www.php.net/strtolower($class_name);
  73. if (!http://www.php.net/isset(self::$map[$class_name]))
  74. {
  75. throw new Exception('Unknown class ' . $class_name);
  76. }
  77.  
  78. require_once self::$map[$class_name];
  79. }
  80.  
  81. /**
  82.  * 
  83.  * 
  84.  *  
  85.  */
  86. private http://www.php.net/static function generateMap()
  87. {
  88. self::$map = http://www.php.net/array();
  89.  
  90. $cached_map = APP_DIR . '/var/cache/autoload.php';
  91.  
  92. self::scan(DAERON_DIR);
  93. self::scan(APP_DIR);
  94.  
  95. $compiled = &#092;"<?phpn\";
  96. $compiled .= 'self::$map = ' . http://www.php.net/var_export(self::$map, true) . &#092;"n?>\";
  97.  
  98. file_write($cached_map, $compiled);  
  99. }
  100.  
  101. /**
  102.  * 
  103.  * 
  104.  *  
  105.  */
  106. private static function scan($dir)
  107. {
  108. $match = array();
  109.  
  110. $rd = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), true);
  111. foreach ($rd as $file)
  112. {
  113. if(!is_file($file->getPathname())) continue;
  114.  
  115. if (preg_match('/([a-z0-9_]+).(class|interface).php$/i', $file->getPathName(), $match))
  116. {
  117. self::$map[strtolower($match[1])] = $file->getPathName();
  118. }
  119. }  
  120. }
  121. }
  122.  
  123. function __autoload($class_name)
  124. {
  125. Autoloader::load($class_name);
  126. }
  127. ?>


Porównanie szybkości (dla stosunkowo małej liczby plików):
- czytanie mapy z generowaniem: 35,67 ms
- czytanie z cache'owanej mapy: 00,57 ms

Kod Vengeance'a:
- czytanie mapy z generowaniem: 129.35 ms

A czytanie z cache wygląda identycznie smile.gif

Napisany przez: Vengeance 13.03.2005, 01:10:23

chmolu: Zauważ, że to Ty trzymasz każdą klasę w innym pliku!

A jak byś chciał dodać do frameworka np. AdoDB, Smarty czy inne rzeczy? One nie stosują twojego nazewnictwa.

Po to właśnie ten mój* skrypt przeszukuje wszystkie pliki wczytując je!

Skoro twoje nazewnictwo jest tak ustandaryzowane to w jakim celu w ogóle tworzyć mape ? przecież wystarczy zrobić

require $className . '.class.php'; hehe

* przypominam, iż nie czuje się właścicielem skryptu smile.gif posklejałem kod z wielu wypowiedzi innych osób i wrzuciłem smile.gif

Napisany przez: chmolu 13.03.2005, 09:44:36

@Vengeance: to prawda, nie każdy musi trzymać każdą klasę w innym pliku. Dla mnie takie rozwiązanie jest najwygodniejsze.

Cytat
A jak byś chciał dodać do frameworka np. AdoDB, Smarty czy inne rzeczy? One nie stosują twojego nazewnictwa.

Autoloader u mnie ma służyć tylko do ładowania klas należących do jądra i aplikacji. Biblioteki typu Smarty i AdoDB zaliczam do "zewnętrznych" części aplikacji, którymi zająć musi się odpowiednia akcja/widok. Poza tym zauważ, że w przypadku tych bibliotek musimy włączyć tylko jeden główny plik, który już sam zajmuje się potrzebnymi mu plikami.
(btw: główny plik Smarty ma nazwę Smarty.class.php, więc pasuje do mojego nazewnictwa tongue.gif)

Cytat
Skoro twoje nazewnictwo jest tak ustandaryzowane to w jakim celu w ogóle tworzyć mape ? przecież wystarczy zrobić

require $className . '.class.php'; hehe

Ale dzięki mapie struktura katalogów jest dowolna. Mogę sobie w dowolny sposób podzielić na różne katalogi i podkatalogi, a gdy zajdzie potrzeba zmiany struktury wystarczy tylko wygenerowac mapę i nie trzeba grzebać w kodzie i poprawiać wszystkich ścieżek.

Napisany przez: bela_666 13.03.2005, 10:44:30

@chmolu a jak zmienisz ścieżke do smarty ? biggrin.gif

Napisany przez: chmolu 13.03.2005, 12:43:09

Nie używam Smarty więc nie ma żadnego problemu biggrin.gif

// btw: poprawiłem troszkę ten kod

Napisany przez: Vengeance 13.03.2005, 13:43:28

@chmolu: ty nie. Ale z tego co wynika z twoich wypowiedzi to co piszesz
to framework. Pewnie kiedys ktos inny z niego bedzie korzystac i moze chciec takze korzystac ze smarty smile.gif

Napisany przez: chmolu 13.03.2005, 13:55:45

Zapewniam cię, że nie będzie to żaden problem aarambo.gif
Ale mam nadzieję, że zamiast Smarty ludzie będą używali systemu wbudowanego w mój framework (wzorowany na WACT/ASP/JSP).
Jak skończę, to zarzucę tu kod do oceny.

Napisany przez: Ociu 4.05.2005, 18:16:29

Pozwole sobie odkopać temat winksmiley.jpg

Zastanawiam się czy nie użyć pliku xml ładowania klas. Coś na wzór:

  1. <Classes>
  2.  
  3. <dirname>
  4. <class name='jakas tam nazwa'>file.php</class>
  5. <class name='jakas tam nazwa2'>file2.php</class>
  6. </dirname>
  7.  
  8. <dirname2>
  9. <class name='jakas tam nazwa3'>file3.php</class>
  10. <class name='jakas tam nazwa4'>file3.php</class>
  11. </dirname2>
  12.  
  13. </Classes>


Potem bardzo prosto: simplexml, loader instacji (w moim przypadku) i tyle smile.gif

Iść dalej tym tropem, czy zacząć myśleć nad czymś innym ?

Napisany przez: davidD 5.05.2005, 00:09:50

Tylko właściwie... po co XML do zwykłego przypisania nazwy pliku do nazwy klasy? IMO jest to lekkie komplikowanie sobie całej sprawy - no chyba, że chcemy przechowywać jakieś dodatkowe informacje.

Mnie osobiście podoba się pomysł z iteratorem, szukającym klas po wszystkich plikach o określonym wzorcu, w podanej ścieżce (ścieżkach). Można z tego zrobić ładną tablicę, zserializować ją i do pliku... Co prawda trzeba się troszkę pobawić samym pisaniem tego generatora mapy, ale za to później, przy zmianie położenia/nazw plików zaoszczędzi się duużo czasu.

Napisany przez: matid 5.05.2005, 06:37:45

Ja to widzę tak:
1. Crowler, szuka po określonych folderach w poszukiwaniu plików php zawierających klasy. Będzie miał możliwość wyszukiwania z filtrowaniem nazw plików, nazwach klas itp. Być może po dodaniu opcji filtracji czas generowania mapy się zwiększy nieco, ale na jakoże jest to czynność wykonywana rzadko, można sobie na to pozwolić.
2. Dodatkowy plik XML z klasami zewnętrznymi. W tym pliku będą zapisane w postaci XML dodatkowe klasy, które autoloader ma obsługiwać. Plik byłby parsowany i razem z wynikami crowlera zapisywany w pliku php w postaci tablicy.

Napisany przez: Ociu 5.05.2005, 07:22:02

Cytat(matid @ 2005-05-05 07:37:45)
2. Dodatkowy plik XML z klasami zewnętrznymi. W tym pliku będą zapisane w postaci XML dodatkowe klasy, które autoloader ma obsługiwać.

O to mi chodziło winksmiley.jpg

Śpieszyłem się i zapomaniałem dopisać.

Napisany przez: chmolu 5.05.2005, 08:24:02

IMO pliki XML w tym wypadku są niepotrzebne. Szybciej i wygodniej będzie to zrobić przy pomocy plików ini

Napisany przez: Nievinny 5.05.2005, 17:06:02

XML to przeładowanie, a nam zależy na czasie, nie? Chociaż wszystko da się zrobić ;P

Napisany przez: matid 5.05.2005, 17:55:53

Cytat(Nievinny @ 2005-05-05 18:06:02)
XML to przeładowanie, a nam zależy na czasie, nie? Chociaż wszystko da się zrobić ;P

Przecież parsowanie pliku XML będzie się odbywać tylko w wypadku rekonstruowania mapy, co za często się dziać nie powinno, czyż nie? Zyskujemy natomiast na przejrzystości i strukturze pliku konfiguracyjnego.

Napisany przez: bigZbig 10.05.2005, 09:28:27

@matid -> zauwazylem u Ciebie duze przywiazanie do technologi xml. Wiesz mnie sie zawsze wydawalo, ze ten jezyk zostal powolany do zycia po to, aby zapewnic przenosnosc danych pomiedzy roznymi platformami. Nie wiem czy w tym przypadku zapewnienie tej cechy jest pozadane. Uwazam ze próbujesz robic cos na sile utrudniajac sobie zywot.

Napisany przez: Nievinny 10.05.2005, 16:01:11

@matid -> ale tą mapę musisz za każdym uruchomnienie skryptu załadować i to będzie się dziać często...

Napisany przez: matid 10.05.2005, 17:36:31

Cytat(Nievinny @ 2005-05-10 17:01:11)
@matid -> ale tą mapę musisz za każdym uruchomnienie skryptu załadować i to będzie się dziać często...

Przecież nie chodzi mi o trzymanie mapy w XMLu, bo jest to totalną pomyłką. Nie jestem aż tak przywiązany do XMLa żeby tracić na wydajności. Chodzi mi o trzymanie w pliku XML dodatkowych ścieżek do przeszukiwania, czyli parsowane będzie to tylko przy regenerowaniu mapy.

Napisany przez: Nievinny 10.05.2005, 18:24:33

Czyli trzymasz tylko katalogi do parsowania? To nie ma sensu, ponieważ powinno się parsować wszystkie katalogi i uzyskać pełną mapę klas.

Napisany przez: Ociu 10.05.2005, 19:40:08

Cytat(matid @ 2005-05-10 18:36:31)
Chodzi mi o trzymanie w pliku XML dodatkowych ścieżek do przeszukiwania, czyli parsowane będzie to tylko przy regenerowaniu mapy.

Przemyślałem sposób z XML i stwierdziłem, ze pomysł jest chybiony. Tak czy inaczej trzeba za każdym razem parsowac plik, traci się na tym cenny czas... A gdy tych dodatkowych ścieżek jest dużo ? wtedy czas wydłuża się do sek, a nie tysiącznych sekund.

Napisany przez: matid 10.05.2005, 20:34:10

Cytat(Ociu @ 2005-05-10 20:40:08)
Przemyślałem sposób z XML i stwierdziłem, ze pomysł jest chybiony. Tak czy inaczej trzeba za każdym razem parsowac plik, traci się na tym cenny czas... A gdy tych dodatkowych ścieżek jest dużo ? wtedy czas wydłuża się do sek, a nie tysiącznych sekund.

Nie wiem jak sobie wyobrażasz wygenerowanie mapy w tysięczne sekund. W średniej wielkości projekcie (jeszcze bez czytania dodatkowych ścieżek, ok. 90 klas w różnych plikach) trwa to ok. 0,4 s. Czytanie z mapy to ok 0.0008s. Więc nie wiem jakbyś chciał kombinować, ale IMO niemożliwym jest generowanie mapy w tysięczne sekundy. Oczywiście mówię tutaj o mapie nie wymuszającej żadnego przystosowania nazwy pliku itp.

Napisany przez: Vengeance 10.05.2005, 21:26:35

" trwa to ok. 0,4 s"

lol :] Mój kod (a ogolnie ten co podałem powyżej gdzieś), który rekurencyjnie schodzi katalogami w dół i zapisuje znalezione klasy już teraz wykonuje się czasem więcej niż 20 sekund :]
Oczywiście robione jest to tylko raz, przy pierwszym uruchomieniu.

Napisany przez: matid 10.05.2005, 21:46:19

Cytat(Vengeance @ 2005-05-10 22:26:35)
" trwa to ok. 0,4 s"

lol :] Mój kod (a ogolnie ten co podałem powyżej gdzieś), który rekurencyjnie schodzi katalogami w dół i zapisuje znalezione klasy już teraz wykonuje się czasem więcej niż 20 sekund :]
Oczywiście robione jest to tylko raz, przy pierwszym uruchomieniu.

Moja klasa autoloadera jest w znaczej części oparta na wypowiedziach i nawet fragmentach kodów z tego tematu, więc nie zdziwiłbym się, gdyby funkcja skanująca foldery (lub jej założenia) była już przez ciebie cytowana.

Prędkości w dostępie do plików mogą wynikać z różnego systemu operacyjnego (ja testuję lokalnie na linuksie, który ma świetny system buforowania dysków), procesora, pamięci, obciążenia, itp.
U mnie przeskanowanie ok 100 plików (ok. 1 MB) i stworzenie mapy zawierającej 90 klas trwa ok. 0,4s.

Oprócz tego mam napisany bardzo fajny system automatycznego regenerowania mapy, cachowania wyników, itp.

Napisany przez: Ociu 11.05.2005, 09:08:31

Czepiasz się szczegółów, liczba była podana dla przykładu.

Napisany przez: Vengeance 11.05.2005, 20:45:37

To nie było czepianie. Chodziło mi o sposób w jaki @matid osiągnął tak dobry jak dla mnie wynik. U mnie już powoli php robi time out przy generowaniu :] (WinXP, 950 MHz, 256mb RAM)

Napisany przez: matid 11.05.2005, 21:21:56

Dla zainteresowanych - moja klasa Autoloader: http://matid.emmatrade.pl/files/Autoloader.class.phps

I jeszcze raz moje testy:
Sprzęt: Pentium 4 2,4 GHz, 1024 MB RAM, dysk twardy 7200 RPM (system plików: ReiserFS)
System: Ubuntu Linux 5.04 Hoary Hedgehog

Dane testowe:
Rozmiar: 913.6 KB
Liczba plików: 120
Liczba klas: 90

Czas generowania mapy po raz pierwszy po włączeniu komputera: 0.872271060944s
Czas generowania mapy po raz kolejny (część plików mogła być w buforze dysków): 0.427920103073

Napisany przez: hawk 12.05.2005, 09:45:30

Skoro temat zrobił się popularny i każdy chce mieć swój autoloader oparty o mapy (co uważam za słuszne), to warto załatwić jedną prostą rzecz. Kompatybilność. Taka mapa to na tyle prosta struktura, że pewnie wszędzie wygląda tak samo:

  1. <?php
  2. $map=http://www.php.net/array(
  3. 'nazwaklasy' => 'ścieżkadoklasy',
  4. );
  5. ?>

Wtedy każdy może sobie generować mapę jak mu się podoba, szybciej lub wolniej. I każdą mapę można wrzucić do każdego autoloadera. Dobrze myślę?

Kompatybilność jest akurat bardzo ważna w przypadku autoloadera. Bo jeżeli napiszę jakąś bibliotekę, to wykorzystam swój pomysł. I ta biblioteka musi działać z biblioteką autorstwa kogoś innego, kto też oparł ją o swój autoloader. A autoloader może działać tylko jeden, więc jeżeli jeden do drugiego nie pasuje, to całość wychodzi do dupy.

Napisany przez: bigZbig 12.05.2005, 09:49:20

@hawk -> Amen

Napisany przez: dr_bonzo 26.05.2005, 15:15:11

...mam i ja.

Stworzylem swojego autoloadera:
- w konfigu podaje katalogi do rekursywnego przejrzenia
- podaje rozszerzenia plikow ktore maja byc tokenizowane

- mapa jest tworzona w std. formacie: nazwa_klasy => sciezka_do_pliku
- i zapisywana w formacie .ini (moze dorobie wybor ini | php)

I co najwazniejsze -- cachuje wyniki tokenizowania plikow (to nie to samo co 'mapa') -- tokenizowane sa tylko te co zostaly zmodyfikowane (okreslane przez filemtime()), co daje znaczna oszczednosc czasu. Zapisuje tablice:

  1. <?php
  2.  
  3. http://www.php.net/array (
  4. [ 'nazwa_pliku' ] ->
  5. (
  6.     [ 'classes' ] ->
  7.     (
  8.         [ 0 ] -> 'klasa_1';
  9.         [ 1] - > itd...
  10.     )
  11.     [ 'modification_time' ] = 111123423423423; // czas modyfikacji
  12.     { ['valid' ] = TRUE }
  13. )
  14. )
  15. ?>


Jak to wyglada w praktyce:
- wczytuje ww. cache
- przegladam katalogi i robie liste znajdujacych sie tam plikow
- loop po tych wszystkich plikach: porownuje czas modyfikacji pliku z czasem zapisanym w cache
- jesli plik zostal zmieniony to go tokenizuje i aktualizuje cache (+ ustawienie znacznika 'valid' w cache)
- jesli nie zostal zmodyfikowany to tylko ustawienie znacznika 'valid' w cache
- po przejrzeniu wszystkich plikow, usuwam te wpisy w cache, ktore nie maja znacznika 'valid', i usuwam znacznik 'valid' z tych, ktore go maja (tylko po to zeby go (znacznika) nie zapisac w cache)
- uzyskuje tablice z wpisami
nazwa_pliku --> klasy/interfejsy w min sie znajdujace + czas modyfikacji pliku
- zapisuje to cache
- na podstawie tego cache tworze mape

Wlasciwosci
- nowe pliki ktorych nie bylo w cache sa tokenizowane i dodawane do cache i mapy
- pliki ktore byly w cache a teraz nie istnieja (nie ma ich w podanych w configu katalogach) sa usuwane z cache i mapy
- pliki ktore byly w cache i nie zostaly zmienione nie sa tokenizowane (duuuza oszczednosc czasowa)
- pliki ktore byly w cache i zostaly zmodyfikowane sa ponowanie tokenizowane -> aktualizacja cache

Czasy wykonania:
6 klas / 5 plikow:
0.1137sec // bez cache
0.0041 sec // z cache

dodanie dodatkowych plikow i klas, w sumie
355 plikow / 138 klas
4.3358 sec // bez cache
0.1672 z cache// z cache

modyfikacja 3 z tych plikow (glowne pliki Smartyego -- 161kB) i utworznie mapy, z cache
0.7571 sec




Kodu jest sporo: > 470 linii, jak dokoncze to dam linka.
Pozostaje jeszcze fopisac __autoload()'a.

Napisany przez: Vengeance 26.05.2005, 18:37:05

Z twojego opisu wynika mi, że za każdym uruchomieniem klasy następuje rekurencyjne przejrzenie katalogów. Jeśli tak jest, to jest to tragiczne rozwiązanie :]

Po co te wszystkie "czasy edycji plikow" itp. Na co ci oszczednosc czasowa przy generowaniu mapy, skoro zalozeniem glownym jest ze robisz to tylko raz! Ewentualnie potem gdy cos zmieniasz w strukturze ponawiasz proces generowania.

Twoje "Jak to wyglada w praktyce" wg mnie powinno ograniczac sie tylko do:
1. przejrzenie rekurencyjne wskazanych katalogów
2. tokenizacja każdego pliku celem wyłapania zawartych w nim klas
3. zapisanie wszystkich powiązań klasa=>plik do .ini czy tam .php

A sam główny skrypt powinien tylko ów .ini lub .php wczytywać, zaś gdy go nie ma powinien uruchamiać generowanie ów pliku :]

Taka moja wersja

Napisany przez: dr_bonzo 26.05.2005, 19:12:28

Cytat
Z twojego opisu wynika mi, że za każdym uruchomieniem klasy następuje rekurencyjne przejrzenie katalogów. Jeśli tak jest, to jest to tragiczne rozwiązanie :]


Jesli mapa istnieje to ten skrypt nie jest uruchamiany.

Cytat
Na co ci oszczednosc czasowa przy generowaniu mapy, skoro zalozeniem glownym jest ze robisz to tylko raz!


Przy rozbudowie aplikacji i sprawdzaniu poprawnosci jej dzialania nie bedzie mi sie chcialo czekac tych 5 czy 20 sekund na zobaczenie rezultatow.

"W praktyce" to robi + robi to szybciej jesli moze.

Napisany przez: hawk 27.05.2005, 20:47:33

Ciekawe rozwiązanie. Faktem jest, że mi by się nie chciało smile.gif. Zwłaszcza że ja raczej na początku mam rozplanowane klasy, a potem głównie edytuję zawartość nie ruszając struktury.

Co do plików ini... rezultat ten sam. Dla ciekawości, sprawdzałeś, czy są szybsze od zwykłego php? Chociaż wada jest taka, że ja bym w pliku php bardzo chętnie umieszczał mapę wykorzystując jakąś zmienną - główny katalog projektu. Po to, aby ktoś, kto tego używa, nie musiał generować dla siebie mapy tylko dlatego, że u niego pliki php są w innym miejscu na dysku.

Napisany przez: dr_bonzo 5.06.2005, 02:20:39

W koncu. Kod przepisany od nowa, lecz jeszcze nie dopieszczony:
http://northslope.lap.pl/dev/Autoloader/0.2-dev/

a tu zrodla do pobrania:
http://northslope.lap.pl/dev/Autoloader/autoloader-0.2-dev.tar.gz

Kod
generowanie mapy (cache puste):
Array
(
   [files_found] => 468
   [files_parsed] => 468
   [classes_found] => 233
)
Map generation time: :: 12.85658 sec

a z cache (nic nie zmienione):
Array
(
   [files_found] => 468
   [files_parsed] => 0
   [classes_found] => 233
)
Map generation time: :: 0.23078 sec

map from php: :: 0.00076 sec
map from ini: :: 0.00412 sec

php szybsze od INI

PSy: statystki (files_parsed)w udostepninych zrodlach sa nieprawidlowe, te wyzej sa ok.
Testowane na P3 @ 560

Napisany przez: Imperior 5.06.2005, 13:02:34

dr_bonzo: http://wiki.php.pl/index.php/RecursiveDirectoryIterator
Czyż nie łatwiej?

Napisany przez: hawk 5.06.2005, 13:23:14

Fajny kod. Nawet zdążyłem już użyć biggrin.gif.

Kilka uwag:
- nie można podać extension np. "class.php", co jest bardzo przydatne, bo pozwala pominąć unit testy ("test.php"). Musiałbyś zmienić metodę isFileExtensionOnTheList tak, aby po prostu sprawdzała, czy nazwa pliku kończy się jednym z podanych wzorców
- przy pierwszym wywołaniu dostaję warning, że cache jeszcze nie istnieje. Potem jest OK.
- RecursiveDirectoryIterator uprościłby sprawę
- przydatną funkcją byłoby parsowanie tagów phpdoc (jeżeli są), aby pomijać klasy "wewnętrzne", czyli takie, które wykorzystywane są tylko w obrębie danego pliku i nie ma potrzeby wystawiać ich na zewnątrz. Ja takich mini-klas używam, i brakuje mi możliwości zrobienia prywatnej klasy wewnętrznej jak w Javie...

Napisany przez: dr_bonzo 5.06.2005, 14:00:34

Cytat
- nie można podać extension np. "class.php", co jest bardzo przydatne, bo pozwala pominąć unit testy ("test.php"). Musiałbyś zmienić metodę isFileExtensionOnTheList tak, aby po prostu sprawdzała, czy nazwa pliku kończy się jednym z podanych wzorców

[DONE] Jest w TODO

Cytat
- przy pierwszym wywołaniu dostaję warning, że cache jeszcze nie istnieje. Potem jest OK.

Kod jeszcze "niedopieszczony" wersja dev -- dziala ok, pokazuje wszystkie bledy na ekranie, potem dodam malpy do funkcji

Cytat
- RecursiveDirectoryIterator uprościłby sprawę

[DONE] Zgadza sie. Poprawie

Cytat
- przydatną funkcją byłoby parsowanie tagów phpdoc (jeżeli są), aby pomijać klasy "wewnętrzne", czyli takie, które wykorzystywane są tylko w obrębie danego pliku i nie ma potrzeby wystawiać ich na zewnątrz.

Troche roboty bedzie. A jaki znacznik odpowiada za plikowy zasieg klasy?

PS. [files_found] to liczba znalezionych plikow z wybranymi rozszerzeniami
-------------------------------------------
EDIT

podglad kodu online
http://northslope.lap.pl/dev/Autoloader/autoloader-0.3.0-sources/

paczka
http://northslope.lap.pl/dev/Autoloader/autoloader-0.3.0.tar.gz

Napisany przez: hawk 12.06.2005, 22:16:47

A co powiecie na taką implementację?

  1. <?php
  2. $i = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(PHIEND_HANDLE_DIR));
  3. $i = new KeyFilter($i, '.class.php$');
  4. $w = new PhiendClassMapWriter('./handle_map.php');
  5. $w->setTargetDir('e:/Web Files/lib/phiend2/phiend.handle', 'PHIEND_HANDLE_DIR');
  6.  
  7. $generator = new ClassMapGenerator();
  8. $generator->addIterator($i);
  9. $generator->setCreator(new BasicClassMapCreator());
  10. $generator->addFilter(new SortClassMapFilter());
  11. $generator->setWriter($w);
  12.  
  13. $generator->run();
  14. ?>

Wynik:
  1. <?php
  2.  
  3. /*
  4.  * File generated by phiend.autoload
  5.  */
  6.  
  7. http://www.php.net/define('PHIEND_HANDLE_DIR', 'e:/Web Files/lib/phiend2/phiend.handle');
  8.  
  9. $aMap = http://www.php.net/array(
  10. 'IHandle' => PHIEND_HANDLE_DIR . '/IHandle.class.php',
  11. 'BasicHandle' => PHIEND_HANDLE_DIR . '/BasicHandle.class.php',
  12. // (ciach)
  13. 'SerializedHandle' => PHIEND_HANDLE_DIR . '/SerializedHandle.class.php',
  14. );
  15.  
  16. Autoloader::addMap($aMap);
  17.  
  18. ?>

Mogę tu zrobić praktycznie wszystko, dzięki dobremu rozbiciu na klasy i dziedziczeniu. W pełni wykorzystuje SPL - do tego stopnia, że moje php 5.0.3 jest za stare i musiałem wykomentować kilka linii winksmiley.jpg.
Kod: http://cvs.sourceforge.net/viewcvs.py/phiend/phiend2/phiend.autoload/
Zip: http://phiend.sourceforge.net/misc/phiend-autoload.zip

Napisany przez: cagrET 13.06.2005, 13:24:34

offtopic.gif

hawk: widzę, że używasz "notacji węgierskiej", ciekawi mnie czy może czytałeś ten artykuł: http://www.joelonsoftware.com/articles/Wrong.html > I’m Hungary . Joel pisze tam o tym, że w zły sposób używa się notacji węgierskiej, nie w taki jakim intencją autora tej notacji było.

Napisany przez: hawk 13.06.2005, 20:10:13

Nie, nie czytałem do tej pory. Przedrostki określające typ stosuję tylko w php, bo tak jest mi wygodniej. W językach ze ścisłą typizacją są mi niepotrzebne.

Napisany przez: matid 13.06.2005, 20:30:28

Cytat(hawk @ 2005-06-05 14:23:14)
- przydatną funkcją byłoby parsowanie tagów phpdoc (jeżeli są), aby pomijać klasy "wewnętrzne", czyli takie, które wykorzystywane są tylko w obrębie danego pliku i nie ma potrzeby wystawiać ich na zewnątrz. Ja takich mini-klas używam, i brakuje mi możliwości zrobienia prywatnej klasy wewnętrznej jak w Javie...

Który znacznik phpDoc oznacza klasy wewnętrzne?
Edit: Ale ja jestem ślepy, że nie widziałem wcześniej tego pytania ohmy.gif. Nie zmienia to faktu, że nie udzielono na nie odpowiedzi, więc dołączam się do grona pytających tongue.gif

BTW. Bardzo mi się twój kod podoba, szczególnie te wszechobecne interface i rozdrabnianie tego na wiele klas. Chyba trzeba przepisać mój Autoloader tongue.gif

Napisany przez: dr_bonzo 13.06.2005, 21:03:51

cagrET: link do swietnego serwisu.

Cytat
Z jakiegoś powodu większość ludzi zdaje się rodzić bez tej części mózgu, która jest potrzebna do rozumienia wskaźników
, LOL

hawk: do takiego OOP troche mi jeszcze brakuje smile.gif

notacja wegierska: pamietam jak na poczatku '90 czytalem o tym w pc kurierze -- nawet mam wyrwane strony z tym artem smile.gif

Napisany przez: hawk 13.06.2005, 21:51:34

@dr_bonzo i matid:
Niestety, phpDocumentor nie ma, AFAIK, takiego znacznika. Szkoda. Chociaż nic nie stoi na przeszkodzie taki sobie stworzyć. Precedens jest - w Javie wiele narzędzi posiada swoje własne znaczniki javadoc. Może @no-autoload?

Napisany przez: matid 13.06.2005, 22:12:12

Cytat(hawk @ 2005-06-13 22:51:34)
@dr_bonzo i matid:
Niestety, phpDocumentor nie ma, AFAIK, takiego znacznika. Szkoda. Chociaż nic nie stoi na przeszkodzie taki sobie stworzyć. Precedens jest - w Javie wiele narzędzi posiada swoje własne znaczniki javadoc. Może @no-autoload?

Osobiście byłbym nie za wprowadzeniem znacznika mówiącego o wykluczeniu klasy z autoładowania (choć może to też by był dobry pomysł), lecz znacznika mówiącego o zasięgu klasy.
Czyli prędzej coś jak:
@range: file
@class-range: file
@classrange: file

Oprócz tego można by się pokusić o bardziej rozbudowany sysem autoloadingu, który np. pozwalałby na uwzględnienie takich czynników jak zakres folderowy (@range: directory), no i oczywiście @range: global, czyli to samo co w przypadku gdy pomijamy parametr zasięgu.

To takie moje 3 grosze.

BTW. Jak widzicie wykorzystanie takiego autoloadera w systemie? Czy korzystacie z niego dla całego kodu, czy tylko dla bibliotek? Bo dość niewygodne może być korzystanie z tego w modułach/akcjach. Bo akcja powinna być unikalna w obrębie modułu, ale może się pojawić w innych.

Napisany przez: hawk 16.06.2005, 12:17:19

@matid: Pomysł wręcz genialny w swojej prostocie. Popieram. Jeżeli mamy wybrać najlepszą nazwę, to ja proponuję

Kod
@scope file

Scope, bo to bardziej odpowiednie słowo niż range. Bez dwukropka, bo chyba tagi phpdoc nie używają normalnie dwukropka? W każdym razie ja nigdy nie pisałem...

Co do zastosowania: generalnie używałbym na biblioteki. Albo na intensywnie wykorzystywaną część kodu - zawsze mogę wygenerować sobie mapę np. dla części Modelu. Natomiast dla akcji - nie warto, bo i tak w akcjach musi być porządek, wiadomo dobrze gdzie jaka akcja jest, i wykonujemy jedną akcję na raz. Szkoda mapę robić.

Napisany przez: matid 16.06.2005, 15:48:55

No rzeczywiście, @scope będzie lepsze.
Spróbuję to jakoś zaimplementować, zobaczymy, co z tego wyjdzie smile.gif

Cytat(hawk)
Bez dwukropka, bo chyba tagi phpdoc nie używają normalnie dwukropka? W każdym razie ja nigdy nie pisałem...

Literówka winksmiley.jpg Miało być bez.
Cytat(serafin)
Ludzie rozwodzicie sie nad mapa, ktora mozna napisac nawet recznie. A wy tworzycie jakies skrypty bog wie po co i tworzycie nowa teorie do tego. Bezsensu. Zamiast napisac prosty skrypt do tworzenia mapy wy tworzycie 50 klas do wygenerowania tablicy 100 klas.

Ja osobiście wole mieć tablice generowane automatycznie. Nie dość że mogę sobie dowolnie zmieniać położenie klas to w dodatku skrypt przy następnym uruchomieniu sam uaktualnia mapę.

A co do podzielenia na 50 klas. Pierwsza wersja mojego skryptu do autoloadingu to była jedna klasa. Ale po przejrzeniu kodu @hawk widzę, ile jeszcze mojemu OOP brakuje. Dopiero podział tej klasy na kilka mniejszych pokazał mi, o ile większe możliwości w późniejszym rozszerzaniu daje OOP.

Napisany przez: hawk 16.06.2005, 18:31:01

@serafin:

1) Nikt ci nie broni tworzyć takiej mapy ręcznie. Gratuluję odwagi. I nadmiaru wolnego czasu.

2) Bezsensu. Zamiast napisac prosty skrypt do wyświetlania stron wy tworzycie 50 klas, jakieś wzorce, MVC, biblioteki. Przecież każdy wie, że najlepiej jest wrzucić wszystko do jednego pliku, a całe to OOP to jedna wielka ściema.

Pokaż swój autoloader, a wtedy możemy porozmawiać, co jest lepsze, a co jest bez sensu.

Napisany przez: hawk 16.06.2005, 20:42:41

Cytat("serafin")
Hawk: Wykorzystales 10 plikow na to co mozna zrobic w jednej klasie (i nie powiedziane ze gorzej).

No dobrze, porozmawiajmy wobec tego konkretnie. Na przykładach. Zaraz zobaczymy, czy rzeczywiście nie gorzej.

1. Chcę włączyć do mapy pliki z katalogów foo/first i foo/second, ale absolutnie nie z foo/third, bo tam są przykłady, które niepotrzebnie zaśmiecałyby mapę.

2. Chcę przeparsować tylko pliki o rozszerzeniu .class.php. Wszystko inne jest niepotrzebne.

3. Chcę, żeby w mojej mapie klasy były posortowane alfabetycznie. Dzięki temu mogę łatwo sprawdzić, jakie klasy są w moim pakiecie, a ponieważ jest to bez różnicy dla przetwarzania, nie ma żadnego powodu, żeby tego nie zrobić. No chyba że narzędzie jest gorsze...

4. Chcę wygenerować mapę dla zewnętrznego pakietu, który inaczej nazywa swoje klasy. Jak narzędzie, to uniwersalne, a nie ograniczone do mojego kodu, prawda? Pakiet jest w zupełnie innym miejscu na dysku.

5. Chcę w wersji produkcyjnej wrzucić kilka klas, które zawsze zawsze są używane, do jednego pliku, żeby przyspieszyć działanie i żeby prościej to wyglądało dla użytkownika końcowego. To częsta praktyka - weźmy np. mojavi.

6. Chcę za pomocą mojego autoloadera wygenerować mapę, która może być od razu wykorzystana w twoim systemie. Czyli plik wygląda tak samo. Chcę mieć uniwersalny autoloader, którego mogą używać inni, a nie nieprzenośne narzędzie.

7. Mam 2 mapy, do 2 różnych pakietów, i chcę obydwie naraz wbić do autoloadera.

Wierzę ci, że można to zrobić w jednej klasie. Która do tego będzie mniejsza niż suma tych plików i w ogóle lepsza. Tylko nie wiem, jak...

A co do tych 10 plików... rozmijasz się z prawdą winksmiley.jpg. To są cztery pliki, plus trzy interfejsy (do niczego niepotrzebne, możesz je spokojnie wywalić, ale ja mam do interfejsów sentyment), plus trzy klasy dziedziczące z klas podstawowych (miałem kaprys zrobić coś extra, ale przecież w ogóle nie musisz ich wykorzystywać). Na podobnej zasadzie możemy zacząć liczyć unit testy i pliki readme.

---------------------------------------------
Cytat
OOP ma sens owszem, ale nie w przypadku gdy tworzy sie klasy na 1-3 metod

Wiesz, co to jest Observer? Abstract Factory? Iterator? Registry? Intercepting Filter? Chain of Responsibility? Command? Strategy? Takie różne wzorce projektowe, ale niestety bez sensu, bo mają za mało metod.

Cytat
Oczywiscie ze rozbijanie problemu na mniejsze czescie jest bardzo dobra technika ale sa pewne granice. Czasami mozna kilka z tych czesci polaczyc w jedna wieksza calosc bez szkody dla ostatecznego rozwiazania.

To ty zacząłeś tą dyskusję, więc odpowiedz: dlaczego rozbijanie problemu na mniejsze klasy jest szkodliwe? Bo z tego, co piszesz, wynika, że wg ciebie jest.

Napisany przez: matid 16.06.2005, 22:08:29

Cytat(serafin)
Ma to sens?

A dlaczego nie? Mi osobiście bardzo się podoba sposób potraktowania typów zmiennych jako klas, a samych zmiennych jako obiektów.
Oprócz php programuję też w Javie i uważam to za świetne rozwiązanie, więc dlaczego by nie używać tego w php?

Teraz pewnie padnie pytanie, co mi da takie "utrudnianie" sobie życia. Ale zdecydowaną korzyścią płynącą z takiego rozwiązania jest możliwość programowania własnych typów danych, czego przy standardowym traktowaniu zmiennych przez php nie zrobisz.

Do notki poniżej: Skoro w php są typy podstawowe, to dlaczego miałoby nie być rozszerzonych? Nie będę nikogo nawracał rzucenia podstawowych typów w php na rzecz klas, bo dla niektórych to może być masochizm. Ja osobiście nie mam nic przeciwko smile.gif
A co do pisania w php systemu operacyjnego. Wiem, że to było pytanie retoryczne, ale odpowiem - bo php się do tego kompletnie nie nadaje tongue.gif

A teraz wracając do autoloadera. Zaimplementowałem parsowanie tagów phpDoc, ale jeszcze borykam się z jedym problemem.
Powiedzmy, że moja mapa wygląda tak:
  1. <?php
  2.  
  3. /*
  4. * File generated by OpenForce Autoloader
  5. */
  6.  
  7. $aMap = http://www.php.net/array(
  8. 'GLOBAL' => http://www.php.net/array(
  9. 'IteratorFilter' => '/var/www/matid/Current/Autoloader/lib/IteratorFilter.class.php',
  10. 'iClassMapGenerator' => '/var/www/matid/Current/Autoloader/lib/autoloader/interfaces/ClassMapGenerator.interface.php',
  11. 'iClassMapEntry' => '/var/www/matid/Current/Autoloader/lib/autoloader/interfaces/ClassMapEntry.interface.php',
  12. 'iClassMapWriter' => '/var/www/matid/Current/Autoloader/lib/autoloader/interfaces/ClassMapWriter.interface.php',
  13. 'iClassMapCreator' => '/var/www/matid/Current/Autoloader/lib/autoloader/interfaces/ClassMapCreator.interface.php',
  14. 'ClassMapGenerator' => '/var/www/matid/Current/Autoloader/lib/autoloader/ClassMapGenerator.class.php',
  15. 'OFClassMapWriter' => '/var/www/matid/Current/Autoloader/lib/autoloader/OFClassMapWriter.class.php',
  16. 'ClassMapEntry' => '/var/www/matid/Current/Autoloader/lib/autoloader/ClassMapEntry.class.php',
  17. 'ClassMapCreator' => '/var/www/matid/Current/Autoloader/lib/autoloader/ClassMapCreator.class.php',
  18. 'ClassMapWriter' => '/var/www/matid/Current/Autoloader/lib/autoloader/ClassMapWriter.class.php',
  19. 'ClassNotFoundException' => '/var/www/matid/Current/Autoloader/lib/exceptions/ClassNotFoundException.class.php',
  20. 'Autoloader' => '/var/www/matid/Current/Autoloader/lib/Autoloader.class.php',
  21. 'Test2' => '/var/www/matid/Current/Autoloader/test/1/2.php',
  22. 'Test3' => '/var/www/matid/Current/Autoloader/test/3.php',
  23. ),
  24. '/var/www/matid/Current/Autoloader/test/1' => http://www.php.net/array(
  25. 'Test1' => '/var/www/matid/Current/Autoloader/test/1/1.php',
  26. ),
  27. );
  28.  
  29. Autoloader::addMap( $aMap );
  30.  
  31. ?>

Klasa Test1 ma ustawiony zasięg directory, reszta jest global. I teraz mam taką sytuację:

1. Jakaś klasa próbuje się odwołać do klasy Test1, ale nie znajduje się w tym samym folderze, autoloader rzuca ClassNotFoundException (a w zasadzie tworzy klasę Test1, której kostruktor rzuca wyjątek).
2. Jakaś inna klasa próbuje się odwołać do klasy Test1 i znajduje się w tym samym folderze, niestety, wcześniej została już utworzona klasa Test1, która domyślnie rzuca ClassNotFoundException.

Ma ktoś jakiś inny pomysł na implementację tego? Chyba jedynym wyjściem jest pozbycie się wyjątków z autoloadera, ale może ktoś wymyśli coś innego.

Napisany przez: bela_666 16.06.2005, 22:12:29

Cytat(matid @ 2005-06-16 23:08:29)
Cytat(serafin)
Ma to sens?

A dlaczego nie? Mi osobiście bardzo się podoba sposób potraktowania typów zmiennych jako klas, a samych zmiennych jako obiektów.

To było takie pytanie retoryczne :] Nie ma to sensu. Po co opakowywać typy wbudowane jak i tak nic nie zyskasz, no może poza estetyką kodu winksmiley.jpg

Cytat
Oprócz php programuję też w Javie i uważam to za świetne rozwiązanie, więc dlaczego by nie używać tego w php?

W Javie też są typy podstawowe i jakoś nikt z tego powodu nie umarł :]
Btw. czemu nie piszesz w php systemu operacyjnego winksmiley.jpg

Cytat
Teraz pewnie padnie pytanie, co mi da takie "utrudnianie" sobie życia. Ale zdecydowaną korzyścią płynącą z takiego rozwiązania jest możliwość programowania własnych typów danych, czego przy standardowym traktowaniu zmiennych przez php nie zrobisz.

Emm a czy klasy nie są typami danych?

Napisany przez: Imperior 17.06.2005, 07:20:58

Cytat(matid @ 2005-06-16 21:08:29)
Ma ktoś jakiś inny pomysł na implementację tego? Chyba jedynym wyjściem jest pozbycie się wyjątków z autoloadera, ale może ktoś wymyśli coś innego.

1. Olać tworzenie pustej klasy
2. Olać zasięgi
3. Każdą klasę tworzyć pośrednio

ad 3. Proxy... zamiast $obj = new Test1(); robić $obj = Class::New('Test1');
A metoda New z Class wszystkim sie zajmie (zwróci instancję, rzuci wyjątek itp)

Napisany przez: matid 17.06.2005, 07:42:50

Chyba jednak skorzystam z opcji 1, ponieważ zasięgów nie chcę olewać, a tworzenie instancji przez proxy wyklucza takie przypadki jak dziedziczenie, implementowanie interfaców, itp.

Napisany przez: hawk 17.06.2005, 08:44:59

@serafin: Trochę konsekwencji. Bardzo się starasz, żeby nie odpowiadać na moje pytania i nie podejmować merytorycznej dyskusji. Zamiast tego widzę, że bardzo lubisz młotki i wymyśloną przez siebie klasę String, bo już drugi post o tym piszesz, co nijak się ma do tematu. Możemy i tak...

@matid: IMHO nie da się przeskoczyć problemu zadeklarowanej już wcześniej "pustej" klasy. Bo za drugim razem po prostu nie uruchomi się autoloader. A tworzenie każdej klasy za pomocą specjalnej metody też jest możliwe, ale to już nie to samo.

Ale zastanawia mnie, w jaki sposób jesteś w stanie stwierdzić, czy pozwolić na utworzenie obiektu? Odpala się autoloader, klasa ma zasięg "directory". W jaki sposób stwierdzasz, czy wolno w tym miejscu utworzyć obiekt? Bo mi nic nie przychodzi do głowy sad.gif.

Napisany przez: matid 17.06.2005, 10:31:38

Korzystam z http://pl.php.net/debug_backtrace
Nie sprawdzałem, na ile jest to wydajne, ale działa smile.gif
Innego pomysłu na razie nie mam.

Napisany przez: Imperior 17.06.2005, 15:12:25

Cytat(matid @ 2005-06-17 06:42:50)
Chyba jednak skorzystam z opcji 1, ponieważ zasięgów nie chcę olewać, a tworzenie instancji przez proxy wyklucza takie przypadki jak dziedziczenie, implementowanie interfaców, itp.

W jaki sposób wyklucza? Nie rozumię...

Napisany przez: matid 17.06.2005, 15:41:10

  1. <?php
  2. class Test1 extends Test2
  3. {
  4. }
  5. ?>

Jak wczytujesz ten plik i korzystasz z Autoloadera to skrypt automatycznie próbuje załadować plik z klasą Test2. Przez Proxy to nie przejdzie.
Poza tym, co z klasami zewnętrznymi? Też chciałbym, aby były obsługiwane przez autoloader, a przepisywać kod tak, aby korzystał z proxy jest bez sensu.

Napisany przez: bela_666 17.06.2005, 20:35:56

Fajna bajeczka : http://groups.google.pl/groups?q=proxy&hl=pl&lr=lang_pl&group=pl.comp.objects&c2coff=1&client=firefox-a&rls=org.mozilla:pl-PL%3a%6ffficial&selm=d2107t%24otc%241%40news.onet.pl&rnum=1 winksmiley.jpg

Napisany przez: Imperior 17.06.2005, 23:03:25

Cytat(matid @ 2005-06-17 14:41:10)
Jak wczytujesz ten plik i korzystasz z Autoloadera to skrypt automatycznie próbuje załadować plik z klasą Test2. Przez Proxy to nie przejdzie.
Poza tym, co z klasami zewnętrznymi? Też chciałbym, aby były obsługiwane przez autoloader, a przepisywać kod tak, aby korzystał z proxy jest bez sensu.

Hm... też fakt. Nie zastanawiałem się nad tym zbytnio (ja wole autoloadera, proxy do tego nie używam). Zatem pozostaje jeszcze imitacja pakietów w nazwach klas. Czyli np. SQL_MySQL_Service.

Napisany przez: squid 8.08.2005, 12:56:24

Jeszcze nie przeczytalem calego tematu wiec sory jesli odpowiedz juz padla ale czy nie mysleliscie o tym zeby wszystkie interfejsy trzymac w jednym lub kilku plikach pogrupowanych kategorie? Albo trzymac interfejs w pliku z klasa?
Moze to i troche malo eleganckie ale szkoda zapuszczac cala maszyne dla znalezienia i odczytania kilku linijek kodu a sa jeszcze klasy bez interfejsow

Napisany przez: hawk 8.08.2005, 21:39:27

Tak naprawdę to wszystkie interfejsy powinno się wywalić przed oficjalnym wypuszczeniem kodu. Przecież są do niczego niepotrzebne... pomijając oczywiście type hinty i inne bzdety, które też w związku z tym powinno się wywalić. I pomijając kilka wrednych sytuacji, w których jednak nie da się wywalić, i dlatego pewnie automatyczny wywalacz interfejsów jeszcze nie powstał winksmiley.jpg.

Napisany przez: Vengeance 8.08.2005, 22:56:24

Wywalić interfejsy? A niby z jakiej racji?
Przecież po to są by szuko móc dojść co klasa musi miec itd w przyszłości...

Napisany przez: NuLL 8.08.2005, 23:02:05

@Hawk - napisać parser plików który będzie usuwał wszystko od słowa implements do napotkania słowa extends lub nawiasu klamrowego tongue.gif

Joking winksmiley.jpg

Wg. mnie częśc interfejsów jest potrzebna. Jak chocby dla jakiegoś sterownika. Nóż, widelec ktoś będzie miał ochote napisać własny - od tego mam interfejs smile.gif

Napisany przez: matid 9.08.2005, 07:36:32

Jeśli kod jest wydany na zasadach OpenSource to mimo wszystko nie wyrzucałbym interface'ów. Jak ktoś inny będzie chciał sobie dostosować ten kod, itd. to będzie miał ułatwione zadanie.

A co do oprogramowania z zamkniętym źródłem - na etapie pisania aplikacji - interface to bardzo przydatna rzecz, w release'ach to tylko zbędne przeciążenie aplikacji.

Napisany przez: hawk 9.08.2005, 14:25:03

Ale ja mówię o dwóch wersjach kodu: debug i release. Debug = mamy wszystkie komentarze, interfejsy, tak jak napisaliśmy. Release = wyrzucamy interfejsy, usuwamy komentarze, pakujemy po kilka klas do jednego pliku.

Też z tego powodu, że - jak wykazało profilowanie - autoloadowanie jest strasznie wolne.

Napisany przez: chmolu 9.08.2005, 14:59:17

No jakoś mi się nie widzi usuwanie w kodzie $obj instanceof interfejs oraz zmienianie deklaracji funkcji, w których używam typehintingu.
Wcale nie uważam, że interfejsy są zbędne. Autoładowanie może i jest wolne, ale z włączonym akceleratorem różnica jest niewielka. Wiem, że nie każdy ma dostęp do akceleratora, ale dalej uważam, że usuwanie interfejsów z kodu to bezsens.

Napisany przez: squid 9.08.2005, 15:12:26

Cytat(chmolu @ 2005-08-09 15:59:17)
No jakoś mi się nie widzi usuwanie w kodzie $obj instanceof interfejs oraz zmienianie deklaracji funkcji, w których używam typehintingu.
Wcale nie uważam, że interfejsy są zbędne. Autoładowanie może i jest wolne, ale z włączonym akceleratorem różnica jest niewielka. Wiem, że nie każdy ma dostęp do akceleratora, ale dalej uważam, że usuwanie interfejsów z kodu to bezsens.

czesciowo sie zgadzam ja tez nie bede usuwal nic z kodu "recznie" bo latwo o pomylke ale moze napisac cos co taka zmiane przeprowadzi robiwac z calej aplikacji pakiet + instalator gotowe do wyslanai na serwer.

Co do akceleratora to on tu nie pomoze bo jego zadanie jest inne ale pewnie zasugerowales sie nazwa Zend Accelerator smile.gif

Autoloader wiekszac czesc czasu spedza na includowaniu (mam nadzeje ze nikt nie uzywa require w autoloaderze winksmiley.jpg ) kodu mam tu namusli jego analize i przeksztalcenie do postaci posredniej. Tu pomoze bufor kompilatora ktorym jest np. Zend Accelerator ktory jest komercyjny ale jest tez APC darmowy i open source dostepny przez PECL do tego kazdy teoretycznie ma dostep.

Faktycznie juz przy 10 plikach widac jak duzo czasy zajmuje analaiza kazdego kolejnego a bufor znaczaco tu pomaga pytanie tylko czy budujac framework czy juz gotowa aplikacje mozemy wymagac aby w srodowisku produkcyjnym byla zainstalowana taka biblioteka? w zasadzie to nic wielkiego a oszczedza czas serwera ale sprobujcie przekonac do czegos takiego admina. Z drufgiej strony zbudowanie czegos co pousuwa rozne obiektowe ozdobniki zkodu troche potrwa a bufor to gotowe rozwiazanie dajace nawet przyros 100% w wydajnosci za darmo i bez dodatkowego nakladu pracy!!

Napisany przez: NuLL 17.01.2006, 11:49:38

Macie może jakiś pomysł co zrobić z autoloaderem kiedy jest w nim 300 plikow ? Wiem ze to troche duzo ale tyle sie nazbieralo kolejnych klas, wyjatkow oraz interfejsow. I nie wiem co z tym zrobic ?

Ja wpadłem na pomysl aby zaladowac tylko najpotrzebniejsze mapy : request, db oraz kilka innych. Natomiast inne mapy mozna by ladowac np rozbijajac nazwe klasy z niej wyciagac dwa pierwsze czlony zalozmy i czyli nazwa pakietu i wtedy zaladowac mape do ktorej nalezy ta konkretna klasa. Co w Wy na to ?

Chetnie poslucham innych pomyslow.

Napisany przez: bigZbig 17.01.2006, 12:18:57

Mozna zastosowac cos w rodzaju przestrzeni nazw. Wszystko zależy od tego jaki sie przyjelo sposob nazewnictwa. Ja wszystkie klasy z danego pakietu poprzedzam nazwa danego pakietu. W ten sposob moglbym latwo podzielic automape na kilka mniejszych czesci. Oczywiscie metoda ta nie nadaje sie do cudzych klas, ktore stosuja odmienna polityke nazewnictwa. Poza tym, na dobra sprawe jest to jedynie polowiczne rozwiazanie gdyz czesto np. na 20 klas z danego pakietu potrzebnych jest zaledwie 5.

Pozostaje rowniez problem odroznienia w nazwie klasy tego co jest przedrostkiem.

Jesli - mojPrzedrostek_wygladaTak.class.php - to nie ma problemu
Niestety - mojPrzedrostekWygladaTak.class.php - a to juz jest problem

Napisany przez: NuLL 17.01.2006, 12:23:08

Ja cos takiego robie tzn mam ezcCacheStorageFileArray i rozbijam to po wielkosci znakow pomijajac pierwszy czlon pisany z malej litery i laduje mape to dla ezcCache.

Co do polowicznosci sie zgadzam m.in dlatego ostatnio twoje wiecej malych map winksmiley.jpg

Napisany przez: bela_666 17.01.2006, 12:57:03

A po co rozbijac? Ja mam kilkaset linijek i smiga.
Poza tym zobacz do phienda 2, tam masz w kazdym komponencie osobną mape

Napisany przez: squid 17.01.2006, 13:12:04

co nazywacie mapa? Czy jest to skojarzona z klasa sciezka dostepu (dla kazdej klasy)?

Napisany przez: NuLL 17.01.2006, 13:18:13

Cytat
A po co rozbijac? Ja mam kilkaset linijek i smiga.
Poza tym zobacz do phienda 2, tam masz w kazdym komponencie osobną mape

W phiendzie komponentow jest nieduzo - u mnie jest ich okolo 30.

Rozbijam bo sie musze dostac do okreslonej mapy.

O co chodzi z tymi linijkami questionmark.gif

Napisany przez: Ociu 17.01.2006, 14:28:28

Cytat(squid @ 2006-01-17 14:12:04)
co nazywacie mapa? Czy jest to skojarzona z klasa sciezka dostepu (dla kazdej klasy)?

Mapa to 'lista' scieżek i nazwy klas.

dajmy na to (posłuże się autoloaderem z phienda2):
  1. <?php
  2. $map = http://www.php.net/array (
  3. # przyklad: 'nazwa_klasy' => 'sciezka_do_pliku.php'
  4. 'Request' => './framework/request.class.php',
  5. 'Response' => './fremework/response.class.php'
  6. );
  7.  
  8. Autoloader::addMap($map);
  9. ?>


pozdrawiam

Napisany przez: bela_666 17.01.2006, 19:16:03

NuLL, pytanie moje brzmi, czy każdy komponent może pracować oddzielnie? Tzn robisz ctrl c, v, wrzucasz do innego katalogu i działa.
Btw. co Ty masz w tych komponenach?

Napisany przez: NuLL 17.01.2006, 20:06:44

bela_666 - moze, nie wszystkiego bo czesc jest zalezna od Db dla przykladu.

Co mam ? Db, Cache, Configuration, Template, Debug, Image itp itd. - Cala aplikacja krotko mowiac.

Napisany przez: hawk 24.01.2006, 08:54:37

@NULL: IMHO rozwiązaniem problemu ze zbyt dużą liczbą wpisów jest sklejenie tego we większe pliki. Autoloadowanie tego wszystkiego zajmuje jednak sporo czasu. Ideałem jest dla mnie rozwiązanie, gdy po ustaleniu, jakie komponenty są nam potrzebne, robimy z tego wersję release pakując automatycznie do jednego pliku i tak dystrybuujemy. Lokalnie mamy wersję dev z rozbiciem na małe pliki, a dzięki autoloaderowi cała operacja jest przezroczysta - mamy tylko inną mapę. Natomiast plików specyficznych dla konkretnego żądania, np. klas akcji, nie warto pakować do mapy, bo by się zbyt duża zrobiła.

Napisany przez: squid 14.02.2006, 13:34:38

Cytat(hawk @ 2006-01-24 09:54:37)
@NULL: IMHO rozwiązaniem problemu ze zbyt dużą liczbą wpisów jest sklejenie tego we większe pliki. Autoloadowanie tego wszystkiego zajmuje jednak sporo czasu. Ideałem jest dla mnie rozwiązanie, gdy po ustaleniu, jakie komponenty są nam potrzebne, robimy z tego wersję release pakując automatycznie do jednego pliku i tak dystrybuujemy. Lokalnie mamy wersję dev z rozbiciem na małe pliki, a dzięki autoloaderowi cała operacja jest przezroczysta - mamy tylko inną mapę. Natomiast plików specyficznych dla konkretnego żądania, np. klas akcji, nie warto pakować do mapy, bo by się zbyt duża zrobiła.

takie rozwiazanie jest kosztowne o tyle ze trzeba napisac cos na ksztalt kompilatora i linkera, ktory zajmnie sie przerobieniem wersji dev na produkcyjna. Nie jest moze to duzy problem bo napisanie to operacja jednorazowal ale zawsze po bokonaniu zmian przeba bedzie uzyc czegos co na nowo zapakuje nam klasy w pliki i wydaje mi sie ze jest to duzo zbednej i nikomu niepotrzebnej roboty zwlaszcza ze narzut czasowy zwiazany z dolaczaniem kolejnych plikow da sie zniwelowac w sosobo o jakim mowilem. Jest to rozwiaznie darmowe wydajne i nie trzeba niczego zmieniac w istniejacych projekatach.

Abstrachujac od tego co powiedzialem wczesniej, mam tu prosty przyklad na wykorzystanie funkcji __autoload():

autoload.lib.php:
  1. <?php
  2. function __autoload ( $className )
  3. {
  4. $classDir = './class/';
  5. $separator = '_';
  6.  
  7. $path2Class = http://www.php.net/str_replace ( $separator, '/', $className );
  8.  
  9. require_once $classDir . $path2Class . '.class.php';
  10. }
  11. ?>


a.class.php:
  1. <?php
  2. // moze byc zaladowane duzo wczesniej np. przez kontroler
  3. require_once './autoload.lib.php';
  4.  
  5. class a
  6. {
  7. public function __construct ()
  8. {
  9. http://www.php.net/echo "<p>Zaraz zaladuje nowa klase </p>";
  10. new katalog_b ();
  11. }
  12. }
  13.  
  14. new a();
  15. ?>


katalog_b.class.php:
  1. <?php
  2. class katalog_b
  3. {
  4. public function __construct ()
  5. {
  6. http://www.php.net/echo "jestem w klasie b </ br>";
  7. }
  8. }
  9. ?>

proste i pozwala na zdefiniowanie jednej prostej f-cji kotra zajmie sie sama wlaczaniem klas jesli taki nie zostaly jeszcze dolaczone.
Sposob nazewnistwa jest oczywiscie umowny i mozna stosowac dla rozroznienia katalogow i plikow male i wielkie litery lub cokolwiek innego. Mozna tez trzymac tablice globalna z plikami ktore juz zaladowalismy i sprawdzac czy oplaca sie uzyc f-cji *_once (to juz bylo chyba wspomniane), chociaz nie wiem czy jest sens bo jesli plik jest juz zaladowany to f-cja __autoload() nie zostanie wywolana.

Co jest tu problemem? Zewnetrzne pakiety i klasy na ktorych nazewnictwo i polozenie nie mamy wplywu. Moze tutaj zwykly include_path rozwiazalby problem? Napewno wystarcza to do PEAR co do innych pakietow to chyba tak czy siak trzeba rozpatrywac to osobno bo napewno autorzy kody przewidzieli swoje sosoby dolaczania innych klas itp.

Co o tym myslicie?

EDIT:
idac dalej mozna zadbac rowniez o wlaczanie interfejsow i wyjatkow w podobny sposob:
  1. <?php
  2.  
  3. function __autoload ( $className )
  4. {
  5. $classDir = './classes/';
  6. $classSuffix = '.class.php';
  7.  
  8. $interfaceDir = './interfaces/';
  9. $interfaceSuffix = '.php';
  10.  
  11. $exceptionDir = './exceptions/';
  12. $exceptionSuffix = '.php';
  13.  
  14. $separator = '_';
  15.  
  16. if ( $className[0] == 'i' )
  17. { // interfejs
  18. $file = $interfaceDir . http://www.php.net/str_replace ( $separator, '/', $className ) . $interfaceSuffix;
  19. }
  20. else if ( $className[0] == 'e' )
  21. { // wyjatek
  22. $file = $exceptionDir . http://www.php.net/str_replace ( $separator, '/', $className ) . $exceptionSuffix;
  23. }
  24. else 
  25. { // klasa
  26. $file = $classDir . http://www.php.net/str_replace ( $separator, '/', $className ) . $classSuffix;
  27. }
  28.  
  29. if ( !http://www.php.net/file_exists( $file) )
  30. {
  31. throw new eClassNotFound();
  32. }
  33.  
  34. require $file;
  35. }
  36. ?>


oczywiscie zmienne mozna ladowac z jakiegos configa ale mamy teraz wszystko co potrzeba nam do automatycznego ladowania klas

Napisany przez: eMartio 31.03.2006, 19:40:31

Cytat(DeyV @ 2005-02-10 20:46:51)

Cytat
Takie mapy można nawet generować automatycznie przeszukując skryptem katalogi i parsując (tokenizując) pliki php.


Co oznacza pojęcie tokenizując w programowaniu? Token to symbol... w wolnym tłumaczeniu.

Cytat
Aby sprawę jeszcze bardziej skomplikować - warto pamietać o tym - że require_once i inlude_once jest w rzeczywistości bardzo wolne


A więc co polecasz? Najlepiej stosować samo: require i include? Czy może jeszcze coś innego?

Napisany przez: matid 31.03.2006, 20:25:42

Cytat(eMartio @ 2006-03-31 19:40:31)
Co oznacza pojęcie tokenizując w programowaniu? Token to symbol... w wolnym tłumaczeniu.

Google dzisiaj nie działa?
http://google.com/search?q=define:tokenize

Napisany przez: bela_666 31.03.2006, 20:51:29

Cytat(eMartio @ 2006-03-31 20:40:31)
Cytat
Takie mapy można nawet generować automatycznie przeszukując skryptem katalogi i parsując (tokenizując) pliki php.


Co oznacza pojęcie tokenizując w programowaniu? Token to symbol... w wolnym tłumaczeniu.

Sam tłumaczysz sobie winksmiley.jpg
To jest rozbicie pliku źródłowego na tablice symboli. Jest to pierwszy etap analizy kodu źródłowego.
Więcej, google: kompilator i interpretator

Napisany przez: NuLL 27.05.2006, 16:52:38

Czy ktos moze wykonywal testy po jakims ciezkim obciazeniem aplikacji pracujacej na autoloaderze i bez niego ? Chetnie bym posluchal jakis opinii. Wiem ze jest to dosc wolne ale czy az tak questionmark.gif Zastanawia mnie tak kwiestia jakoze zalozmy Panowie z ez.no postanowili w eZ Components jechac na autoloadzie napewno zdajac sobie sprawe z problemu wydajnosci smile.gif

Co sadzicie o tym ? Pisac z __autoload() czy bez ? snitch.gif

Napisany przez: Ludvik 27.05.2006, 21:44:26

Ja osobiście wszystko mam przygotowane pod autoloadera. Co z wydajnością? Przy cache mapy klas i włączonym xdebugu załadowanie wszystkich klas i systemu zajmuje ok. 35ms, a samo nawiązanie połączenia przez PDO z PostgreSQL na localhoście ok. 230, więc strata wydajności nie jest duża. Z tych 35ms połowa to require_once. Myślę, że nie jest to zbyt duża strata wydajności w porównaniu do ułatwienia pisania kodu.

Na poważnych serwerach kod zenda jest i tak trzymany w pamięci, więc straty są jeszcze mniejsze.

Napisany przez: squid 27.05.2006, 22:19:50

przy dobrym projekcie autoloadera mozna ominac f-cje *_once mozna podniesc wydajnosc tak ze autoloadowanie bedzie praktycznie niezauwazalne

Napisany przez: NuLL 28.05.2006, 15:16:33

@Ludvik - dzieki za wiesci - o cos takiego mi chodzilo smile.gif Teraz tylko czekac na otwarty autoloader od eZ components i po bolu.

Napisany przez: Vengeance 10.06.2006, 17:38:21

To ciekawe co mówicie, bo w mojej aplikacji autoload() zajmował 98% czasu wykonywania (tak, tak... i to już z cache mapy w pliku!). Wróciłem do require_once. Zresztą takie rozwiązanie jest dużo czytelniejsze przy późniejszej analizie kodu. Wiadomo gdzie co jest trzymane winksmiley.jpg

Napisany przez: squid 10.06.2006, 19:07:47

Cytat(Vengeance @ 10.06.2006, 18:38 ) *
To ciekawe co mówicie, bo w mojej aplikacji autoload() zajmował 98% czasu wykonywania (tak, tak... i to już z cache mapy w pliku!). Wróciłem do require_once. Zresztą takie rozwiązanie jest dużo czytelniejsze przy późniejszej analizie kodu. Wiadomo gdzie co jest trzymane winksmiley.jpg

pewnie te 98% czasu zajmowalo wlaczenie analiza i interpretacja kodu co chyba nie jest rownowazne z narzutem czasu samego autoloadera.

P.S.
Obecnie nie mam czasu ale za klika tygodni postawie odizolowana maszyne i przeprowadze profilowanie zeby sprawdzis te teorie

Napisany przez: bigZbig 12.06.2006, 08:04:11

Ja tez uzywam autoloadera, ale nie tworze jednej globalnej tablicy, a juz napewno nie generuje jej automatycznie. Staram się tworzyć pakiety. Kazdy pakiet ma swoj plik inicjalizacyjny, w ktorym jest m.in. mapa klas. Jesli dolaczam dany moduł (pakiet) do aplikacji poprzez includowanie jego pliku inicjalizujacego to juz mechanizm zawarty w tym pliku zajmuje się dołączeniem mapy klas danego pakietu do ogolnej mapy klas całej aplikacji na ktorej operuje funkcja __autoload. Dla wygody testowania zaimplementowalem tez mozliwosc ominiecia autoloadera. Tracę tym może nieznacznie na wydajności, ale znacznie zyskuję na wygodzie. Poniższy przykład ilustruje to o czym pisałem:

  1. <?php
  2. /**
  3.  * @author hawk
  4.  */
  5. class bigAutoloader {
  6. private http://www.php.net/static $aMap = http://www.php.net/array();
  7.  
  8. public http://www.php.net/static function load($sName) {
  9. if (http://www.php.net/array_key_exists($sName, self::$aMap)) {
  10. include self::$aMap[$sName];
  11. }
  12. if (!class_exists($sName) && !interface_exists($sName)) {
  13. http://www.php.net/eval("class $sName { 
  14. public function __construct() { 
  15. throw new ClassNotFoundException('$sName not found'); 
  16. }
  17. }");
  18. }
  19. }
  20.  
  21. public http://www.php.net/static function addMap($aMap) {
  22. self::$aMap = http://www.php.net/array_merge(self::$aMap, $aMap);
  23. }
  24. }
  25. ?>


  1. <?php
  2. /**
  3.  * bigLang inicjalizator
  4.  */
  5. if (!http://www.php.net/defined('BIGLANG_DIR')) http://www.php.net/define('BIGLANG_DIR',http://www.php.net/dirname(__FILE__));
  6. $aClassesMap = http://www.php.net/array(
  7. 'bigLang' => BIGLANG_DIR.'/bigLang.abstract.php',
  8. 'bigLangManager' => BIGLANG_DIR.'/bigLangManager.class.php',
  9. );
  10.  
  11. if (class_exists('bigAutoloader')) {
  12. bigAutoloader::addMap($aClassesMap);
  13. } else {
  14. foreach($aClassesMap as $sClassName) {
  15. include_once($sClassName);
  16. }
  17. }
  18.  
  19. ?>



@Vengeance - wybacz, ale wydaje mi sie, że jednak się mylisz. Bo jeśli używając funkcji __autoload dołączam potrzemne pliki przy pomocy instrukcji include, lub require, a Ty posługujesz się require_once lub include_once to zadaj sobie pytanie co jest bardziej wydajne. Czy narzut czasowy potrzebny dla samego wywołania funkcji autoladujacej jest faktycznie tak duży? Poza tym autoloader dolancza jedynie te pliki, ktore sa wymagane, a zastosowanie bezposrednio *_once dołącza zawsze wszystkie pliki - nawet jesli ich zawartość nie jest w danym wywołaniu potrzebna.

Napisany przez: Vengeance 12.06.2006, 13:46:28

Jakie argumenty by tu nie padały, i tak nic nie zmieni mojego "self experience" gdzie Zend pokazywał 98% dla samego autoloadu.

Ja nie twierdze, że ta metoda jest cool, albo że jest do dupy. Mówię tylko, że z początku stosowałem ją ale zrezygnowałem.

Jednym z argumentów przeciw jest choćby czytelność kodu (z mojego punktu widzenia).

Że już nie wspomne o np. umiejetnośći podpowiadania składni w edytorach :] Które bardzo mocno wspierają się na include i require w kodzie... a autoload powoduje oduczenie ich umiejętności poprawnego podpowiadania.

Napisany przez: bigZbig 13.06.2006, 06:52:15

Argument o czytelności kodu do mnie nie przemawia bo wszystkie pliki dolanczam (dokladam do mapy klas) w scisle okreslonych miejscach. W plikach poszczegolnych klas nie ma miejsca na dolanczanie zaleznosci jak np. definicje wyjatkow. Od tego mam plik inicjalizujacy dany pakiet i jest to wygodne. W zasadzie przy takim podejsciu to nie ma znaczenia czy uzyjesz autoloadera, czy bezposrednio require.

Wada tego rozwiazania jest oczywiscie to, ze chcac uzyc tylko jednej klasy z calego pakietu musisz sie upewnic czy do jej dzialania nie jest wymagana obecnosc jakiegos innego pliku.

Co do edytorow i podpowiadania skladni - argument wart przemyslenia. Tak swoja droga jakiego edytora uzywasz?

Napisany przez: NuLL 13.06.2006, 16:36:42

Cytat
Że już nie wspomne o np. umiejetnośći podpowiadania składni w edytorach :] Które bardzo mocno wspierają się na include i require w kodzie... a autoload powoduje oduczenie ich umiejętności poprawnego podpowiadania.

W Zendzie nie ma z tym problemu.

Napisany przez: orson 13.06.2006, 17:37:21

witam

eclipse przechowuje informacje o wszystkich klasach w cache ... więc w nim nie ma problemu z używaniem metod z obiektów nie włączonych jawnie do kodu ...

używam autoloadera z mapą i uważam że jest to bardzo wydajne i wygodne rozwiązanie ... jak mi się uda to włączę xdebuga na jakimś serwerze u mnie w pracy i zrobię testy i profilowanie na żywej aplikacji (ale to po długim weekendzie dopiero)

pozdrawiam

Napisany przez: eai 5.08.2006, 16:28:49

Ja generuje sobie mapę przy instalacji aplikacji. Zapisuje w pliku tekstowym z zakodowanym serialize();

Mapa wyglada tak:

Kod
Array
(
    [Action] => Array
        (
            [Dir] => Actions/
            [Elements] => Array
                (
                    [ImageFunctions] => Array
                        (
                            [Dir] => Image/
                            [Class] => Array
                                (
                                    [ImageHandler] => Array
                                        (
                                            [file] => Img.Handler.php
                                            [SubDir] =>
                                        )

                                    [ImageDraw] => Array
                                        (
                                            [file] => Img.Handler.php
                                            [SubDir] =>
                                        )

                                )

                        )

                    [SessionHandler] => Array
                        (
                            [Dir] => Session/
                            [Class] => Array
                                (
                                    [Session] => Array
                                        (
                                            [file] => Session.Handler.php
                                            [SubDir] =>
                                        )

                                )

                        )

                )

        )

    [Core] => Array
        (
            [Dir] => Core/
            [Elements] => Array
                (
                    [View] => Array
                        (
                            [Dir] => View/
                            [Class] => Array
                                (
                                    [view] => Array
                                        (
                                            [file] => view.php
                                        )

                                    [View] => Array
                                        (
                                            [SubDir] =>
                                        )

                                )

                        )

                )

        )

    [Plugin] => Array
        (
            [Dir] => Plugins/
            [Elements] => Array
                (
                    [DBHandler] => Array
                        (
                            [Dir] => DbHandler/
                            [Class] => Array
                                (
                                    [mySQL] => Array
                                        (
                                            [file] => Db.MySql.php
                                            [SubDir] =>
                                        )

                                    [pgSQL] => Array
                                        (
                                            [file] => Db.pgSql.php
                                            [SubDir] =>
                                        )

                                )

                        )

                )

        )

)


AutoLoader robie na 2 sposoby.
1. Szukam klasy w mapie i includuje ja sprawdzajac czy taki plik istnieje
2. Szukam Calego modulu Np ImageFunctions i includuje wszystkie klasy [nie Classy] z tej mapy.

--
"klasy"
dr_bonzo

Napisany przez: squid 5.08.2006, 16:44:16

"bigZbig dla kaydego pakietu musisy kopiowac inicjaliyator cyz autoloade

Napisany przez: mtod 8.08.2006, 16:21:45

Przepraszam, ale nie doczytałem tego wątku od deski do deski - limit czasu.
Kiedyś pisałem i niedawno też opisałem taką klasę autoloadera. http://metoda.wordpress.com/2006/07/20/autoloader-dla-php5/. Wszelkie sugestie/komentarze wezmę chętnie pod uwagę =)

Napisany przez: lukir 8.08.2006, 23:27:40

Mógłby ktoś z was podsumować wszystkie te rozważania jakimś zgrabnym kodem autoloader'a? Taki najbardziej wydajny i uniwersalny?


@mtod: Fajna ta klasa, ale możnaby dla większej elastyczności dodać możliwość ładowania również nowych klas...

Napisany przez: mtod 9.08.2006, 11:57:53

@lukir: nie rozumiem, jak to: "nowych"?

Napisany przez: lukir 12.08.2006, 10:24:01

@mtod: na podanej przez Ciebie stronce jest napisane coś takiego:



Cytat
Oczywiście cache należy stosować tylko w środowisku tzw. produkcyjnym, ponieważ w przeciwnym wypadku nowo dodane klasy będą poza zasięgiem autoloadera.




...,więc stąd moja propozycja, by rozszerzyć ten skrypt o możliwość automatycznego ładowania klas bez definiowania katalogu "produkcyjnego". Wtedy ten autoloader byłby wygodniejszy smile.gif

Napisany przez: Ludvik 12.08.2006, 10:30:55

Wtedy ten autoloader straci na wydajności. Każdorazowe sprawdzanie całego drzewa katalogów jest czynnością zajmującą masę czasu. W moim systemie odświeżenie cache jest równe skasowaniu jednego pliku, więc nie wiem czy to takie męczące.

Napisany przez: squid 12.08.2006, 11:09:43

wracajac do wydajnosci __autoload() to wspomniane wczesniej 90% czasu wykonania to jaka bzdura. Ponizej przedstawiam Wam wynik szybkiego profilowania mojej przykladowej aplikacji programem PECL APD

Kod
Total Elapsed Time = 0.27
Total System Time  = 0.05
Total User Time    = 0.06


         Real         User        System             secs/    cumm
%Time (excl/cumm)  (excl/cumm)  (excl/cumm) Calls    call    s/call  Memory Usage Name
--------------------------------------------------------------------------------------
28.6 0.01 0.01  0.02 0.02  0.02 0.02   242  0.0001   0.0001            0 is_array
14.3 0.06 0.08  0.02 0.03  0.00 0.00     9  0.0017   0.0035            0 require_once
14.3 0.00 0.08  0.02 0.06  0.00 0.00     5  0.0031   0.0125            0 include
14.3 0.01 0.01  0.00 0.00  0.02 0.02     4  0.0039   0.0039            0 defined
14.3 0.02 0.02  0.02 0.02  0.00 0.00    36  0.0004   0.0004            0 define
14.3 0.05 0.06  0.00 0.00  0.02 0.03    10  0.0016   0.0031            0 require
0.0 0.00 0.00  0.00 0.00  0.00 0.00     1  0.0000   0.0000            0 Forms_Register->_recursiveFilter
0.0 0.00 0.00  0.00 0.00  0.00 0.00     2  0.0000   0.0000            0 Forms_Register->_updateAttrArray
0.0 0.00 0.06  0.00 0.02  0.00 0.02     1  0.0000   0.0313            0 Forms_Register->registrationForm
0.0 0.00 0.00  0.00 0.00  0.00 0.00     2  0.0000   0.0000            0 Forms_Register->updateAttributes
0.0 0.00 0.04  0.00 0.00  0.00 0.02    11  0.0000   0.0014            0 Forms_Register->_loadElement
0.0 0.00 0.04  0.00 0.00  0.00 0.02    11  0.0000   0.0014            0 Forms_Register->addElement
0.0 0.00 0.00  0.00 0.00  0.00 0.00    11  0.0000   0.0000            0 Forms_Register->isTypeRegistered
0.0 0.00 0.00  0.00 0.00  0.00 0.00     4  0.0000   0.0000            0 Forms_Register->_parseAttributes
0.0 0.00 0.08  0.00 0.02  0.00 0.02     1  0.0000   0.0313            0 Registrations_RegistrationsOps->register
0.0 0.00 0.00  0.00 0.00  0.00 0.00     4  0.0000   0.0000            0 array_merge
0.0 0.00 0.00  0.00 0.00  0.00 0.00     2  0.0000   0.0000            0 View->setOutputData
0.0 0.00 0.02  0.00 0.00  0.00 0.00     2  0.0000   0.0000            0 Forms_Register->HTML_QuickForm
0.0 0.00 0.00  0.00 0.00  0.00 0.00     2  0.0000   0.0000            0 Forms_Register->HTML_Common
0.0 0.00 0.00  0.00 0.00  0.00 0.00     2  0.0000   0.0000            0 HTML_QuickForm_hidden->HTML_QuickForm_hidden


Widac ze samej f-cji autolad nie ma w zestawieniu pozeraczy czasu, czas zabiera wykonanie includow a to czy sa wywolywane bezposrednio czy w autoladerze nie ma znaczneia jest tylko roznica w wygodzie bo uwazam lepiej jest napisac
  1. <?php
  2. new JakasKlasa();
  3. ?>

niz
  1. <?php
  2. include 'JakasKlasa.php';
  3. new jakasKlasa();
  4. ?>

Napisany przez: Denver 18.08.2006, 22:52:45

Ja, wykorzystując ujednolicone nazewnictwo klas, korzystam z autoloadera w taki sposób:

  1. <?php
  2. function __autoload ($strClass)
  3. {
  4. if ($strClass == 'Actions')
  5. {
  6. require_once ('./modules/class.Actions.php');
  7. }
  8. elseif (http://www.php.net/preg_match ('/Admin$/', $strClass))
  9. {
  10. require_once ('./modules/admin/lib/class.'.$strClass.'.php');
  11. }
  12. elseif (http://www.php.net/preg_match ('/Exception$/', $strClass))
  13. {
  14. require_once ('./lib/exceptions/class.'.$strClass.'.php');
  15. }
  16. else
  17. {
  18. require_once ('./modules/lib/class.'.$strClass.'.php');
  19. }
  20. }
  21. ?>


Żadne niepotrzebne klasy nie spowalniają mojego kodu - są wczytywane dopiero wtedy, gdy naprawdę są potrzebne. Świetnym przykładem mogą być klasy wyjątków. Możemy mieć 1000 różnych klas dziedziczących po Exception, które w żaden sposób nie spowalniają kodu, gdyż są ładowane dopiero po wyrzuceniu danego wyjątku.

Powyższe, bardzo proste rozwiązanie, jest raczej przeznaczone do małych projektów z maksymalnie kilkunastoma klasami działającymi podczas jednego żądania ze względu na użycie wyrażeń regularnych. Dla większych projektów zalecane jest oczywiście mapowanie wszystkich klas opisane w tym temacie chyba wystarczająco obszernie winksmiley.jpg

Napisany przez: LBO 16.01.2007, 20:23:45

Bardzo przydatne przy pisaniu autoloadera mogą być funkcje http://pl.php.net/get_declared_classes i http://pl.php.net/get_declared_interfaces. Niestety wiążą się z tym pewne problemy:

Jakieś pomysły?
edit:
Po zastanowieniu drugi problem, chyba nie jest jednak taki ważny. Skoro autoloader w zamierzeniu istnieje dla ładowania klas, nie wyobrażam sobie sytuacji w której ktoś miałby dołączać do deklaracji klasy jakieś instrukcje strukturalne. Chociaż, fakt-faktem jest to możliwe, a autoloader powinien być na to odporny.
Co do wspomnianego limitu pamięci - testów nie robiłem i te standardowe 8M wbrew pozorom może pomieścić chyba dużo. A przecież klasy nie będą instancjonowane, z zasobów korzystać powinien tylko autoloader.

Napisany przez: dr_bonzo 16.01.2007, 21:57:56

Cytat
Skoro autoloader w zamierzeniu istnieje dla ładowania klas, nie wyobrażam sobie sytuacji w której ktoś miałby dołączać do deklaracji klasy jakieś instrukcje strukturalne. Chociaż, fakt-faktem jest to możliwe, a autoloader powinien być na to odporny.

Taki exit to blad (w OOP), a bledy nalezy usuwac a nie hackowac (obchodzic) w autoloaderze.

Napisany przez: LBO 16.01.2007, 22:06:02

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ś.

Napisany przez: NuLL 17.01.2007, 15:13:32

Cytat
Chodzi o sam fakt, że model obiektowy php dopuszcza do takiego czegoś.

Tzn do czego ? Co ma ten exit do OOP ?

Napisany przez: athabus 17.01.2007, 16:08:59

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.

Napisany przez: bigZbig 17.01.2007, 16:52:03

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.

Napisany przez: athabus 17.01.2007, 17:18:00

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.

Napisany przez: J4r0d 18.01.2007, 00:50:57

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.

Napisany przez: dr_bonzo 18.01.2007, 01:06:33

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

Napisany przez: J4r0d 18.01.2007, 01:16:20

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()..

Napisany przez: dr_bonzo 18.01.2007, 09:09:06

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.

Napisany przez: cadavre 18.01.2007, 16:52:08

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

Napisany przez: DjKermit 22.01.2007, 12:26:38

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 http://www.php.net/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 http://www.php.net/static function getInstance() {
  79. if (http://www.php.net/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 = http://www.php.net/str_replace('.', DIRECTORY_SEPARATOR, $class_name);
  96. if (http://www.php.net/strstr($class_name, '*')) { // load all class package
  97. $package_path = http://www.php.net/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(http://www.php.net/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 (http://www.php.net/array_key_exists($class_name, $this->class_map) && http://www.php.net/file_exists($this->class_map[$class_name])) {
  115. require_once($this->class_map[$class_name]);
  116. }else{
  117. $this->generateClassMap();
  118. if (http://www.php.net/array_key_exists($class_name, $this->class_map) && http://www.php.net/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 (http://www.php.net/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 (http://www.php.net/file_exists($this->cache_file_path . $this->cache_file_name)) {
  151. $this->class_map = http://www.php.net/parse_ini_file($this->cache_file_path . $this->cache_file_name);
  152. }else{
  153. if (!http://www.php.net/is_dir($this->cache_file_path)) http://www.php.net/mkdir($this->cache_file_path);
  154. }//end if
  155. if (http://www.php.net/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 = http://www.php.net/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 http://www.php.net/readdir($path) {
  207. if ($handle = http://www.php.net/opendir($path)) {
  208. while (($file = http://www.php.net/readdir($handle)) !== false) { 
  209. if (http://www.php.net/is_file($path . $file) && http://www.php.net/strstr($file, 'class') && !http://www.php.net/strstr($file, 'ClassLoader')) {
  210. $this->readed_dir[http://www.php.net/str_replace('.class.php', '', $file)] = $path . $file;
  211. }else if (http://www.php.net/is_dir($path . $file) && $file != '.' && $file != '..') {
  212. $this->readDir($path . $file . DIRECTORY_SEPARATOR);
  213. }//end if
  214. }//end while
  215. http://www.php.net/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. http://www.php.net/define('BASE_PATH', http://www.php.net/dirname(__FILE__) .'/');
  4. /** Path to config files */
  5. http://www.php.net/define('CONFIG_PATH', BASE_PATH . 'config/');
  6. /** Path to class and interface files */
  7. http://www.php.net/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ć ?

Napisany przez: hwao 23.01.2007, 18:14:47

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 (http://www.php.net/strstr($class_name, '*')) { // load all class package
  3. $package_path = http://www.php.net/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ć http://pl.php.net/glob() ?

Napisany przez: DjKermit 23.01.2007, 18:28:59

Dzięki bardzo.

Cytat(hwao @ 23.01.2007, 18:14:47 ) *
Czy w tym momencie nie było by lepiej użyć http://pl.php.net/glob() ?

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.

Napisany przez: Turgon 23.01.2007, 18:32:35

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?

Napisany przez: bigZbig 24.01.2007, 16:19:57

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ć?

Napisany przez: Turgon 25.01.2007, 15:51:37

bigZbig: Podobno szybsze jest winksmiley.jpg Ale zastanowię się nad tabliczką smile.gif .

Napisany przez: DjKermit 25.01.2007, 17:52:44

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.

Napisany przez: bigZbig 25.01.2007, 19:34:40

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.

Napisany przez: LBO 25.01.2007, 19:49:48

hmmm, parsowanie to parsowanie... i "na oko" parsowanie kodu php jest bardziej czasochłonne od pliku ini, który jest prostszy

Napisany przez: Turgon 25.01.2007, 20:23:36

Ja zapisuje zserializowaną tablicę i działa sprawnie.

Napisany przez: Strzałek 20.02.2007, 09:29:17

O autoloadzie oraz przykładowa klasa generująca mapę opublikowałem post na moim blogu: http://strzalek.net/blog/8/autoload-automatyczne-ladowanie-klas

Napisany przez: Sh4dow 1.03.2007, 12:58:38

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.

Napisany przez: J4r0d 1.03.2007, 13:05:04

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..

Napisany przez: athabus 1.03.2007, 13:34:02

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.

Napisany przez: grzegorzr 14.07.2007, 06:10:01

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]

Napisany przez: Ludvik 14.07.2007, 09:29:16

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...

Napisany przez: deirathe 19.07.2007, 18:32:24

  1. <?php
  2. function __autoload($classname){
  3. $path = "library".DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR.http://www.php.net/str_replace("_", DIRECTORY_SEPARATOR , $classname).".class.php";
  4. if(!http://www.php.net/file_exists($path)){
  5. http://www.php.net/exit("Brak pliku: ".$path);
  6. }
  7. require_once($path);
  8. if(!class_exists($classname)){
  9. http://www.php.net/exit("Brak klasy: ".$classname);
  10. }
  11. }
  12. ?>

A ja zawsze wykorzystywałem to:P kod nie jest długi, co o tym myślicie?

Napisany przez: athabus 20.07.2007, 11:52:43

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.

Napisany przez: Turgon 20.07.2007, 22:11:45

athabus: W takim przypadku mapy się wydają być jedynym sensownym wyjściem, z punktu estetyki i także wydajności.

Napisany przez: athabus 20.07.2007, 22:31:07

wiem i sam właśnie map używam biggrin.gif

Napisany przez: kaniagandzowski 27.08.2007, 18:05:25

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


Nie wiem czy dobrze robie pisząc w tym temacie, zgłaszając taki błąd!!!.

Napisany przez: Sagnitor 5.07.2011, 12:53:10

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

Napisany przez: by_ikar 5.07.2011, 13:29:25

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

Napisany przez: Sagnitor 5.07.2011, 13:39:31

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

Napisany przez: Crozin 5.07.2011, 13:51:01

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 http://pl.php.net/manual/en/apc.configuration.php#ini.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.

Napisany przez: by_ikar 5.07.2011, 14:38:53

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.

Napisany przez: Sagnitor 6.07.2011, 10:52:46

@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.

Napisany przez: Crozin 6.07.2011, 11:17:27

@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. http://www.php.net/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?

Napisany przez: Sagnitor 6.07.2011, 11:26:29

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

Napisany przez: Zyx 13.07.2011, 10:14:52

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 http://pecl.php.net/package/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).

Napisany przez: wookieb 13.07.2011, 10:36:11

A to nie wystarczy już http://pl2.php.net/manual/pl/function.apc-store.php do przechowywania takiej "mapy" ?

Napisany przez: Zyx 13.07.2011, 11:12:50

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.

Napisany przez: Hellz 13.07.2011, 18:01:10

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.

Napisany przez: Zyx 14.07.2011, 18:47:00

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.

Napisany przez: Hellz 14.07.2011, 23:38:34

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ć.

Napisany przez: Zyx 16.07.2011, 12:20:44

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.

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