ugrás a tartalomhoz

PHP forráskódok tömörítése

bbalint · 2004. Jún. 8. (K), 21.00
PHP forráskódok tömörítése
A PHP Tokenizer kiterjesztésről, majd annak kód-zagyváláshoz kapcsolódó felhasználásáról íródott cikkeimet szerencsére nem kis érdeklődés kísérte. Újabb csoportos foglalkozásunkon ezúttal szkriptjeink megrövidítése érdekében vetünk be hatékony terápiás módszereket. Fontos követeleményünk marad továbbra is, hogy kódunk működésében, funkcionalitásában károsodást ne szenvedjen. Várakozásom szerint nem lep meg senkit, hogy ismét a Tokenizer kiterjesztést húzzuk elő varázskalapunkból.

A sorozatban megjelent

Mitől akarunk megszabadulni?


Szerény véleményem szerint a programozók többsége úgy írja forráskódjait, hogy azokat sorokra tördeli, behúzásokkal formázza, nem pedig egy szép szál sorba gépel be minden utasítást. Aki mégis a takarékosság ezutóbbi útján jár, az itt hagyja abba az olvasást.

Ezek a szóközök, tabulátorok és újsor karakterek jelentősen megkönnyítik a forráskód kezelését, bár végső soron csak az állományok méretét növelik. Bár egy ember számára valószínűleg sokkal olvashatóbb a program, ha nem egy sorban vannak az utasítások, egy átlagos PHP értelmező számára nem jelentenek semmi különöset, csak a különböző tokeneket választják el. A rengeteg megjegyzéssel és üres sorokkal tűzdelt PHP állományok nagyobbak, többet foglalnak a memóriából, és tovább tart beolvasni illetve feldolgozni azokat. Nem véletlen, hogy a PHP 5-ben újdonságkent megjelenik a php_strip_whitespace() függvény, mely hasonló célokat szolgál, mint az itt bemutatott megoldás.

Nem szabad azonban teljesen fellelkesülni a térközök elmozdításával. Vannak ugyanis olyan tokenek, melyek nem állhatnak egymás mellett. Ilyen például a változó (T_VARIABLE) és az "as" (T_AS) token:

<?php foreach($arrayas$key=>$value){ ?>
Ez a kód-részlet hibát fog eredményezni, mert a bizonyos "as" elemet a feldolgozó az azt megelőző változó (T_VARIABLE token) részeként ismeri fel; ezért kell egy elválasztó token a kettő közé, amely lehet akár egy (vagy több) megjegyzés, szóköz, újsor, stb.

A sok térközön felül a megjegyzéseket is nyugodtan ki lehet gyomlálni, azok is csak a helyet foglalják: sokszor csak dokumentációt tartalmaznak, pedig azt szerintem jobb lenne egész máshol tartani (például egy Documentation.html állományban).

A PHP-ban létezik egy érdekes és meglehetősen hosszú nyitó/záró tag, név szerint a <script language="php"></script> páros. Ezekből érdemes <?-t és ?>-t csinálni, ugyanúgy mint az összes többi (<?php <%) nyitó és záró elemből. Mivel most a méret a lényeg, ezért nem mindegy az a néhány karakter sem, amit így nyerünk. További előnye a <? rövid nyitó elem használatának, hogy nem kell utána szóközt, újsort vagy tabulátort írni, hanem egyből jöhet például egy értékadó utasítás:
<?$változóm='értéke'?>
összehasonlítva a következő kód csak feldolgozási hibát ad:
<?php$változóm='értéke'?>
Gondolhatunk még az utolsó pontosvesszőkre és záró elemekre is. Ha PHP programjaink végén elmarad ezek valamelyike, az egyáltalán nem zavarja az értelmezőt. Arra viszont figyelnünk kell, hogy csak az egyiket lehet elhagyni. A következő kód csak hibát ad:
<?exit
az alábbi kettő viszont teljesen korrekt:
<?exit;
<?exit?>
Ezekből az következik, hogy a PHP kód végén elhelyezett pontosvessző és záró PHP elem párosból egy pontosvesszőnyit érdemes meghagyni, az utolsó záró elemet pedig pontosvesszőként praktikus tovább éltetni.

Létezik két nagyon hasonló konstrukció a PHP-ben az else if és az elseif. Bár ezek lényegében ugyanazt a logikai műveletet eredményezik, belsőleg nem ugyanazt a kódot generálják. Lássuk a következő két példát:

<?php
if(1);
else if(0);
else if(1);
else;
  
if(1);
elseif(0);
elseif(1);
else;
?>
A második megoldás nem csak rövidebb, hanem gyorsabb is, nem fogyaszt annyi memóriát. Míg az elsőnél a Zend motor megkezd egy else ágat és abba még beletesz egy if-et majd még egy else-t, addig a második megoldásnál csak az első elseif-nél hoz létre egy elseif-listát és ezután ebbe kerülnek bele a további feltételek.

Lássuk a medvét

A következőkben bemutatnám azt a kódot, ami korábbi tokenizer ismereteinkre építve elvégzi a kirótt feladatot. A logikát egy függvényben valósítjuk meg, amely a php_strip_whitespace() fantázianevet kapta, a fent említett okok miatt. Azt fontosnak tartom megjegyezni, hogy a PHP 5-ös azonos nevű új függvénye csak egy szóközt csinál a T_WHITESPACE tokenekből és törli a megjegyzéseket, mi pedig ennél többet vállaltunk. Függvényünk egy paramétert vár, mely karaktersorozatként átadott PHP kódot tartalmaz, és ennek a rövidített változatával tér vissza.

<?php
  function php_strip_whitespace($code){
   $tokens = token_get_all($code);               // kérjük a $code PHP tokenjeit
   
   $return = '';                                 // visszatérési érték
   while(list($i, $token) = each($tokens))       // végig kell menni az összes tokenen
    if(is_string($token))                        // ez a token egy sztring, nincs mit tenni
     $return .= $token;
    else switch($token[0]){                      // $token[0] tartalmazza a token típusát
     case T_OPEN_TAG:                            // nyitó tag, bármi is legyen az, inkább váljon <?-vé
      $return .= '<?';
      
      break;
     case T_CLOSE_TAG:                           // záró tag
      if(!@$tokens[$i + 1]){                     // ha az utolsó, akkor nem kell kitenni, de:
       if(@$tokens[$i - 1] !== ';'               // ha előtte nem volt pontosvessző,
       && !@($tokens[$i - 1][0] == T_WHITESPACE
        && $tokens[$i - 2] === ';'))
        $return .= ';';                          // akkor mostmár legyen
      }else
       $return .= '?>';                          // mindenféle záró tagek is inkább ilyenek legyenek
      
      break;
     case T_COMMENT:                             // ilyenkor egy megjegyzéssel
     case T_ML_COMMENT:                          // vagy töbsoros megjegyzéssel van dolgunk, ami
     case T_WHITESPACE:                          // üresség, és nem biztos, hogy kívánatos, ezért
                                                 // jöjjön egy szép feltétel-lista,
                                                 // mely eldönti, hogy kell-e oda egy "elválasztó"
      
      if(@($tokens[$i - 1][0] == T_CLASS         // class, class Osztály
      || $tokens[$i - 1][0] == T_EXTENDS
      || $tokens[$i + 1][0] == T_EXTENDS         // extends, class Osztály extends Másikosztály
      || $tokens[$i - 1][0] == T_FUNCTION        // function, function Függvény
      || (($tokens[$i - 1][0] == T_CASE          // ha előtte egy case, echo, print, return áll, akkor
        || $tokens[$i - 1][0] == T_ECHO          // meg kell nézni mi is jön utána...
        || $tokens[$i - 1][0] == T_PRINT
        || $tokens[$i - 1][0] == T_RETURN
        || ($tokens[$i - 1][0] == T_ELSE         // else, de ugye utána nem if jön...
         && $tokens[$i + 1][0] != T_IF))         // mert így az else és az if egybeíródik és lesz belőle elseif
       
       && ($tokens[$i + 1][0] == T_ARRAY         // array(), return array(...)
        || $tokens[$i + 1][0] == T_BREAK         // break, else break
        || $tokens[$i + 1][0] == T_CLASS_C       // __CLASS__, echo __CLASS__
        || $tokens[$i + 1][0] == T_CONTINUE      // continue, else continue
        || $tokens[$i + 1][0] == T_DECLARE       // declare, else declare
        || $tokens[$i + 1][0] == T_DNUMBER       // törtszám, case 1.23: case .5:
        || $tokens[$i + 1][0] == T_DO            // do ciklus, else do{...}while(...);
        || $tokens[$i + 1][0] == T_ECHO          // echo, else echo
        || $tokens[$i + 1][0] == T_EMPTY         // empty(), return empty(...)
        || $tokens[$i + 1][0] == T_EXIT          // exit, else exit;
        || $tokens[$i + 1][0] == T_EVAL          // eval(), case eval()
        || $tokens[$i + 1][0] == T_FILE          // __FILE__, return __FILE__
        || $tokens[$i + 1][0] == T_FOR           // for ciklus, else for(...);
        || $tokens[$i + 1][0] == T_FOREACH       // foreach ciklus, else foreach(...);
        || $tokens[$i + 1][0] == T_FUNC_C        // __FUNCTION__, echo __FUNCTION__
        || $tokens[$i + 1][0] == T_GLOBAL        // global, else global $...
        || $tokens[$i + 1][0] == T_INCLUDE       // include, return include ...
        || $tokens[$i + 1][0] == T_INCLUDE_ONCE  // include_once, echo include_once(...)
        || $tokens[$i + 1][0] == T_ISSET         // isset, return isset(...);
        || $tokens[$i + 1][0] == T_LINE          // __LINE__, echo __LINE__
        || $tokens[$i + 1][0] == T_LIST          // list(), else list(...)
        || $tokens[$i + 1][0] == T_LNUMBER       // egész szám, echo 0xff; echo 123
        || $tokens[$i + 1][0] == T_PRINT         // print, else print ...
        || $tokens[$i + 1][0] == T_REQUIRE       // require
        || $tokens[$i + 1][0] == T_REQUIRE_ONCE  // require_once
        || $tokens[$i + 1][0] == T_RETURN        // return, else return ...
        || $tokens[$i + 1][0] == T_STATIC        // static, else static $...
        || $tokens[$i + 1][0] == T_STRING        // állandó vagy függvénynév, echo phpversion(); echo PHP_VERSION
        || $tokens[$i + 1][0] == T_SWITCH        // switch, else switch(...){}
        || $tokens[$i + 1][0] == T_WHILE))       // while, else while(...);
      
      || $tokens[$i - 1][0] == T_NEW             // new, new Osztály();
      
      || ($tokens[$i + 1][0] == T_AS             // utána 'as' előtte változó, foreach($tömb as ...)
       && $tokens[$i - 1][0] == T_VARIABLE)      // míg ilyenkor nem kell: foreach(array()as ...)
      
      || (($tokens[$i + 1][0] == T_LOGICAL_AND   // logikai műveletek (AND, OR és XOR), azt kell vizsgálni, nincs-e
        || $tokens[$i + 1][0] == T_LOGICAL_OR    // előttük olyasmi, amivel esetleg "összekeverhető"
        || $tokens[$i + 1][0] == T_LOGICAL_XOR)
       && ($tokens[$i - 1][0] == T_VARIABLE      // változó, 1 XOR $változó
        || $tokens[$i - 1][0] == T_STRING        // állandó, 3.14 AND M_PI
        || $tokens[$i - 1][0] == T_FILE
        || $tokens[$i - 1][0] == T_LINE          // __LINE__, __LINE__ AND 1
        || $tokens[$i - 1][0] == T_FUNC_C
        || $tokens[$i - 1][0] == T_CLASS_C))
      
      || (($tokens[$i - 1][0] == T_LOGICAL_AND   // megint a logikai műveletek, de most utánuk kell nézni
        || $tokens[$i - 1][0] == T_LOGICAL_OR
        || $tokens[$i - 1][0] == T_LOGICAL_XOR)
       && ($tokens[$i + 1][0] == T_STRING        // állandó vagy függvény, OR isset
        || $tokens[$i + 1][0] == T_FILE
        || $tokens[$i + 1][0] == T_LINE
        || $tokens[$i + 1][0] == T_FUNC_C
        || $tokens[$i + 1][0] == T_CLASS_C
        || $tokens[$i + 1][0] == T_EXIT          // exit vagy die, ... OR die(...)
        || $tokens[$i + 1][0] == T_EVAL
        || $tokens[$i + 1][0] == T_LIST          // list, AND list(...) = 
        || $tokens[$i + 1][0] == T_LNUMBER
        || $tokens[$i + 1][0] == T_DNUMBER))))
      $return .= ' ';                            // ha már annyira kell, akkor
                                                 // legyen csak egy szóköz belőle
      
      break;
     default:                                    // minden más tokent békén kell hagyni,
      $return .= $token[1];
      break;
    }
   
   return($return);                              // vissza a feladónak
  }
?>

A forráskódra közelebbi figyelemmel lévők számára kiderül, hogy bizonyos fura esetekben is meghagyja a T_WHITESPACE tokeneket a függvény:

<?php
  case array():
  return exit;
  echo echo;
?>
Ilyen esetekben azonban már az eredeti forráskód sem értelmes PHP, ezért már úgysem tudjuk tovább rontani...

Ez az egyszerűen használható eszköz segíthet abban az esetben, ha nem tudunk kód gyosítót telepíteni szerverünkre, de az állomány rendszer terhelése már gondot okoz webhelyünk magas látogatottsága miatt. Ezúttal sem egy komplett obfuszkátor megvalósítása volt a cél, bár az eredményül adódó kódok jelentősen nehezebben olvashatóak.

Mára ennyit, a forráskód használatához és megértéséhez sok sikert!
 
bbalint
PHP-vel 2000 óta foglalkozik, köszönhetően az index.hu forráskódjának és maga kíváncsi természetének. Jelenleg egy reklámszervező cégnél dolgozik, mint (web)programozó és rendszergazda.
1

Teszteredmenyek? ;) bye, B

Benjamin · 2004. Jún. 9. (Sze), 10.46
Teszteredmenyek? ;)

bye, Benjamin
2

tesztel és

bbalint · 2004. Jún. 11. (P), 16.41
jelentős kód-gyorsulást nem fog úgysem eredményezni, de pl a PHPNuke mainfile.php, sql_layer.php és hasonló összetevőinek méretét a felére csökkenti. ezek az állományok azonban igen gyakran vannak feldolgozva, ezért sebességjavulás ilyenkor bizton tapasztalható...
(talán) még a drupalt "tömörítve" is érezhető valami, mivel - ahogy láttam - egy-egy modul mindenféle dolga egy filébe van sűrítve,
3

eredmények...

bbalint · 2004. Jún. 16. (Sze), 23.42
egy merész gombolattal készítettem egy scriptet ami végignézi a magdott könyvtárban található php kiterjesztésű filéket és megállapítja róluk, hogy mennyivel lennének kisebbek egy php_strip_whitespace() függvényhívás után.
az egészről végül generál egy táblázatocskát, ím néhány példa:
ADODB 4.23
Drupal 4.4.1
phpBB 2.0.8a
PHPNuke 7.2
4

eredmények?

Anonymous · 2004. Jún. 20. (V), 14.05
mi az hogy KiB?
5

Re: KiB?

T.G · 2004. Jún. 20. (V), 20.06
Ha ez a egy mértékegység a fájl méretéről, akkor mi lehet? :)
KiB = KB (azaz KiloByte)
6

Kicsit pontosabban...

Bártházi András · 2004. Jún. 20. (V), 23.24
Ez, így, ebből a szempontból... Szóval egy kicsit pontosítsunk. ;)

Adott a "kilo" előtag, amit ugye egy kilogramm, kilométer és stb. kontextusban 1000-nek értünk. Azaz egy kilogramm 1000 gramm és ugyanez a kilométerre is.

Ha ugyanezt a kilo-t a byte elé tesszük, az lenne és az a logikus, hogy szintúgy 1000-et értsünk alatta, azaz 1 kB = 1000 byte. Ennek ellenére a rossz értelmezése terjedt el a dolognak, és 1 kB != 1024 byte-ot szoktak érteni alatta.

Erre lett azonban bevezetve a hülyén hangzó kibi nevű előtag, ami a bináris "kilo"-nak megfelelő szorzót, azaz az 1024-et jelzi. 1 KiB = 1024 byte.

A bevezetője ennek az oldalnak hasznos olvasmány, illetve a táblázatokból az is megtudható, hogy mi a helyes írásmódja a mértékegységeknek, mikor kell kis B-t, kis K-t, mikor kell nagy B-t, nagy K-t írnunk.

-boogie-
7

WOW :)

T.G · 2004. Jún. 21. (H), 09.39
Érdekes, engem sosem zavart, hogy 1 kilo az valahol 1000-t, valahol 1024-et jelentet, mert mindig egyértelmű volt, hogy hol melyiket, de ezzel a IEC Standard-dal, pont azt érték el, hogy már nem egyértelmű. :))))
mindenesetre jogos, jelen példában a KiB kibibyte-ot jelentett :)
13

nuke dög

Anonymous · 2004. Szep. 1. (Sze), 23.32
sejtettem mindig hogy a nuke 1 dög;) csomó vacak/sallang van benne, és tessék...
8

par apro bug..

Anonymous · 2004. Jún. 28. (H), 21.19
Szia,
A fentivel a kovetkezo gondok vannak:
a foreach($this->attribute as $key => $value) tipusu ciklusokat elrontja, valamint a return new Akarmi() tipusuakbol is returnnew-t csinal..

Egyiknel az i-1. token tipusat kell ellenorizni T_RETURN-re, mig utobbi esetben az as elotti T_OBJECT_OPERATOR-t kell figyelni.

Amugy jo, feleakkora lett a kodom (es szazadannyira atlathato :D)
Bar mondjuk kimondott gyorsulast sem tapasztaltam..
De meg tesztelem :-) Minden tizedmasodperc szamit! :-)
9

rovarírtás

bbalint · 2004. Jún. 29. (K), 19.56
bátorkodtam javítani a kódot és ezen a címen elérhetővé tenni.

a javított változatban amúgy nem a T_OBJECT_OPERATOR van figyelve, hanem inkább a T_STRING: a T_OBJECT_OPERATOR és a T_AS közöt nem mindig csak egy token foglal helyet
<?php
 forech(${'változó'} as $kulcs => $érték);
?>

bbalint
10

in_array()?

Hojtsy Gábor · 2004. Jún. 29. (K), 20.09
Különben miért nem in_array() segítségével ellenőrzöd a következő/előző tokeneket? Az sokkal rövidebben leírható lenne. Nem kellene ennyiszer ugyanazt a kódot leírni.
11

hmü

bbalint · 2004. Jún. 30. (Sze), 08.28
az lehet... de.
számomra így olvashatóbb, de ha nagyon lassú akód, akkor a bácsi megcsinálhatja in_array()olgatással, statikus változók felvételével.

amúgy, így jó?
12

:)

Anonymous · 2004. Aug. 2. (H), 11.20
Számára így olvashatóbb, meg aztán így van értelme utána a méretcsökkenés vizsglatának ,-)
Vicczen kívül teccik ez is, mint a többi cikked a tokenizerrel...

BYE: TeeCee :o)
14

<?php miért?

Anonymous · 2005. Nov. 24. (Cs), 16.54
Készítettem egy scriptet ami a megadott könyvtárból kiindulva a php scripteket megszabadítja fölös terheitől. A MÁSODIK kód már majdnem jónak bizonyult, de a <?php után némelyik fájlban nincs szóköz. Egyébként mintha a cikkben az lett volna hogy a <?php helyett csak <? kell és nekem mégsem szedi ki a php szót belőlle.

Tudom ha jól átrágnám megtalálhatnám magam is a hiba okát de most nincs sok időm. Ha esetleg...

Amúgy már nagyon régen szerettem volna egy ilyen kis gyönyörűséget, úgy hogy a cikk COOL!
15

Belenéztem egy kicsit...

Anonymous · 2005. Nov. 24. (Cs), 16.57
Ja, most láttam a fájlokban, hogy a hiba akkor jelentkezik, ha:
<?php
if( ...
Tehát ha egy if utasítás következik akkor sajna nincs space.
17

teszt

bbalint · 2005. Nov. 25. (P), 00.23
egy gyors teszt (lásd. alább) alapján nekem semmi rosszat nem csinált. továbbra is szépen kérem, tessék a rosszul "tömörített" forráskód beidézését (a bíróság elé ;-)

<?php
 if($valami)
  echo semmi;
?>
"tömörítve":

<?if($valami)echo semmi;
a tesztelés egy AppServ által telepített, 4.3.3-as PHP-val (CGI SAPI) történt.

bbalint
16

ööö

bbalint · 2005. Nov. 25. (P), 00.17
(elöljáróban: éjfél múlt és második bór :-/)

szóval, a PHP-ban úgy van, hogy a <?php T_OPEN_TAG nemcsak abból a két karakterből (kisebb mint- és kérdőjel) áll, hanem egy vagy több térköz karakterből (szóköz, tabulátor, lapdobás, újsor- és kocsivissza-) karakterből is. talán ebből is eredhet a hiba, node "mi lenne ha?" a problémás file első [hibásan "tömörített"] sorát beidéznéd...

én is boldogabb volnék,

bbalint