Blackout - przedstawienie projektu

Już sporo czasu upłynęło od ostatniego wpisu na moim blogu, tak więc stwierdziłem, że nadszedł czas na podzielenie się częścią informacji o tym nad czym właśnie pracuję.

Być może pamiętacie poprzedni wpis  „recenzujący” skrypt xNova służący do tworzenia własnych klonów gry przeglądarkowej Ogame. Postanowiłem przepisać go po swojemu i uzyskać z niego w miarę uniwersalny silnik gier MMORPG via WWW. Zależy mi na tym, aby nie był ściśle ukierunkowany na grę dziejącą się w kosmosie, żeby po dokonaniu zmian w szablonach, plikach językowych i ewentualnie lekkich zmianach mechaniki móc na nim postawić dowolną inną grę strategiczno-ekonomiczną. Skrypt zdecydowałem się ochrzcić Blackout (nazwę, tak jak zazwyczaj wymyślił Rhino, chwała mu za to). Może nie jest to nazwa super oryginalna, ale bardzo chciałem uniknąć kolejnego potworka o nazwie "Super Game Uber-pro Elo Melo Maka-Faka Engine".

Jak wskazują daty modyfikacji niektórych plików – przepisywanie skryptów zacząłem dziesiątego lutego tego roku. Tak więc całkiem przypadkiem dziś mija miesiąc – sądzę, że to niezły pretekst do opisana tego co udało mi się zrobić i tego co mnie jeszcze czeka.

Tak więc: zacząłem od przygotowania najbardziej potrzebnych elementów do rozpoczęcia pisania. Poprawiona klasa baz danych, wzorowana lekko na tej użytej jeszcze w Iron CMS-ie, bardzo prosty logger błędów i króciutki system obsługi szablonów (nie ma chyba nawet 60 linijek).

Zacząłem rzecz jasna od napisania plików dołączanych do każdej strony – mamy więc znany z oryginalnej xNovy plik common.php – w przebudowanej formie, ale spełnia identyczne w założeniu zadanie – wykonuje czynności potrzebne przy wczytaniu każdej strony (np. sprawdzenie czy gracz nie jest zbanowany). W moim silniku pozostały jeszcze resztki ze sławetnego pliku todofleetcontrol.php (możecie o nim przeczytać w tym wpisie, punkt 10). Na razie okroiłem go tylko i przemianowałem na pure_evil.php – sądzę, że to bardziej adekwatna nazwa :D. Potem zostanie usunięty, a każda podstrona będzie ładować tylko to, czego faktycznie potrzebuje.

Jeśli chodzi o zakładki, które dla użytkownika są najważniejsze to:’

  • Podgląd – gotowy w 80%, nie gotowe opuszczanie kolonii i zmienianie nazwy planety
  • Budynki, laboratorium, stocznia, obrona – odtworzone działanie i wygląd z oryginalnej xNovy, mocna przebudowa planowana na następną wersję
  • Oficerowie – gotowe w 100%, jeśli nie liczyć części niedokończonych tłumaczeń
  • Handlarz – gotowy w 20%
  • Sojusz – na początku próbowałem wzorować się na oryginalnym alliance.php jednak po 4 godzinach stwierdziłem, że nie ma to żadnego sensu. Aktualna koncepcja jest rozrysowana na kartce i czeka na realizację
  • Flota – działa sama czynność wysyłania flot, w większości działa misja walki
  • Wiadomości – napisane całkowicie od nowa – jest tam kilka rzeczy do dorobienia i poprawki, ale ogólnie mówiąc plik jest prawie gotowy
  • Galaktyka – ciekawa historia: przerażony ilością zapytań jakie generuje ta zakładka (średnio 50 na odsłonę, przy 1 planecie w pokazywanym systemie) i ilością funkcji jakich używa tylko ta zakładka (17 plików – 1200 linijek) postanowiłem napisać go od zera. Zapytania w pętli zastąpione jednym podwójnym JOIN-em, wszystko ładnie umieszczone w jednym pliku – sądzę, że w wersji ostatecznej plik nie przekroczy 300 linijek
  • Imperium, Surowce, Technologia – gotowe
  • Rekordy – nawet nie zaczęte
  • Statystyki – prawdopodobnie działają, lecz to się okaże dopiero po zrobieniu skryptu do przeliczania punktów
  • Szukaj – gotowe
  • Zbanowani – gotowe, przebudowana tabela trzymająca bany, użyto relacji
  • Chat – gotowy, tylko trzeba go ujednolicić w jeden plik i ogarnąć system BBCode – jeden dla całej gry
  • Kontakt – działa
  • Opcje – mocno odchudzone, nie działa jeszcze urlop i usuwanie konta
  • Panel administracyjny – grubsza sprawa, temat na osobny wpis

Tak, wiem, w większości zrobiłem sobie z tego wpisu kolejne wcielenie notatek odnośnie skryptu, mam jednak nadzieję, że umieszczenie tego publicznie pomoże mi się zmotywować do dalszej pracy.

Jak widać, wiele jeszcze przede mną, myśląc o kilku plikach robi mi się słabo :D, ale jakoś to będzie… Więcej konkretów, mniej suchych notatek obiecuję już w przyszłym wpisie.

Przegląd skryptów genialnych #3 - xNova

Witajcie, w kontynuacji dawno zaczętej serii, w której przyglądam się największym „perełkom” pośród skryptów PHP. Dzisiaj pod nóż pójdzie coś specjalnego, prawdziwy as wśród asów. Drodzy Państwo, przywitajcie xNovę.

Najpierw krótko o samym skrypcie. xNova to skrypt, który ma umożliwić tworzenie gier MMO via WWW wyglądających dokładnie jak OGame (mówimy tutaj o starej wersji, bo od czasu wydania ocenianej wersji skryptu, OGame doczekał się całkowitej przebudowy interfejsu).

Ciężko jest nawet powiedzieć jaką wersję będę opisywał, bo xNova od wieków nie posiada oficjalnej strony/forum/bloga. Tak więc namnożyło się bez liku wersji (najczęściej po prostu przeróbek prywatnych osób, które połatały 3% bugów i zoptymalizowały kilka zapytań). Wersja którą mam, była opisana jako „xNova 0.3 Multilanguage”, ale zdefiniowana wewnątrz skryptu stała VERSION miała wartość 0.8, więc mamy niezły rozrzut.

W związku z ilością bugów oraz masą amatorsko tworzonych „nowych wersji” najczęstszymi poradami na bugi zamieszczonymi na „profesjonalnych suportach xNovy” nie są wcale poprawki błędnej linijki, tylko zdanie „podmień sobie plik z wersji 1.1A/5.5/x.x by Ktośtam” – to też bardzo skutecznie tworzy chaos w plikach.

No, ale przejdźmy do właściwej części wpisu:

  1. Już patrząc w strukturę katalogów możemy mieć pewne pojęcie o skrypcie. W katalogu głównym umieszczono foldery scripts i js. Oba zawierają skrypty JavaScripts, część z nich jest identyczna i po analizie można dojść do tego, że około 12 z plików nie jest nigdzie używanych.

  2. Wygląd skryptu opiera się na dwóch folderach: templates, w którym leżą pliki dla systemu szablonów i skins, w którym leżą obrazki i pliki CSS. Bardzo piękne założenie, szkoda tylko , że poza tymi folderami w katalogu głównym mamy jeszcze foldery css i images, więc zmiana szablonu nie daje możliwości pełnej modyfikacji wyglądu bez modyfikacji zawartości tych folderów. Ponadto wg mnie pliki JavaScript też powinny być zależne od wybranego theme’u, a więc leżeć w jego katalogu.

  3. Przejdźmy do instalacji. Uruchamiam folder z xNovą a tam bardzo prosty do zdiagnozowania błąd. Skrypt wymaga włączonej dyrektywy short_open_tag Wracamy po szybkim restarcie serwera :)

  4. Po zmianie konfiguracji próbuję jeszcze raz i co widzę? Błąd połączenia z bazą danych zamiast instalatora. W pliku index.php jest odpowiednia instrukcja, która przekierowuje do instalatora jeśli config.php jest pusty. Niestety, ktoś udostępniając tą paczkę ludziom nie pomyślał i zostawił swojego wypełnionego configa. Tak więc poprawmy jego błędy i wyczyśćmy plik config.php. Do trzech razy sztuka…

  5. Próbujemy jeszcze raz i oto naszym oczom ukazuje się przepiękny instalator… po francusku. Szkoda tylko, że silnik posiada możliwość zmiany języka w całej grze, w instalatorze także. Niestety nikt nie pomyślał nad możliwością wyboru języka instalacji.

  6. Lecąc na czuja wypełniamy tabelkę z danymi do DB, standardowy układ jak w każdym skrypcie. Ukazuje nam się podstrona z pociesznym napisem „La base de donnée a bien été installée!”. Szkoda tylko, że ktoś jej pożarł całego CSS-a.

  7. Styl powraca, tworzymy konto admina i mamy rejestrację z głowy. Czas się zalogować.

  8. Bez problemu loguję się do gry, a tam wszystko po… tak, zgadliście, po francusku. Instalator z automatu ustawia adminowi język na francuski. Trzeba go przestawić w opcjach konta/w bazie danych.

  9. Teraz, kiedy gra już działa, przyjrzyjmy się plikom: widać wyraźnie, że każda z podstron najpierw dołącza plik common.php, a więc to jemu przyjrzymy się na początku. Co my tam mamy? Widać, że istnieje możliwość ustawienia własnych ścieżek do kilku folderów oraz domyślnego języka gry. Tylko dlaczego to nie jest w configu? Potem widzimy dołączanie kilku plików, w tym todofleetcontrol.php.

  10. Ten plik jest na tyle wyjątkowy, że zdecydowałem się go pokazać w całości:

    <?php
    /**
    *
    *
    * @version 1
    * @copyright 2008 By Chlorel for XNova
    */
    
    // Fonctions deja 'au propre'
    include($ugamela_root_path . 'includes/functions/FlyingFleetHandler.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseAttack.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseStay.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseStayAlly.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseTransport.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseSpy.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseRecycling.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseDestruction.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseColonisation.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseExpedition.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SendSimpleMessage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SpyTarget.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/RestoreFleetToPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/StoreGoodsToPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckPlanetBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckPlanetUsedFields.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CreateOneMoonRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CreateOnePlanetRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertJavaScriptChronoApplet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsTechnologieAccessible.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetBuildingTime.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetRestPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetElementPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetBuildingPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsElementBuyable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckCookies.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ChekUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertGalaxyScripts.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyCheckFunctions.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyRows.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetPhalanxRange.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetMissileRange.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPos.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPlanetName.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowMoon.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowDebris.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowava.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowAlly.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowActions.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxySelector.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyMISelector.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyTitles.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyLegendPopup.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyFooter.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetMaxConstructibleElements.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetElementRessources.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ElementBuildListBox.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ElementBuildListQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/FleetBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/DefensesBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ResearchBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BatimentBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckLabSettingsInQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertBuildListScript.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/AddBuildingToQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/HandleTechnologieBuild.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildingSavePlanetRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildingSaveUserRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/RemoveBuildingFromQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CancelBuildingFromQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SetNextQueueElementOnTop.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowTopNavigationBar.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SetSelectedPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MessageForm.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/PlanetResourceUpdate.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildFlyingFleetTable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SendNewPassword.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/HandleElementBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/UpdatePlanetBatimentQueueList.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsOfficierAccessible.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckInputStrings.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MipCombatEngine.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/DeleteSelectedUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SortUserPlanets.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildFleetEventTable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildRessourcePage.'.$phpEx);
    
    ?>
    

    Co my tu mamy? Bezmyślne dołączanie 78 plików za każdym wywołaniem strony. Nawet gdy się logujemy, rejestrujemy, oglądamy podstronę kontakt itd. silnik ładuje 78 plików potrzebnych tylko w grze.

  11. Kolejnym plikiem ładowanym w common.php jest plik /includes/db.php. On też jest w pewien sposób wyjątkowy: jedyne co robi to include’uje kolejny (jeden) plik. Gdzie tu logika?

  12. Wymieniony wyżej plik includes/db.php dołącza plik db/mysql.php. Jego zawartość też jest warta przytoczenia:

    <?
    function doquery($query, $table, $fetch = false){
    global $numqueries,$link,$debug,$ugamela_root_path;
    require($ugamela_root_path.'config.php');if(!$link)
    {$link = mysql_connect($dbsettings["server"], $dbsettings["user"],
    $dbsettings["pass"]) or
    $debug->error(mysql_error()."
    $query","SQL Error");
    
    mysql_select_db($dbsettings["name"]) or $debug->error(mysql_error()."
    $query","SQL Error");
    mysql_query("SET NAMES latin2");
    echo mysql_error();}
    
    $sql = str_replace("{{table}}", $dbsettings["prefix"].$table, $query);
    $sqlquery = mysql_query($sql) or
    $debug->error(mysql_error()."
    $sql
    ","SQL Error");
    
    unset($dbsettings);
    $numqueries++;
    $arr = debug_backtrace();
    $file = end(explode('/',$arr[1]['file']));
    $line = $arr[1]['line'];
    $debug->add("
    Query $numqueries:$query$file($line)$table$fetch
    
    ");
    
    if($fetch)
    {$sqlrow = mysql_fetch_array($sqlquery);
    return $sqlrow;
    }else{return $sqlquery;}}
    ?>
    

    Pierwszą rzeczą jaka rzuciła mi się w oczy poza genialnym układem kodu był fakt, że przy każdym zapytaniu jest dołączany config.php. No ktoś tam nie ma mózgu… nie można po prostu dołączyć go w common.php? Tak samo ewentualne łączenie powinno się odbywać nie funkcji wykonującej zapytania tylko tam.

  13. W folderze includes znajduje się plik databaseinfos.php, który na pierwszy rzut oka jest używany do tworzenia tabel przez instalator, a krótka analiza wykazuje, że używany jest tylko tam. W takim wypadku, czy nie logiczniej byłoby go usunąć automatycznie po zakończeniu instalacji (tak jak zresztą cały skrypt). Ponadto prościej byłoby chyba, gdyby znajdował się po prostu w folderze install.

  14. Samo nazewnictwo plików jest lekko mówiąc dziwne. Czy to mówiąc o sposobie zapisu (add_moon.php, ale już activeplanet.php), czy o języku (errors.php, paneladmina.php, verband.php). Do wyboru, do koloru!

  15. Jeśli chodzi o bazę danych, to widać jak na dłoni, że była dorabiana po kawałku zależnie od konieczności. Jednak nikt nie zadał sobie trudu, aby spojrzeć na nią całościowo, przeprojektować i choćby pousuwać zbędne pola z tabel czy ustawić bardziej optymalne typy dla pól. Jako najlepszy przykład można podać fakt, że informacje o występujących w grze Księżycach są trzymane w dwóch tabelach, przy czym jedna zawiera po prostu część danych z pierwszej tabeli. Niestety, aby to poprawić trzeba dokonać dość mocnej przeróbki skryptu, a mało kto ma tyle zawzięcia.

Powyższy skrypt jest bardzo wymownym przykładem na to jak nie powinno się tworzyć jakichkolwiek aplikacji. Zero organizacji pracy i początkowych założeń daje właśnie takie efekty. Można tylko współczuć ludziom którzy z wielkim zapałem chcą zrobić "super wypasionom grem na xNovie" nie zaglądając nawet do "jej wnętrza".

[PHP] Sprawdzanie poprawności numeru PESEL

Dzisiaj przedstawię prostą funkcję do walidacji numeru PESEL w PHP. Przyjmowany argument i zwracana wartość nie powinny być niespodzianką - bierzemy numer PESEL, a dostajemy wartość logiczną (prawda/fałsz).

Aby sprawdzić poprawność numeru PESEL, należy obliczyć jego tak zwaną cyfrę kontrolną. Jest to ostatnia cyfra numeru PESEL, obliczana na podstawie jego wcześniejszych cyfr i sprawdzana na podstawie wzoru (zakładamy, że a - j to kolejne cyfry PESEL-u licząc od lewej):

a+3b+7c+9d+e+3f+7g+9h+i+3j+k

Teraz należy obliczyć resztę dzielenia powyższej sumy przez 10. Jeżeli ostatnia cyfra otrzymanego wyniku jest równa 0, to numer jest poprawny. W przeciwnym przypadku cyfra kontrolna nie zgadza się z resztą. Przykładowa implementacja w PHP, może wyglądać następująco:

function peselValidate($pesel)
{
    $sum = 0;
    $weights = array(1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1); // Wagi dla kolejnych cyfr numeru PESEL
    
    foreach (str_split($pesel) as $position => $digit) {
        $sum += $digit * $weights[$position];
    }

    return substr($sum % 10, -1, 1) == 0;
}

Należy jednak zwrócić uwagę na dwie rzeczy:

  • poprawność cyfry kontrolnej nie gwarantuje, że dany numer PESEL istnieje - tylko tyle, że jest zgodny wymogami co do numeru. Faktyczne potwierdzenie istnienia danego numeru jest niemożliwe bez dostępu do ewidencji.
  • w Polsce odnotowano przypadki wydania nieprawidłowych numerów PESEL

Aktualizacja (21.02.2015): aktualizacja kodu funkcji, reorganizacja treści

[PHP] Podstawy pisania czytelnego kodu

Piszę ten artykuł, ponieważ zauważyłem, że naprawdę wiele początkujących osób nie zwraca na to najmniejszej uwagi.

Zacznijmy od początku. Być może zastanawiasz się dlaczego trzeba dbać o czytelność kodu?

Podstawową zasadą jaką powinieneś przyjąć jest to, że z kodem będą pracować także inni ludzie, a nie tylko Ty. Może do tego dojść choćby przy udzielaniu pomocy na forum lub pisania projektu przez kilka osób. Wtedy czytelność kodu staje się naprawdę ważna, choć uwierz mi, że w momencie kiedy napiszesz naprawdę dużo kodu, bez zwracania uwagi na jego czytelność, to sam bez problemu się w nim pogubisz.

Komentarze
Po to bozia dała chyba praktycznie w każdym języku programowania możliwość komentowania kodu, żeby z niej korzystać. Jak pewnie wiesz, w PHP mamy 3 rodzaje komentarzy.

// komentarz jednolinijkowy

# inny komentarz jednolinijkowy (wywodzący się z Perla)

/* komentarz
wielolinijkowy
może się ciągnąć
w nieskończoność */

Pamiętaj o tym, aby wybrać jeden sposób tworzenia komentarzy jednolinijkowych. W przeważającej większości używa się komentarza //. Komentarze rozpoczynające się od znaku hash #) są już prawie niespotykane, choć część skryptów wykorzystuje je np. do oznaczania sekcji w plikach konfiguracyjnych.

Wcięcia
Ośmielę się napisać, że jest to najważniejsza kwestia jeśli chodzi o czytelność kodu. Porównaj te 2 przykładowe listingi.

if ($petla == true)
{
foreach ($zmienne as $zmienna) {echo strtolower($zmienna); }
} else {
echo 'nie ma pętli';
}

versus

if ($petla == true) { // w tym wypadku lepiej po prostu if ($petla)
    foreach ($zmienne as $zmienna) {
        echo strtolower($zmienna);
    }
}

else {
    echo 'nie ma pętli';
}

Z ułożenia kodu pokazanego na drugim listingu wynika dużo korzyści. Przede wszystkim na pierwszy rzut oka widać, że mamy do czynienia z dwoma warunkami, z czego w pierwszym z nich zawarta jest pętla foreach. Ważne jest to, aby „głębokość” wcięć przy każdym kolejnym poziomie zagnieżdżenia była jednakowa w całym skrypcie.

Nazewnictwo
Kolejna bardzo ważna kwestia. Tutaj liczy się zarówno sposób zapisywania nazw zmiennych, funkcji, klas, metod itd. jak i język w jakim je zapisujemy. Bardzo wielu początkujących zapisuje zmienne w języku polskim. Ponownie przyjrzymy się dwóm przykładom kodu.

$tablica = array(1, 3, 66);
$napis = ‘To prawda’;

if (count($tablica) == 3)
    echo $napis;
$array = array(1, 3, 66);
$text = ‘To prawda’;

if (count($array) == 3)
    echo $text;

Przyznacie chyba, że dużo logiczniej brzmi sekwencja if count array == 3, echo text niż if count talica == 3, echo napis. Skoro instrukcje oraz wbudowane funkcje w języku PHP są w języku angielskim, to nie ma najmniejszego sensu miksować tego z innym językiem i robić bałaganu.

Tutaj zrobię też krótką dygresję do osób, które w tym momencie pomyślały „ale ja nie znam angielskiego. Nie będę się go uczyć, tylko po to, żeby nazwać kilka zmiennych”. Otóż angielski w każdym języku programowania jest językiem podstawowym. Pomijając to, o czym już mówiłem, czyli to, że wszystkie wbudowane instrukcje i funkcje są nazwane po angielsku, niezaprzeczalnym faktem jest, że dużo więcej materiałów na temat programowania jest właśnie w języku angielskim (choćby większość dokumentacji PHP).

Drugą kwestią jest to jak zapisujemy nazwy zmiennych, klas, funkcji, metod itd. Oto niektóre ze sposobów, przedstawię je na przykładzie zmiennej  o nazwie simple var.

echo $simplevar; // totalnie nieczytelne
echo $simple_var ; // metoda pierwsza
echo $simpleVar; // metoda druga (tzw. camelCase)

Oczywiście, że poza tym mamy jeszcze wiele mniej lub bardziej stosowanych sposobów nazewnictwa – chociażby notację węgierską, jednak temu tematowi można by poświęcić spokojnie cały wpis, a ja chciałem jedynie pokazać przykładowe sposoby nazewnictwa.

Zaletami nazywania w stylu $simple_var, jest to, że wszędzie stosowane są tylko małe litery, a PHP rozróżnia je, jeśli chodzi o nazwy zmiennych.

Podsumowanie
Ten artykuł omawia zaledwie zalążek tematu, jednak być  może po jego przeczytaniu łatwiej będzie Wam tworzyć kod łatwiejszy w zarządzaniu. Pamiętajcie, że ważne jest bycie konsekwentnym w decyzjach (zresztą nie tylko w programowaniu…). Kiedy już zdecydujecie się np. na pisanie zmiennych po angielsku, metodą camelCase, to trzymajcie się tego przez cały skrypt, nie róbcie bałaganu i nie utrudniajcie roboty, tym, którzy będą czytać kod po Was.

Przegląd skryptów genialnych

Nie wiem czy ktokolwiek pamięta jeszcze pierwszy wpis z tej serii. Ja w każdym razie o nim zapomniałem. Jednak przy okazji dzisiejszego przeglądania jednego ze skryptów do tworzenia gry MMORPG via WWW przypomniałem sobie o tej serii i postanowiłem co nieco napisać.

Miałem zamiar przetestować skrypt MafiaWarz służący do tworzenia gier MMORPG w klimatach gangsterskich. Oczywiście nie spodziewałem się żadnych rewelacji po darmowym skrypcie tego typu, ale to co tam zobaczyłem dało kilka okazji do śmiechu dlatego postanowiłem się tym podzielić ze światem ;)

Zacznijmy rzecz jasna od początku, bo tak bywa najłatwiej. Instrukcji instalacji/pliku README nigdzie nie było, ale akurat zaimportowanie jednego pliku SQL i uzupełnienie danych w configu (choć dość trudnym do znalezienia) nie należało do bardzo trudnych.

Problem pojawił się podczas gdy chciałem zarejestrować konto. Na szczęście Wujek Google pozwolił szybko dojść do rozwiązania tego problemu - rozbiło się w zasadzie o jedną linijkę, więc na razie idzie dość gładko. Teraz zacznijmy ocenę właściwą:

  1. Błąd uniemożliwiający rejestrację na samym początku. Wielu ludzi może to odstraszyć, ale może i słusznie? :P
  2. Hasło miało zostać wysłane mailem, ale jako że skrypt testowałem na localhoście, to postanowiłem podmienić hasło w bazie danych na nowe. Długo szukałem charakterystycznego zahashowanego hasła w tabeli users, ale go nie znalazłem, bo hasła są trzymane w niezakodowanej postaci.
  3. Kontynuując temat bazy danych - brak prefiksów tabel, a ich nazwy zbyt oryginalne nie są, więc może być mały problem.
  4. Pokręcone typy pól: money jako int, online oraz lastcrime (trzymane jako znacznik czasu) jest polem typu varchar(100) zamiast po prostu int
  5. Jako że skrypt jest po angielsku, postanowiłem go przynajmniej częściowo spolszczyć. Niestety mimo tego, że użytkownicy mają możliwość zmiany języka w profilu to pliki językowe są przygotowane tylko dla dwóch z kilkudziesięciu zakładek. Dodatkowo oba te pliki zawierają dużo nigdzie niewystępujących tekstów, a nie mają tych najważniejszych.
  6. Totalny bajzel w plikach. Dużo zbędnych, po kilka kopii tego samego pliku, pliki do niczego niepotrzebne (przykładowo plik w00t.php zawierający jedynie pustą tabelę w HTML). Takie rzeczy zdecydowanie nie powinny leżeć w publikowanej wersji.
  7. Zero konwencji kodowania dla PHP
  8. Kod HTML na poziomie internetu łupanego. Można by go nazwać HTML-em 2.0 gdyby nie brak doctype. Nazwijmy go więc umownie HTML-em 0
  9. Design oparty na ramkach, znaczniki noframes oczywiście puste…
  10. Postanowiłem znaleźć panel administracyjny. Zajrzałem do jedynego pliku który wydał mi się właściwy (admincp.php), aby poszukać jakie pole w bazie muszę zmienić. Zabezpieczenie tego pliku naprawdę mnie oczarowało.
    if ($userlevel != 3){
        echo "Your not a admin.";
    }
    
    faktycznie - większość użytkowników na pewno przestraszy się tej informacji i czym prędzej ucieknie. Co jednak z pozostałymi niegodziwcami? Czy użycie die() byłoby zbyt trudne?
  11. Używanie " praktycznie wszędzie gdzie to niepotrzebne, np. dla zwyczajnych stringów lub nawet stosowanie echo "$style";. Nie szkoda serwera?
  12. Prawie w każdym pliku występuje podwójne łącznie z bazą. Najpierw dołączany jest plik includes/db_connect.php, a potem includes/functions.php na którego początku jest dołączany includes/db_connect.php
  13. Masa obrazków leżących w katalogu głównym oraz includes mimo istnienia katalogu images
  14. Źle zaprojektowana baza danych - 45 tabel dla tak w sumie nieskomplikowanego skryptu mówi samo za siebie
  15. Totalnie niespójny desing. Wygląda to tak, jakby każda zakładka była robiona przez inną osobę. Poza tym nie ma to jak biały tekst na jasnożółtym tle (no kto tutaj coś widzi?)
  16. Krótka analiza zakładek z menu - kilka to biała strona, jedna to "coming soon!", inna to 404. Zabrakło natomiast linków w menu do tak mało istotnych zakładek jak np. panel admina.
  17. Skrypt wymaga do działania włączonej opcji short_open_tags
  18. Podatność na SQL Injection
  19. CSS rozbite na masę poszczególnych plików
  20. W wielu miejscach widać pozostałości albo podwaliny systemu do aktywacji użytkowników - nie jest on jednak w żaden sposób wykorzystywany.

Skrypt ma na pewno dużo więcej błędów i niedoróbek, których nie wypisałem w tym wpisie. Powyższa lista to tylko jego ogólny przegląd i pewnego rodzaju przestroga dla osób które chciałyby stawiać gry online na takich gotowcach. Szkoda czasu i nerwów na poprawianie takich pseudoskryptów - mówię to także z własnego doświadczenia ;)

Sądzę że w niedługim czasie mogę się pokusić o opisanie większej ilości skryptów tego typu.

Przepraszam też za ewidentną chaotyczność tego wpisu, był on pisany jeszcze podczas "badania" skryptu dlatego nowe rzeczy były dopisywane na bieżąco.

Wstępna zapowiedź IronCMS-a 2.0

Od dość dawna nic nie pisałem na blogu. Przede wszystkim od bardzo dawna nie pisałem nic o IronCMS-ie. A mam w stosunku do niego pewne plany.

Ostatnią wydaną wersją był Iron z numerkiem 1.2.1. Od tamtego czasu nie raz miałem okazję patrzeć na jego kod i zmieniać go. Za każdym razem myślałem o tym samym – „czas na nową wersję z większymi zmianami. Trzeba to w końcu ogarnąć”.

A teraz bez zbędnego gadania przedstawię kilka rzeczy, które chciałbym zmienić w wersji 2.0 skryptu.

Czytaj dalej →

WordPress zaskoczyć umie

Przyznam się, że odkąd na innym blogu na którym pisuję (polecam swoją drogą ;)) dokonałem aktualizacji WP do wersji 3.2 (co zawsze robiłem, gdy tylko wychodziła polska wersja), byłem dość mocno rozczarowany. Przeszkadzał mi nowy wygląd panelu administracyjnego. Jeszcze bardziej zminimalizowany, jeszcze jaśniejszy i ogólnie jakiś taki dziwny (jak dla mnie, rzecz jasna).

Dlatego też wyjątkowo odkładałem aktualizację WP na sobak.pl. Odkładałem aż do dzisiaj

Czytaj dalej →

Backupowanie baz danych MySQL z poziomu PHP

Na samym początku zaznaczam, że pierwowzór skryptu przedstawionego poniżej nie jest mojego autorstwa! Skrypt znalazłem tutaj (skrypt na samym dole artykułu). Dokonałem w nim kilku przeróbek i w tym miejscu zostawiam dla potomnych - może się komuś przyda!

{{{ more }}}

Wprowadzone zmiany:

  • Umieszczenie daty wykonania backupu na górze zrzutu bazy
  • Lekkie ogarnięcie kodu PHP. Wiem, że wiele mu brakuje - jak komuś chce się bawić, to proszę bardzo - dla mnie aktualnie liczy się fakt, że skrypt działa
  • Zmiana formatu nazwy pliku (na taki który mi bardziej odpowiada :P). Jakby się komuś nie podobał, to zmiana jest przecież banalna.
  • Możliwość ustawienia katalogu w którym będą lądowały backupy
  • Kilka zmian czysto estetycznych
<?php
header('Content-type: text/html; charset=utf8');

try{
    // KONFIGURACJA - START
    $dbName = 'revival'; // Nazwa bazy danych do zbackupowania
    $dbHost = 'localhost'; // Nazwa serwera baz danych
    $dbUser = 'root'; // Nazwa użytkownika
    $dbPass = ''; // Hasło

    $backupsDir = 'backups'; // Katalog do którego będą zapisywane backupy. Bez końcowego ukośnika!
    // KONFIGURACJA - STOP
    // Dalej lepiej nie ruszać!

    // Stworzenie nowego obiektu klasy PDO
    $pdo = new PDO("mysql:host=$dbHost;dbname=$dbName", $dbUser, $dbPass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
    $pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $sqlResult = $pdo -> query("SHOW tables FROM $dbName");

    // Stworzenie nagłówka informacyjnego
    $sqlData = "-- Data wykonania kopii: ".date('d.m.Y')." r. o godzinie ".date('H:i')."
    -- Baza: $dbName
    SET SQL_MODE=\"NO_AUTO_VALUE_ON_ZERO\";";

    while ($queryTable = $sqlResult -> fetch(PDO::FETCH_ASSOC)){
        $sqlTable = $queryTable['Tables_in_'.$dbName];
        $sqlResultB = $pdo -> query("SHOW CREATE TABLE $sqlTable");
        $queryTableInfo = $sqlResultB -> fetch(PDO::FETCH_ASSOC);

        // Dodanie nagłówków dla konkretnych tabel
        $sqlData .= "\n\n--
        -- Struktura dla tabeli `$sqlTable`
        --\n\n";
        $sqlData .= $queryTableInfo['Create Table'] . ";\n";
        $sqlData .= "\n\n--
        -- Wartości tabeli `$sqlTable`
        --\n\n";

        $sqlResultC = $pdo -> query("SELECT * FROM $sqlTable");

        // Stworzenie INSERT-a dla każdego rekordu
        while ($queryRecord = $sqlResultC -> fetch(PDO::FETCH_ASSOC)) {
            $sqlData .= "INSERT INTO `$sqlTable` VALUES (";
            $sqlRecord = '';
            foreach( $queryRecord as $sqlField => $sqlValue ) {
                $sqlRecord .= "'$sqlValue',";
            }
            $sqlData .= substr($sqlRecord, 0, -1);
            $sqlData .= ");\n";
        }
    }

    // Zapisujemy wynik do pliku
    file_put_contents($backupsDir.'/backup_'.$dbName.'_'.date('d_m_Y').'.sql', $sqlData);
    echo 'Backup został zapisany.';
}

catch(PDOException $e){
    echo 'Połączenie nie mogło zostać utworzone: '.$e->getMessage();
}

Skrypt umieścić u siebie na FTP i uzupełnić sekcję konfiguracyjną. Wywoływać ręcznie (poprzez wpisanie URL-a) albo przez CRON-a.

Musisz koniecznie pamiętać o zabezpieczeniu katalogu z backupami (chociażby przez plik .htaccess). Dobrze by było też zabezpieczyć sam skrypt backupujący, aby nikt nie zajechał nam bazy danych przez wywoływanie w pętli.

Mimo tego, że skrypt testowałem, to nie biorę żadnej odpowiedzialności za jakiekolwiek szkody wynikłe z jego korzystania! Boisz się to nie używaj.

Mam nadzieję, że ten skrypt pomoże Wam w tworzeniu kopii swoich baz danych i sprawi, że będzie się to działo częściej niż raz na rok :P. Bo jak wiadomo:

"Ludzie dzielą się na tych, którzy robią backupy i tych, którzy zaczną je robić".

Pozdrawiam.

PS: Jakby ktoś zdecydował się rozbudować ten skrypt np. o wysyłanie backupów na e-mail czy ich uploadowanie na zewnętrzny serwer FTP, to niech go udostępni, może innym też się przyda.

Dwa nowe skrypty w Dev Center

Żeby moje nowe Dev Center (opublikowane wczoraj) nie świeciło pustkami toteż napisałem dwa nowe skrypty, które w dniu dzisiejszym dochodzą do jego skromnych zasobów.

Postanowiłem odpicować dwie z klas, których używanych przeze mnie w IronCMS-ie. Upiekłem przy tym dwie pieczenie na jednym ogniu. Po pierwsze: zyskałem nowe skrypty do kolekcji, a po drugie uzyskałem mocno poprawione klasy do użytku w kolejnych wersjach CMS-a (a te się powoli budują…).

Nie ma sensu się rozpisywać. Po prostu prezentuję:

Klasa do obsługi MySQL oraz powiązana z nią klasa loggera zdarzeń.

Wszelkie komentarze mile widziane ;)

PHP 5.4 alpha wydane - krok do przodu? [aktualizacja]

Wczoraj developerzy PHP wydali pierwszą wersję alpha z gałęzi 5.4. Wersja ta przynosi sporo zmian, w tym kilka które naprawdę warto wymienić:

  • usunięto register_globals - wprawdzie nikt normalny tego nie włącza, ale to będzie jedna luka bezpieczeństwa mniej o którą będziemy się martwić
  • usunięto safe_mode - hostingi przestaną nas tym męczyć
  • <?= jest teraz zawsze dostępne bez względu na ustawienie short_tags
  • Dodano domyślnie multibyte support. Wcześniej, aby go mieć należało skompilować PHP z opcją --enable-zend-multibyte. Teraz można to włączać lub wyłączać w php.ini
  • zdeprecjonowano funkcję mysql_list_dbs()
  • sporo innych zmian - głównie w rozszerzeniach

Na ostudzenie emocji można zadać dwa pytania. Po pierwsze: kiedy doczekamy się wersji finalnej? Drugie pytanie może jeszcze bardziej zdemotywować: Kiedy doczekamy się wprowadzenia PHP 5.4 na hostingach (nie mówiąc już o tych darmowych, które aktualizacje PHP często odciągają jak tylko się da).

Pełna lista zmian tutaj.

Aktualizacja (09.07.11)

Wszystko wskazuje na to, że w prowadzonym właśnie głosowaniu kolejnych zmian do wersji finalnej zostaną wywalone także magic quotes. Niedługo osiągnę PHP-ową Nirvanę :)

Aktualizacja (16.08.11)

W najnowszej wersji PHP5.4 - alpha 3 zostały usunięte wszystkie rzeczy związane z magic quotes.