Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Włączanie plików + autoloader
Forum PHP.pl > Forum > PHP > Pro
Stron: 1, 2, 3, 4
hawk
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
DeyV
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.
Imperior
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. define('NN_SEP', DIRECTORY_SEPARATOR);
  3. define('NN_LIB', dirname(dirname(__FILE__)).NN_SEP);
  4. define('NN_PRIVATE', dirname(NN_LIB).NN_SEP);
  5.  
  6. function __autoload($sName) {
  7. if (!(CORE :: Import($sName) || CORE :: ImportEx($sName))) {
  8. echo 'Kaboooom! Reason? '.$sName.' not found!!!';
  9. }
  10. }
  11.  
  12. class CORE {
  13. public static function Import($sName, $sFind = null, $bSearch = true) {
  14. if (!isset($sFind)) {
  15. $sFind = $sName;
  16. }
  17. if ($bSearch && (class_exists($sFind, false) || interface_exists($sFind, false))) {
  18. return true;
  19. }
  20. $sName = strtolower($sName);
  21. $aArr = explode('_', $sName);
  22. array_pop($aArr);
  23. $sPath = NN_LIB.implode(NN_SEP, $aArr).NN_SEP.$sName.'.php';
  24. if (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 static function ImportEx($sName) {
  35. $aArr = explode('_', $sName);
  36. array_pop($aArr);
  37. array_push($aArr, 'package');
  38. if (CORE :: Import(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.
hawk
@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 = 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.
bela
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
Imperior
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?
hawk
@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
hawk
@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.
Imperior
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?
Vengeance
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
hawk
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.
Vengeance
ale to jest php a nie java smile.gif

Co do autoloadera... co za problem zrobić jakiś explode po znaku _
i odpowiednio sprawdzić?
hawk
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.
Seth
Troche to stary kod ale moze sie przyda:

  1. <?php
  2. 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 ( preg_match( '/^[a-z0-9_]+(.[a-z0-9_]+){0,}(.*)?$/i', $nsPattern ) )
  14. {
  15. $nsPattern = str_replace( '.', '/', $nsPattern );
  16.  
  17. if ( preg_match( '/^(.*)/*$/', $nsPattern, $match ) )
  18. {
  19. $patch = INCLUDE_PATH.$match[1];
  20.  
  21. if ( $handle = opendir( $patch ) )
  22. {
  23. while ( false !== ( $file = readdir( $handle ) ) )
  24. {
  25. $i = 0;
  26.  
  27. if ( $file != '.' && $file != '..' && preg_match( '/^(.*).class.php$/', $file, $match ) )
  28. {
  29. $filePath = $patch.&#092;"/\".$file;
  30.  
  31. if ( file_exists( $filePath ) && is_file( $filePath ) && !class_exists( $match[1] ) )
  32. {
  33. require_once( $filePath );
  34.  
  35. $i++;
  36. }
  37. }
  38. }
  39.  
  40. closedir($handle);
  41. }
  42.  
  43. return ( $i ? 1 : 0 );
  44. }
  45. else
  46. {
  47. $className = substr( strrchr( $nsPattern, '/'), 1 );
  48. $filePath = INCLUDE_PATH.$nsPattern.&#092;".class.php\";
  49.  
  50. if ( file_exists( $filePath ) && 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 :/
bela
Seth, a czy te pregi nie spowalniają bardzo przy dużej ilości plików ?
Seth
Jakies spowolnienie napewno jest ale za to mozna latwo zaincludowac kilka klas winksmiley.jpg
bela
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
Nievinny
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. print Debuger::errorDisplay( $Error );
  11. }
  12. break;
  13. }
  14. }
  15.  
  16. ?>

Tylko trzeba zdefiniować stała ROOT_PATH i dodać do nazwy rozszerzenie i zdefiniować klasę CORE.
Warunki:
  • nazwy klas musza byc takie same ja nazwy plikow (bez rozszerzenia) lub odwrotnie winksmiley.jpg
  • Może przy dużej ilości katalogów spowoli trochę, ale można dodać funkcje ładowania kilku klas i odrzucić katalogi smile.gif
Za jakiś czas dołącze dokładniejszy kod, może trochę wolnawe, ale samo znajdzie ścieżkę dostępu do plików smile.gif
hawk
@bela_666: pokaż pokaż pokaż tongue.gif
Vengeance
Nievinny: jakbyś zrobił cache jakiś to ok. Teraz uważam, że to jest najgorsze z najgorszych rozwiązań smile.gif
bela
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. error_reporting(E_ALL|E_STRICT);
  3. function get_microtime() {
  4. list($usec, $sec) = explode(&#092;" \",microtime());
  5. return ((float)$usec + (float)$sec);
  6. }
  7.  
  8. global $fileContext;
  9. $fileContext = 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 = end(explode('.', $v->getPathname()));
  18. // is svn dir
  19. $name = $v->getFilename();
  20. $first = 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'][] = array($v->getPathname(), token_get_all(file_get_contents($v->getPathname())));
  30. //print $v->getPathname() . \"<br />n\";
  31. }
  32. }
  33.  
  34. }
  35.  
  36. //dump($fileContext);
  37. $classes = array();
  38. $key = 0;
  39. foreach($fileContext as $file) {
  40. static $i;
  41. foreach ($file as $c) {
  42. if(is_array($c)) {
  43. $a = false;
  44. foreach ($c as $k => $v) {
  45. if(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[] = array($v[1], 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. ?>
Vengeance
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(!is_file($oFile->getPathname()))
  8.                continue;
  9.             $aTokenAll = token_get_all(file_get_contents($oFile->getPathname()));
  10.             $bIsClass = false;
  11.             foreach ($aTokenAll as $iToken)
  12.             {
  13.                if (!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. ?>
Vengeance
Linia nr 9: zmieniasz ją na:
Kod
           if(!is_file($oFile->getPathname()) OR $oFile->getFileName == '.svn')


Powinno zadziałać
Nievinny
@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. define( 'IN_CORE', true );
  4.  
  5. // ---- Definicja stałych
  6. define( 'ROOT_PATH', './' );
  7. 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. 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. print Debuger::errorDisplay( $Error );
  26. }
  27. $bLoad = true;
  28. break;
  29. }
  30. }
  31. if( $bLoad === false ) {
  32. print Debuger::errorLiteDisplay( 'Nie można odnaleźć podanej klasy' );
  33. }
  34. }
  35.  
  36. ?>


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

Chyba tes sposób lepszy, czy nie?
Vengeance
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
Nievinny
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
Vengeance
A po co foreach? Jeszcze opozniac ? To $map[$className] jest takie zle? W sumie to i tak kazdy robi jak mu wygodniej ;]
hawk
@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 static $aMap;
  8.  
  9. public static function addMap($aMap) {
  10. self::$aMap = self::$aMap == null ? $aMap : array_merge(self::$aMap, $aMap);
  11. }
  12.  
  13. public static function load($sClass) {
  14. if (array_key_exists($sClass, self::$aMap)) {
  15. require self::$aMap[$sClass];
  16. }
  17. }
  18. }
  19. ?>
Vengeance
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
Nievinny
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:
  • Czas gen bez cache: 0.0617 sec
  • Z cache: 0.0111 sec
  • Czyli o 0.0516 sec szybciej
  • Czyli ok 5,5 raza szybciej
Jak ktoś jest zainteresowany klasą cache to na PW
chmolu
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 (!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(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.
Nievinny
A ja generujesz listę klas? Tu masz od razu generator
chmolu
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
hawk
@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 = 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.
chmolu
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
Nievinny
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
Imperior
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
hawk
@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.
Bora
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.
Vengeance
A czy są szybsze od zserializowanej tablicy, zapisanej w pliku ? smile.gif
Imperior
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.
hawk
@Bora: Pliki ini moze i sa wczytywane szybciej, ale zamiana %APP_DIR% na odpowiednia sciezke niestety zepsuje tutaj wydajnosc.
orson
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
hawk
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.
Nievinny
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?
orson
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. print $iniFile['path']['mainAppPath'].$iniFile['clases']['dbclass'];
  4. print $iniFile['path']['mainAppPath'].$iniFile['clases']['session'];
  5. //defaultem
  6. 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
hawk
@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.
orson
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
dr_bonzo
@Nievinny: wyrzuc wyjatek w tej metodzie i sprawdz. Wyjatek wedruje w gore do ekranu (error) dopuki go nie zlapiesz gdzies po drodze.
Nievinny
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. 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?
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2024 Invision Power Services, Inc.