Codice: weryfikacja właściciela zasobu (uprawnień użytkownika)

Z kilku przyczyn delikatnie rozjechał mi się harmonogram cotygodniowy, ale czas nadrobić straty. Poprzednio opisywałem doświadczenia zebrane podczas projektowania procesu instalacji dla Codice. Zajmiemy się teraz kolejnym zagadnieniem, które mimo iż oparte na przykładzie tego konkretnego projektu, jest jednym z podstawowych zagadnień dla każdej aplikacji CRUD. Zachęcam do tego aby ten, jak i kolejne wpisy traktować jako zwyczajne poradniki dla piszących w Laravelu, z nutką historii w tle.

Za każdym razem gdy piszemy aplikację, której instancja ma być używana równolegle przez więcej niż jedną osobę, pojawia się kwestia weryfikacji właściciela zasobu. Prościej mówiąc - sprawdzenia czy obecnie zalogowany użytkownik ma uprawnienia do wyświetlenia/edycji/usunięcia danego rekordu. Na pierwszy rzut oka sprawa nie należy do skomplikowanych:

$note = Note::where('id', $request->get('id'))->where('user_id', Auth::id())->get();

Na przykładzie notatek, pobieramy rekord o ID przekazanym w adresie i zaznaczamy że ID przypisanego użytkownika musi być równe identyfikatorowi obecnie zalogowanego (metoda pomocnicza id() z fasady Auth). Przyjmijmy też że naszym celem jest usunięcie wybranej notatki. W razie błędu przekierujemy zaś na inną stronę, na której wyświetlimy czytelny dla użytkownika komunikat. Bardziej kompletny kod oparty o metodzie przedstawionej powyżej mógłby wyglądać tak:

$note = Note::where('id', $request->get('id'))->where('user_id', Auth::id())->get();

if ($note === null) {
    return redirect('/')->with('error', 'Nie jesteś właścicielem podanej notatki');
}

$note->delete();

return redirect('/')->with('success', 'Notatka usunięta');

W wypadku gdy nie uda się odnaleźć pasującego rekordu otrzymujemy null. Wiemy wtedy że podana notatka nie istnieje lub istnieje, ale należy do kogoś innego (wymagamy spełnienia obu warunków), możemy więc cofnąć użytownika i wyświetlić mu odpowiednią wiadomość.

Na dobrą sprawę wpis można by zakończyć już w tym miejscu. Warto jednak spojrzeć bardziej perspektywicznie. Podany fragment kodu będzie się przecież powtarzał także dla innych operacji CRUD (np. edycji lub odczytu), nasza aplikacja może też zarządzać więcej niż jednym rodzajem danych (na przykładzie Codice - notatkami oraz etykietami). W efekcie otrzymamy brzydką i niewygodną w utrzymaniu powtarzalność kodu. Zastanówmy się jak można to poprawić.

Rozsądne wydaje się skorzystanie z metody find(), która przyjmuje za konwencję, że polem, po którym będziemy indeksować tabelę jest id. Możemy połączyć to także z możliwością opisaną w dokumentacji Laravela jako query scopes. Do naszego modelu dodajemy:

public function scopeMine($query)
{
    return $query->where('user_id', '=', Auth::id());
}

a wynikowo naszą metodę kontrolera redukujemy do postaci

$note = Note::mine()->find($request->get('id'));

if ($note === null) {
    return redirect('/')->with('error', 'Nie jesteś właścicielem podanej notatki');
}

$note->delete();

return redirect('/')->with('success', 'Notatka usunięta');

Jest lepiej. Powtarzalna część zapytania została przeniesiona do modelu (oraz konwencji używanej przez ORM Laravela). Wciąż jednak widzimy niezmienną część logiki, którą także przydałoby się wypchnąć na zewnątrz, w jakieś wspólne miejsce.

W tym celu możemy wykorzystać wyjątki, bazując zresztą na logice używanej przez wbudowaną metodę findOrFail(). Do naszego modelu dodajemy kolejną metodę pomocniczą:

public static function findMine($id)
{
    $note = self::mine()->find($id);

    if (!$note) {
        throw new NoteNotFoundException;
    }

    return $note;
}

W obrębie aplikacji tworzymy wyjątek NoteNotFoundException i importujemy go w modelu.

<?php

namespace AppExceptions;

class NoteNotFoundException extends Exception {}

Logikę obsługi błędów przenosimy natomiast do handlera wyjątków, edytując metodę render() w klasie AppExceptionsHandler.

public function render($request, Exception $e)
{
    if ($e instanceof NoteNotFoundException) {
        return Redirect::route('index')->with('message', 'Nie jesteś właścicielem podanej notatki`);
    }

    // reszta domyślnego kodu, np. ModelNotFoundException
}

Teraz kod użyty w kontrolerze powinien być naprawdę krótki i satysfakcjonujący:

$note = Note::findMine($request->get('id'));

$note->delete();

return redirect('/')->with('success', 'Notatka usunięta');

Jest dobrze? Tak. Może być lepiej? Oczywiście! Wady rozwiązania ujawnią się przy jakiejkolwiek większej skali. Pozostając przy Codice - ten sam kod musiałby być zduplikowany dla notatek i etykiet - dwa różne wyjątki, dwa bloki obsługujące go w handlerze, analogiczne modyfikacje w obu modelach.

Skupimy się więc na wyłączeniu części wspólnych. Wyeliminujemy potrzebę tworzenia indywidualnych wyjątków dla każdego rodzaju danych, a metody wymagane dla każdego z modeli przeniesiemy do traita.

<?php

namespace CodiceSupportTraits;

use Auth;
use IlluminateHttpExceptionsHttpResponseException;

trait Owned
{
    /**
     * Find owned model (limited to the current user).
     *
     * @param  int $id Model ID
     * @return static
     */
    public static function findMine($id)
    {
        $model = self::mine()->find($id);

        if (!$model) {
            $modelLangFile = array_reverse(explode('\', get_called_class()))[0];

            throw new HttpResponseException(redirect()->route('index')->with([
                'message' => trans("$modelLangFile.not-found"),
                'message_type' => 'danger',
            ]));
        }

        return $model;
    }

    /**
     * Query scope for getting owned models (limited to the current user).
     *
     * @param $query IlluminateDatabaseQueryBuilder
     * @return IlluminateDatabaseQueryBuilder
     */
    public function scopeMine($query)
    {
        return $query->where('user_id', '=', Auth::id());
    }

    /**
     * Define relationship to the User owning the model.
     */
    public function user()
    {
        return $this->belongsTo('CodiceUser');
    }
}

W tym wypadku obsługa po stronie kontrolera wygląda dokładnie tak, jak pokazano wyżej, jednak cała implementacja logiki dla określonego modelu ogranicza się do use Owned, bez żadnego powtarzania schematycznego kodu. Dodatkowym klasom wyjątków również możemy już podziękować, korzystamy z wbudowanego HttpResponseException, więc AppExceptionsHandler może zostać przywrócone do wcześniejszej postaci. Dodatkowo mamy też własny scope mine() (przydatny przy innych rodzajach zapytań niż wybranie pojedynczego rekordu po ID) oraz zdefiniowaną relację do modelu użytkownika.

Ciekawostką jest hack w metodzie findMine(), który pobiera nazwę modelu wywołującego metodę i na tej podstawie buduje klucz do pliku z tłumaczeniami (w celu zróżnicowania komunikatów błędów, np. "nie znaleziono notatki" vs "nie znaleziono etykiety").

Przedstawiłem kilka rożnych metod, skracając nieco drogę, przez którą ten kawałek kodu przeszedł w moim własnym projekcie. Zainteresowanych sprawdzeniem faktycznego kodu mogę odesłać do historii zmian w kontrolerze notatek oraz oczywiście traita Owned


Powyższy wpis przedstawia część doświadczeń zdobytych przy tworzeniu projektu Codice. Po więcej szczegółów zapraszam do pierwszego wpisu oraz na codice.eu. Poza planowaną od dawna serią jest to też realizacja wymogów konkursu "Daj Się Poznać". Więcej o nim możecie przeczytać w tym wpisie. Po nowe materiały poświęcone tworzeniu projektu Codice zapraszam w każdy wtorek. Ponadto więcej wpisów, poświęconych temu projektowi lub ogólnej tematyce bloga przeczytacie co sobotę.

Laravel i podpowiedzi IDE? To możliwe

Nie ukrywajmy - magii w Laravelu jest dużo. Na tyle dużo, że można ją kochać bądź nienawidzić - nie wyobrażam sobie stanu pośredniego. Abstrahując zupełnie od tego, czy takie rozwiązanie jest słuszne, trzeba mu wytknąć jedną, definitywną wadę. Zintegrowane środowiska programistyczne (IDE) w kontakcie z nią po prostu wariują.

Nie ma problemu dopóki korzystamy z prostych edytorów tekstowych typu Sublime Text, Atom czy Visual Studio Code. Więszkość z nich albo w ogóle nie dostarcza podpowiedzi dla kodu PHP lub są one mało "inteligentne". Bazują na prostym słowniku użytych symboli (nazw funkcji, klas czy zmiennych) i wyszukiwarce. Tutaj rysuje się jedna z głównych różnić pomiędzy edytorem, a IDE - środowisko programistyczne stara się "zrozumieć kod". Za pomocą różnych zaawansowanych parserów stara się przewidzieć zachowanie faktycznego interpretera PHP i jako podpowiedzi podaje nam tylko to, czego możemy faktycznie użyć w danym kontekście, bez spowodowania błędu. Nie dostaniemy w podpowiedzi zmiennej, która jest niedostępna w danym zasięgu, ponieważ PHP jako interpreter też nie byłby jej w stanie użyć.

Mimo całej swojej "inteligencji" IDE nie odwzorowują jednak w stu procentach zachowania języka - złożoność takiego programu byłaby naprawdę ogromna, a prędkość działania niezadowalająca. Dlatego występują sytuacje, w których dany kod w rzeczywistości działa, a IDE po prostu się "gubi". Nie przetwarza wszystkich dynamicznych sytuacji obecnych na przykład przy użyciu wspomnianej magii języka.

Z opisaną sytuacją spotkał się na pewno każdy, kto próbował używać projektu napisanego w Laravelu w którymkolwiek z popularnych środowisk programistycznych dla PHP, na przykład phpStormie. Zaznaczę że mówimy tu głównie o wykorzystaniu fasad (w rozumieniu Laravela), których można unikać - promuje je jednak sama dokumentacja. W praktyce program pokazuje tak wiele błędów, że żeby nie rozpraszać się nimi trzeba by wyłączyć praktycznie wszystkie podpowiedzi (lub tzw. inspekcje). Przyznacie jednak, że pozbawienie IDE całej jego "inteligencji" i tym samym zrobienie z niego zwyczajnego edytora trochę mija się z celem.

Czy jest na to jakieś rozwiązanie? Oczywiście.

W przypadku Laravela możemy użyć bardzo popularnej paczki laravel-ide-helper. Analizuje ona kod frameworka i generuje plik będący swego rodzaju "mapą" pomiędzy fasadami, prostymi interfejsami używanymi przez programistę, a normalnymi klasami, których wewnętrznie używa framework, komunikując te dwie warstwy za pomocą sporej ilości skomplikowanych odwołań i opisywanej magii.

Tak wygenerowany plik, obecny w katalogu projektu wystarcza aby więszkość środowisk potrafiła nadążyć za sztuczkami zastosowanymi w Laravelu. Nie tylko przestała pokazywać błędy, ale faktycznie zaczęła pomagać, na przykład podpowiadając dostępne metody i ich parametry.

Zaczynamy od zainstalowania paczki przy użyciu Composera. Myślę że dowolnej osobie używającej Laravela czy innego nowoczesnego frameworka PHP nie trzeba tłumaczyć obsługi tego narzędzia.

composer require --dev barryvdh/laravel-ide-helper

Zwróćcie uwagę że instalujemy paczkę jako zależność deweloperską. Skutkuje to tym, że w środowisku produkcyjnym w ogóle nie będzie ona pobierana. Teraz przechodzimy do kolejnego kroku, czyli zarejestrowania Service Providera, który komunikuje paczkę z frameworkiem. Tutaj też odbiegniemy nieco od standardowej metody, czyli dodania wpisu w config/app.php i rejestracji Service Providera dokonamy warunkowo. Dlaczego? Ponieważ w przeciwnym razie, w środowisku produkcyjnym (a więc pominięciu instalacji tej paczki) framework próbowałby użyć nieistniejącej klasy. Dlatego też otworzymy plik app/providers/AppServiceProvider.php i w metodzie register() dodamy następujący kod:

public function register()
{
    if ($this->app->environment() !== 'production') {
        $this->app->register(BarryvdhLaravelIdeHelperIdeHelperServiceProvider::class);
    }
    // ...
}

Dzięki temu framework nie będzie próbował rejestrować Service Providera na produkcji.

Następnie możemy już skorzystać z udostępnionych nam komend.

php artisan clear-compiled
php artisan ide-helper:generate
php artisan optimize

Paczka udostępnia nieco więcej opcji, włącznie z generowaniem dokumentacji dla naszych modeli, jednak nie są one generowane domyślnie. Po szczegóły raz jeszcze odsyłam do oficjalnego repozytorium

W głównym katalogu projektu powstanie plik _ide_helper.php. Nie musimy go nigdzie dołączać, w tym momencie wszystko powinno już działać. Ów plik zdecydowanie dodajmy do ignorowanych przez Gita, o ile z niego korzystamy (a przecież wszyscy to robimy, prawda?). W najprostszym wariancie dodajemy linię /_ide_helper.php do .gitignore. O tym, dlaczego niekoniecznie jest to najlepszy pomysł, postaram się napisać już niedługo ;)


Powyższy wpis przedstawia część doświadczeń zdobytych przy tworzeniu projektu Codice. Po więcej szczegółów zapraszam do pierwszego wpisu oraz na codice.eu. Poza planowaną od dawna serią jest to też realizacja wymogów konkursu "Daj Się Poznać". Więcej o nim możecie przeczytać w tym wpisie. Po nowe materiały poświęcone tworzeniu projektu Codice zapraszam w każdy wtorek. Ponadto więcej wpisów, poświęconych temu projektowi lub ogólnej tematyce bloga przeczytacie co sobotę.

Codice: instalator webowy dla aplikacji opartej o Laravel

Dośc długo zastanawiałem się czemu mógłbym poświęcić swój pierwszy, faktyczny wpis poświęcony rozwojowi Codice i problemom jakie musiałem rozwiązać. Daruję sobie rozpoczynanie od struktury katalogów, tworzenia kontrolerów i podobnych zadań — zaczniemy od początku, ale z perspektywy użytkownika.

Dość dużo mówi się o tym, jak zmienił się rozwój aplikacji webowych w ostatnich latach. Kiedyś (czy to patrząc na rok, czy na doświadczenie konkretnego programisty) prawdpodobnie wszyscy zaczynaliśmy, w wypadku PHP, od kilku plików na krzyż, w ekstremalnych przypadkach wrzucając jakąś rozpakowaną bibliotekę do osobnego folderu. Dzisiaj rozwój projektów, zwłaszcza na początku, przypomina budowę z klocków Lego. Zaczynamy od zainstalowania całego środowiska programistycznego — serwera (lub programu do obsługi środowisk zwirtualizowanych), Composera do obsługi zależności PHP, menedżera zależności dla JavaScript popularnego akurat w danym tygodniu (pun intended ;) ) i dopiero ostatecznie przystępujemy do pisania faktycznego kodu.

Ze stawianiem już gotowej aplikacji jest całkiem podobnie. Pobieramy kod, a następnie zdefiniowane w projekcie zależności. Potem jest już z górki — kompilujemy assety dla frontendu (w końcu to tylko kwestia odpalenia task runnera), wypełniamy plik konfiguracyjny (lub ustawiamy zmienne środowiskowe), uruchamiamy migracje dla bazy danych, jeśli nam zależy to wrzucamy do cache co się da — na koniec tworzymy konto użytkownika i voilà. Proste, prawda?

A no właśnie nie bardzo. Wszystkie z opisanych rozwiązań to ogromny krok na przód i ułatwienie dla programistów. Co więcej, przekładające się bezpośrednio na jakość ostatecznego produktu. Nie przerzucajmy jednak całego procesu ustawiania środowiska na użytkownika. Mamy dostępnych tyle świetnych technik i narzędzi, a tymczasem proces instalacji przeciętnego skryptu PHP się uwstecznił. W wypadku Codice postanowiłem połączyć zdobycze naszych czasów z prostotą instalacji dawnych skryptów.

Czynności do wykonania możemy podzielić na trzy grupy:

  • wykorzystanie zewnętrznych narzędzi — node.js wraz z Yarnem zarządzającym zależnościami dla tej technologii i gulpem odpowiedzialnym za przygotowanie zasobów dla client-side, ale także Composer dla zależności PHP
  • wypełnienie plików konfiguracyjnych
  • uruchomienie procesów oskryptowanych w PHP — na przykład migracji dla struktury bazy danych

Pierwszy krok zrealizowałem dzięki udostępnieniu do pobrania wstępnie zbudowanej paczki plików. W archiwum znajdują się produkcyjne zależności PHP, a także skompilowane assety (nie ma za to node_modules i innych plików wykorzystywanych wyłącznie przy developmencie — szanujmy łącza i czas użytkowników).

Resztą zajmuje się już instalator, będący de facto zwyczajnym kontrolerem Laravela. Aplikację zainstalowaną od tej jeszcze niezainstalowanej postanowiłem rozróżniać sprawdzajac obecność pliku .env. Ponieważ jednak jest on tworzony na pewnym etapie instalacji, potrzebny był dodatkowy sposób oznaczenia obecnie trwającej instalacji (tak, aby po kroku wypełniającym .env nie wyrzuciło nas z instalatora) — jest to po prostu plik tymczasowy tworzony po wejściu do instalatora i usuwany na jej ostatnim kroku. Cały proces wygląda następująco:

public function __construct()
{
    if (file_exists(base_path('.env')) && !file_exists(storage_path('app/.install-pending'))) {
        $this->denyInstallation = true;
        return Redirect::route('index')->send();
    }

    // ...
}

Kod znajduje się w konstruktorze więc automatycznie jest wykonywany dla każdego z kroków (akcji) instalatora. Warto zwrócić uwagę na użycie metody send(), jest ona wymagana w wypadku próby obsługi żądań HTTP z poziomu konstruktora.

Kolejnym etapem jest sprawdzenie wymagań, tj. obecności rozszerzeń PHP i zapisywalności określonych katalogów. Nie ma tu żadnej filozofii także przejdę dalej, do wypełniania pliku .env. Z pozoru proste zadanie, ot umieszczenie określonego stringa w pliku tekstowym. Jest jednak pewna pułapka, która swego czasu spędziła mi sen z powiek na ładnych parę godzin...

Problem pojawia się gdy w instalatorze wykorzystujemy jakiekolwiek sesje bądź ciasteczka (w moim wypadku było to ustawienie języka instalacji). Zarówno sesje jak i ciasteczka obsługiwane przez Laravela są szyfrowane przy użyciu klucza, tzw. APP_KEY. Jest on indywidualny dla każdej aplikacji, a więc przechowywany właśnie w pliku .env. Ten jednak nie istnieje na samym początku instalacji. Co wtedy robi Laravel? Spójrzmy na plik config/app.php:

'key' => env('APP_KEY', 'SomeRandomStringSomeRandomString'),

W wypadku gdy zmienna nie istnieje, klucz jest ustawiany na wartość domyślną, podaną w drugim parametrze funkcji. To jest właśnie klucz szyfrujący ciastka i sesje na poczatku instalacji... aż do momentu gdy, wypełniając .env, wstawimy do niego nowy klucz, który własnie wygenerowaliśmy. Skutek można sobie łatwo wyobrazić - dane w nich zapisane stają się niemożliwe do odczytania. Mając tę wiedzę spokojnie możecie wymyślić kilka różnych obejść problemu — ja zdecydowałem się doczepić aktualnie wybrany język do adresu, na który przekierowuję po wypełnieniu pliku konfiguracyjnego. W kolejnym kroku mogę go z tego adresu odczytać i ponownie zapisać do sesji.

W ten sposób doszliśmy do kolejnej kwestii, która na pierwszy rzut oka może wydawać się problematyczna — uruchomienie migracji z poziomu skryptu PHP. Domyślnie jest to bowiem komenda artisana, a dokumentacja zdaje się nie wspominać o alternatywnych drogach. Również wymóg użycia exec() dla zwykłej instalacji wydaje się przesadny, wszkaże wiele hostingów nadal blokuje tę i podobne funkcje. Tym razem jednak rozwiązanie jest wyjątkowo proste, a z pomocą przychodzi nam Artisan::call(), który wykonuje kod danej komendy bez jakiegokolwiek używania warstwy linii komend.

Artisan::call('migrate', ['--force' => true]);

Pamiętajmy o użyciu opcji --force, jest ona wymagana aby migracje mogły być uruchomione w środowisku produkcyjnym.

W wypadku Codice to już praktycznie koniec, pozostało tylko utworzenie konta dla użytkownika i dodanie mu notatki powitalnej. W swoich aplikacjach możecie oczywiście wykonać wszystkie inne czynności związane z instalacją, tak aby jak najbardziej ułatwić życie Waszych użytkowników końcowych. Naprawdę nie ma potrzeby wymagać od nich znajomości typowo programistycznych narzędzi ;) Ponadto, stosując przedstawione tutaj rozwiązanie nie blokujemy żadnej z dotychczasowych dróg instalacji. Nic nie stoi na przeszkodzie aby pobrać źródła (lub sklonować repozytorium) i wszystkie czynności wykonać ręcznie.

Na koniec, wszystkim zainteresowanym dokładnym działaniem instalatora polecam zacząć przeglądanie kodu od tego kontrolera. Aby zamieszać jeszcze troszkę, dodam że Codice obsługuje też instalację z poziomu CLI, dla osób które chciałyby postawić całą instalację bez opuszczania terminala bądź szukają wygodnej drogi na zautomatyzowanie procesu. Odpowiedzialny za to kod znajdziecie w tym miejscu.


Powyższy wpis przedstawia część doświadczeń zdobytych przy tworzeniu projektu Codice. Po więcej szczegółów zapraszam do pierwszego wpisu oraz na codice.eu. Poza planowaną od dawna serią jest to też realizacja wymogów konkursu "Daj Się Poznać". Więcej o nim możecie przeczytać w tym wpisie. Po nowe materiały poświęcone tworzeniu projektu Codice zapraszam w każdy wtorek. Ponadto więcej wpisów, poświęconych temu projektowi lub ogólnej tematyce bloga przeczytacie co sobotę.

Codice: zarządzanie zadaniami i notatnik online

W życiu każdego projektu (i jego twórcy) przychodzi taki moment, kiedy należy wyjść z wygodnego stanu wiecznej wersji beta i niezobowiązującego dłubania w kodzie przy okazji wolnych wieczorów. Wyjść z kodem do ludzi, liczyć się z tym, że ktoś może go rzeczywiście użyć (kto by pomyślał?), a nawet skrytykować. Uznałem, że ten etap nadszedł zarówno dla mnie, jak i mojego dziecka ochrzczonego mianem Codice.

Mówi się że w programowaniu istnieją jedynie dwie trudne rzeczy. Unieważnianie cache i nazewnictwo. Nazwa dla projektu (zarówno w tej wersji i jego wcześniejszych iteracji) powstała już lata temu i z włoskiego oznacza kod lub kodeks. Nawiązanie do kodu jest tak naprawdę miłym zbiegiem okoliczności, który powoduje że wydaje się znacznie bardziej przemyślana niż była w rzeczywistości. Kodeks zaś? Pamięć podpowiada mi że miałem na myśli zbiory notatek Leonarda da Vinci, które nazwano właśnie kodeksami. Czytałem o nich jako dziecko, zafascynowany różnorodnością i ilością zapisków sporządzanych przez signor Leonardo, i jak to dziecko - pomyślałem że też bym tak chciał!. Szczęśliwie, myśl ta przetrwała przez kilka lat i stwierdziłem że mógłbym ją wcielić w życie korzystając z technologii, których się uczyłem.

Długim słowem wstępu przechodzimy do przedstawienia projektu notatnika i aplikacji do zarządzania zadaniami online. Potrzeba gromadzenia i wykorzystywania przeróżnych informacji jest chyba wspólna dla każdego z nas. Pomieszczenie, a zwłaszcza późniejsze odnalezienie istotnych danych z całego szumu którym jesteśmy bombardowani jest niełatwe. Dlaczego więc i w tym wypadku nie wspomóc
się technologią?

Pierwsza wersja Codice (wtedy jeszcze pod inną nazwą) powstała w 2011 roku. Była tak naprawdę jednym olbrzymim switchem, którego eksperymentalnie umieściłem w jednym pliku - bo dlaczego nie? Niespełna 300 linii PHP plus jeden arkusz stylów zapewniał mi miejsce do dodawania notatek, którym opcjonalnie mogłem przypisać termin wykonania, a później oznaczyć taką notatkę (czy już wówczas zadanie) jako wykonaną. Zainteresowanych archeologią zapraszam (ostrzegając jednocześnie) do zobaczenia kodu na własne oczy.

Co zmieniło się przez te 6 lat? Przepisywałem projekt kilka razy, nieraz odkładając go do szuflady na długie miesiące. Na pewno jednak nie zmieniło się założenie, obecna wersja działa w naprawdę zbliżony sposób.

W pewnym momencie zdałem sobie sprawę że w tym właśnie leży siła. Banalnie prosta idea, do której można dobudować bardzo wiele - do tego użyteczna. Zrozumiałem że jest to coś, czemu jestem gotów poświęcić sporo pracy, a przy okazji podzielić się efektem ze światem.

Dzisiaj Codice jest aplikacją MVC, zbudowaną w oparciu o framework Laravel, która, pod względem napisanego kodu, powiększyła się niemal... pięćdziesięciokrotnie. Tyle mniej więcej wypuściłem z pod palców, w wolnych chwilach od grudnia 2015, kiedy raz jeszcze postanowiłem wymienić fundamenty i większość kodu.

Codice, o wciąż niezmiennej koncepcji, doczekał się nowego designu, wielu dodatkowych możliwości, nieco lepszej architektury a także protego systemu wtyczek, który powinien zapewnić mu pewną elastyczność na najbliższe lata i moje plany z nim związane.

Na obecnym etapie można chyba zaryzykować nazwanie go produktem używalnym, czego powinienem być żywym dowodem, korzystając z niego na co dzień od około pół roku. W dalszym ciągu udziela mi się oczywiście syndrom malarza i widzę w nim nieskończenie wiele rzeczy, które można by zrobić lepiej. No ale cóż, w myśl tego, co napisałem we wstępie czas zmierzyć się z odbiorem na zewnątrz i wykorzystać go jako niezaprzeczalną szansę na zebranie kilku opinii, przed osiągnięciem wersji stabilnej.

Swoje postępy na tej drodze będę opisywał od tej chwili na blogu. Według obecnego planu wpisy powinny pojawiać się w każdy wtorek, co najmniej do połowy maja.

Jeśli zaintrygowałem kogoś już w tym momencie, zapraszam na stronę projektu. Można poczytać sobie dokumentację jeśli ktoś ma problemy z zasypianiem lub po prostu pobrać paczkę i spróbować coś zepsuć ;)


Powyższy wpis poza planowanym już dawno przedstawieniem Codice i wstępem do serii, w której postaram się przybliżyć jego możliwości, pokazując przy tym jak rozwiązałem co ciekawsze problemy, jest też realizacją wymogów konkursu "Daj Się Poznać". Więcej o nim możecie przeczytać w moim wczorajszym wpisie. Oznacza to też, że co najmniej do połowy maja co sobotę będą pojawiać się kolejne materiały, tym razem poświęcone niekoniecznie rozwojowi Codice, ale również związane z tematyką bloga.

„Daj Się Poznać”? Czemu nie?

W ostatnich dniach dwójka znajomych, Comandeer i Soanvig, namówiła mnie do czegoś, od czego uparcie stroniłem przez ostatnie lata. Być może nie brzmi to zbyt przełomowo, ale postanowiłem wziąć udział w konkursie programistycznym.

"Daj Się Poznać" to konkurs organizowany przez Macieja Aniserowicza z blogu devstyle.pl. Popularny, a przy tym bardzo prosty w założeniach - przez 10 tygodni rozwijamy projekt i piszemy o nim na blogu. Na tyle proste, konkretne i sensowne, że przekonało mnie do małego otwarcia się na świat. Dlaczego?

Uwaga. Ostrzegam uczciwie, że co bardziej wyczuleni mogą zwęszyć znamiona wpisu sponsorowanego. Nieszczególnie mi to przeszkadza, bo przeświadczonych o tym fakcie i tak już pewnie nie przekonam. Mogę tylko poinformować że z typowego wpisu sponsorowanego brakuje elementu sponsorstwa. Jest natomiast sporo zadowolenia, z tego jak organizator podszedł do pewnych kwestii.

Po pierwsze, duża dowolność. Brak narzuconej tematyki oraz technologii. Co jeszcze fajniejsze, w konkursie mogą brać udział projekty rozwijane już wcześniej, nie muszą powstawać od początku na jego potrzeby. Nie mamy tu więc do czynienia z żadnym hackathonem, gdzie głównym elementem jest wyścig z czasem, a zwyczajnym konkursem na projekty programistyczne. Widać skupienie się na samym programowaniu (i pisaniu o tym), co mi osobiście bardzo odpowiada.

Kolejna rzecz, nie mam pewności czy zamierzona, ale bardzo udana w efektach. Ze względu na swoje założenia, konkurs jest atrakcyjny także dla tych, którzy nie osiągną w nim wysokich miejsc. Ja sam podchodzę do tego bardziej jak do zewnętrznego motywatora z nutką dobrej zabawy. Może nie wypada pisać takich rzeczy na samym początku, ale prawdę mówiąc nie liczę na zdobycie którejś z nagród. Widząc na
liście nazwiska moich starszych (przynajmniej doświadczeniem) kolegów, czy osób takich jak Piotr Nalepa (którego przerobiony skrypt dumnie nazywałem lata temu swoim pierwszym "projektem w PHP") nie łudzę się odniesieniem sukcesu. I szczerze? Absolutnie mi to nie przeszkadza! Rzucenie wyzwania swojej prokrastynacji jest dla mnie wystarczająco atrakcyjnym celem.

Trzecia rzecz, na którą zwróciłem uwagę to regulamin. Moją uwagę opis konkursu przykuł bardzo szybko, do ostatniej chwili odkładałem jednak zajrzenie na podstronę z zasadami. Byłem mentalnie przygotowany na zderzenie z długą listą reguł, wymogów i analizowanie czy na pewno spełnię wszystkie z nich i czy gdzieś nie czyhają kruczki, którymi wolałbym się nie wiązać. Nie ma co się rozwodzić, zainteresowanych odsyłam tutaj. Krótko i na temat!

Tyle słowem wstępu. Przedstawienie projektu, którym będę zajmował się do 31 maja, już jutro.

Jak powinien wyglądać dobry commit?

Dzisiejszy wpis będzie skierowany do osób zaczynających swoją przygodę z systemami kontroli wersji (VCS). Mimo że porady będą bazowane na systemie git, większość z nich można z powodzeniem zastosować także dla jego alternatyw.

Rozmiar commitu

Jest to rzecz, z którą - według moich obserwacji - początkujący mają największy problem. Nie istnieje bowiem idealna ilość zmian w jednym commicie, którą możemy się kierować za każdym razem. Myślę, że commit o dobrym rozmiarze najprościej jest zdefiniować słowem samodzielny. Co takiego mam przez to na myśli?

  • zmiany w commicie powinny stanowić działąjącą całość, nie powinno się commitować kodu, który generuje na przykład błąd parsera
  • commit powinien obejmować jedną funkcjonalność. Ponownie, nie da rady zdefiniować jak dużo obejmuje jedna "funkcjonalność". Spróbuj jednak postąpić w ten sposób - wyobraź sobie, że commit, który masz zaraz wyślesz, będzie musiał zostać cofnięty (np. komendą git revert). Czy robiąc to, pozbędziesz się tylko jednego kroku, czy też będziesz musiał w osobnym commicie powtórzyć część z dokonanych wcześniej zmian (które właśnie hipotetycznie cofnąłeś). Jeżeli zachodzi druga sytuacja, to commit jest za duży.

Opis commitu

Bądźmy szczerzy, każdy lubi czasem popuścić wodze fantacji wpisując wiadomość commitu. Najczęściej jest to danie upustu frustracji, która towarzyszyła tworzeniu danego kodu. Powstały nawet specjalne strony jak whatthecommit.com. Warto jednak poświęcić kilka sekund więcej na napisanie wiadomości commitu, aby w przyszłości zaoszczędzić swój czas.

Opis commitu jest wykorzystywany w bardzo wielu sytuacjach - komendach git log, git rebase, git blame, czy ich odpowiednikach w serwisach typu GitHub. Traktuj opis commitu tak, jakbyś musiał go przeczytać i szybko zrozumieć kilka dni później. Staraj się być zwięzłym i rzeczowym - jeżeli nie ze względu na siebie, to na współpracowników.

Kolejną sprawą jest możliwość podzielenia commitu na krótki opis główny i dłuższą (opcjonalną!), bardziej szczegółową część, która nie jest pokazywana w niektórych sytuacjach. Wspiera to oryginalny klient gita (np. komenda git log --abbrev-commit) i np. GitHub (w widoku listy commitów pokazywana jest tylko krótka wiadomość, a reszta rozwija się po kliknięciu). Git oddziela streszczony opis commitu od jego pełnej treści za pomocą jednej pustej linii.

Oto skrótowy, kontretny opis Twojego commitu

Tutaj możesz zawrzeć długi opis, w którym przykładowo omówisz przyczyny
wprowadzenia danej zmiany i jej konsekwencje dla projektu. Może być to
przydatne szczególnie dla Twoich współpracowników.

Na koniec kilka dodatkowych (częściowo subiektywnych, lecz popartych obserwacjami) zaleceń dotyczących formy samej wiadomości:

  • żadna z linii w opisie commita nie powinna przekraczać 72 znaków (konsola ma domyślnie 80 znaków szerokości)
  • rozpocznij opis od wielkiej litery
  • na końcu pierwszej linii commitu nie stawiaj kropki
  • użyj trybu rozkazującego (zwłaszcza w języku angielskim; powód - git używa takiego samego trybu dla wiadomości generowanych przez siebie, na przykład "Merge a from remote b")

Weryfikacja commitu

Przed zatwierdzeniem dowolnego commitu, przejrzyj go - najlepiej dwa razy. Służy do tego oczywiście diff, czyli wykaz wszystkich zmian poczynionych w ramach danego commmitu. Generalnie do jego wyświetlenia służy komenda git diff (trudne, prawda?), ale oczywiście jest haczyk. Domyślnie nie pokazuje ona zawartości plików właśnie dodanych do repozytorium, a jedynie zmiany w przestrzeni roboczej (przed wykonaniem git add).

Metodą uniwersalną jest wydanie komendy git add . (lub git add . --all dla gita 1.x - wszystko, aby uwzględnić też pliki właśnie usuwane z repozytorium) a następnie git diff --cached. Po tych dwóch poleceniach dostaniemy pełną listę zmian, zarówno w dodawanych, zmienianych, jak i usuwanych plikach - pozwala to zaoszczędzić naprawdę wielu pomyłek w kodzie.

Łączenie (squashowanie) commitów

Łączenie kilku commitów w jeden, zwłaszcza po ich wysłaniu do zdalnego repozytorium (origin), zaburza poniekąd historię zmian i może prowadzić do tego, że system kontroli wersji nie będzie spełniał swojej roli. Zachodzą jednak sytuacje, w których może być to przydatne, a nawet wymagane przez innych developerów. Najczęściej dzieje się tak w wypadku Pull Requestów lub ich odpowiedników z serwisów innych niż GitHub.

Powodem, dla którego w tej sytuacji możemy użyć squashowania (dosł. zgniatania) commitów jest zachodzący workflow. Zmiany są wykonywane w obrębie naszego własnego repozytorium (forka) i wysyłane jako propozycja zmian dla jakiegoś projektu. Bardzo często ludzie zarządzający tym projektem będą mieli do nas pewne uwagi i wskazówki, w związku z czym do Pull Requesta dołączają kolejne commity. Warto je na końcu połączyć, bo Pull Request z założenia powinien obejmować jedną funkcjonalność, a więc często być też jednym commitem (jak pisałem na początku artykułu). Kilka różnych metod squashowania commitów znajdziecie w tym wątku na SO.

Zakończenie

Mimo, że opisałem tutaj całkiem sporo założeń i może się wydawać, że teraz wysłanie dobrego commita miałoby zajmować wieczność i oznaczać sprawdzanie każdego punktu z listy, to nie ma czym się przejmować. Większość z wniosków wyciągniętych w artykule jest bardzo logiczna i nie powinna być dla Was niczym nowym, a reszta szybko wejdzie w nawyk. Należy brać poprawkę na dozę subiektywizmu zawartą w tym tekście.

WordPress Cleaner – optimize your database

Back in the old days I've written a very simple script to remove posts' revisions from WordPress database. It was published on my GitHub, but it was broken due to syntax error in SQL query... and it was a good thing because it might actually remove important content from your database.

And yet, the time has come. I sat down and rewrote whole script from scratch. Turned it into modular WordPress cleaner, improving performance of your installation by removing tons of garbage from your database. It is now able to remove following kinds of junk:

  • post revisions (disabled by default)
  • auto drafts
  • spam comments
  • unapproved comments
  • orphan commentmeta
  • orphan postmeta
  • transient options
  • akismet commentmeta

Look how the script performs on production database of my website (using default settings).

WordPress Cleaner

You can grab it from GitHub repository. Feedback and contributions are welcome!

What will PHP 7 bring us?

New major version of PHP is under active development since over a year (including non-public rewrite called phpng) and obviously number of changes still increases. Therefore, I have decided to point out most of important things we can expect.

For this article, I will split them between new features and actions aimed at improving language consistency and cleaning up some rubbish accumulated over the past years.

The name

Short explanation to begin with. The next version after PHP 5 will be PHP 7. The main reason is the fact that PHP 6 already existed and while it never reached stable state, there are many books and resources refering to its name. It was assumed that skipping this number will allow to avoid potential confusion.

Performance

The most notable change is huge speed improvement. Guys from Zend are doing awesome job refactoring Zend Engine and looking for new ways to make code execution faster. It results in even 50% boost in real-life applications, not in synthetic benchmarks.

New features

It should be noted that new major version almost certainly will not bring us highly-desired changes such as renaming functions to end with haystack, needle vs needle, haystack problem once and for all. While it is technically possible, PHP developers don't want to ruin their users' trust breaking too much BC and became another Python 3 (if you know what I mean).

OK, enough talking, let's get down to the facts:

  • Abstract Syntax Tree - this change is under the hood and don't have direct impact on userland code. However, it makes parsing code easier, more flexible and less error prone (it is obvious simplification of the subject, but you probably won't care about the details at this moment).
  • Null Coalesce Operator - new operator ?? (known from some other languages) is introduced. The easiest explanation would be $id = isset($_GET['id']) ? $_GET['id'] : 0; written as $id = $_GET['id'] ?? 0;. Check the original RFC for more details.
  • Unicode Codepoint Escape Syntax - introduces syntax to escape Unicode codepoint (e.g. \u{202E})
  • Closure::call() method has been added - it is a feature known from JavaScript (or rather ECMAScript), which allows to bind variables to closures at call-time
  • Error call to a member function of a non-object is now catchable
  • unserialize() allows to either completely prohibit restoring objects or restrict the objects being restored to a whitelist of objects

Cleanups

  • mysql_* functions are no longer available by default, because ext/mysql has been removed from core and will be moved to PECL
  • ext/ereg (ereg_* functions) has been removed and will be migrated to PECL as well
  • Uniform Variable Syntax - variable syntax in PHP 7 is more consistent and allows for more advanced expressions. This change introduces some backwards compatibility breaks in edge cases, but they can be avoided by using curly braces, so the code is parsed the same way in PHP 5 and 7. Click on the link to check real-life examples.
  • Some inconsistences in handling integers on different platforms are fixed (more details)
  • String support for list() has been removed as it has been introduced unintentionally and might cause unexpected results (more details)
  • Alternative PHP tags, namely <%, <%= (ASP tags) and <script language="php">, are removed
  • Support for multiple default clauses in switch has been removed
  • Support for comments indicated with # in INI files has been removed
  • $is_dst parameter of the mktime() and gmmktime() functions has been removed
  • Support for assigment of new by reference has been dropped (write $obj = new ClassName instead of $obj =& new ClassName)
  • e (eval) modifier for preg_replace() has been removed. preg_replace_callback() should be used instead
  • PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT driver option has been removed - more general PDO::ATTR_EMULATE_PREPARES should be used instead
  • CN_match and SNI_server_name stream context options are removed
  • Scoped calls of non-static methods from incompatible $this context are no longer supported (more details)
  • Support for string category names in setlocale() has been dropped (use LC_* constant instead of e.g. 'LC_ALL' string)
  • Support for unsafe CURL uploads has been removed
  • Following php.ini directives have been removed: iconv.input_encoding, iconv.output_encoding, iconv.internal_encoding, mbstring.http_input, mbstring.http_output, mbstring.internal_encoding and xsl.security_prefs
  • Following functions are removed: dl (only on fpm-fcgi SAPI), set_magic_quotes_runtime, magic_quotes_runtime, set_socket_blocking, mcrypt_generic_end, mcrypt_ecb, mcrypt_cbc, mcrypt_cfb, mcrypt_ofb, datefmt_set_timezone_id and IntlDateFormatter::setTimeZoneID

Conclusion

Personally, I find all changes mentioned above as a very good message for everyone interested in PHP development. More consistent language means less quirks to remember and allows to write code more effeciently.

Moreover, I think it's a great starting point for potential new features. Focusing on cleanup, internal and userland as well, is very important aspect, as the new major version is probably the only oportunity to do this in next about ten years.

Note: it is my first article written in English, so I would be very glad for being forgiving (in linguistic aspect, not meritorical, of course!) and pointing out eventual language mistakes.

Strony na Facebooku a kwestia jakości

Uprzedzam od razu, wpis szczególnie odkrywczy nie będzie. Łatwość tworzenia i promowania miejsc do dzielenia się swoją treścią ze światem powoduje, że zabierają się do tego zadania również osoby, nazwijmy to... mniej rozgarnięte. Tak było, jest i będzie, bo samemu portalowi na rękę jest generowanie jak największej ilości treści - o czym już pisałem - jej jakość nie ma wielkiego znaczenia.

Od pewnego czasu mam jednak wrażenie, że kilka zjawisk występuje na praktycznie każdym "fanpejdżu", a ponadto, nasila się. Ze swoich obserwacji mogę wymienić kilka zabiegów, które są powszechne i bardzo denerwują.

Czytaj dalej

Liczby nie kłamią

Od samego początku miałem założenie, że strona sobak.pl ma być moją wizytówką w sieci - całkiem logiczne i szczwane, nieprawdaż? ;) Aby jednak tak się stało, przydało by się prezentowała ona trochę danych o mojej osobie i była punktem wyjścia do dalszej eksploracji. Dlatego w dniu wczorajszym do Sobakowego Bloga dołączyła strona stats.sobak.pl. Jest to agregator kilku mniej lub bardziej interesujących statystyk związanych z moją działalnością w sieci. W chwili pisania tego skryptu pobiera i prezentuje w jednolity sposób dane pochodzące z tego bloga, a także serwisów Forumweb.pl, Wikipedia, PHP.net i Twitter.

Skrypt ma budowę modułową, aby w przyszłości łatwo móc rozszerzać go o integrację z większą ilością witryn. Cały kod źródłowy jest dostępny publicznie na Githubie. Sugestie poprawek lub pull requesty są mile widziane.

Co do samej treści serwisu - statystyki może niezbyt piękne, w paru miejscach wręcz żenujące (ah, ta aktywność na Twitterze...), jednak jednemu zaprzeczyć się nie da... liczby nie kłamią ;)

Wpis w portfolio dla tego projektu znajduje się w tym miejscu.