Doctrine a klasy potomne

W dzisiejszym wpisie chce się z Wami podzielić moimi ostatnimi potyczkami z używaniem klas potomnych w Doctrinie gdy potrzebowałem zrobić bardziej zaawansowane zapytania w DQL.

Na pierwszy ogień przypadek operowania na właściwościach dostępnych tylko w klasie potomnej. Załóżmy, że mamy taką sytuacje - encje Word, przechowującej słówka do wyuczenia w ramach jakiegoś kursu internetowego. Mamy też jej klasę potomną DifficultWord, która posiada dodatkowe właściwości jak np. referencje do mema ułatwiającego zapamiętanie trudnego słowa. Encja Word jest również w relacji z uczestnikiem kursu. Gdybyśmy chcieli uzyskać informacje dla jakiego uczestnika przypisany jej konkretny mem z trudnym słówkiem to nie możemy operować tutaj encją Word (pułapka szczelinowa). W takim przypadku musimy złożyć zapytanie operując na jej klasie potomnej.

$qb = $this->createQueryBuilder('md');
$qb->join('Entity\DifficultWord', 'd', Join::WITH, 'md.difficultWord = d.id');
$qb->andWhere('d.mem = :mem');
$qb->setParameter('mem', $mem);

Całość sprowadza się tutaj do wykonania złączenia tabeli uczestników z ich trudnymi słówkami (join) i wyfiltrowanie właściwego mema (where).

Znacznie trudniejszy przypadek dotyczył operowaniem encją rodzica, ale w taki sposób jakby był klasą potomną. Trzymając się przytoczonego przykładu załóżmy, że chcemy pobrać listę słów, których nauczył się uczestnik kursu. Musimy tutaj pominąć trudne słowa. Jednocześnie pamiętamy, że przecież trudne słowa są podzbiorem słów jako takich. Stąd warunek taki jak poniżej się nie sprawdzi.

$qb->andWhere($qb->expr()->isInstanceOf('w', Word::class));

Po długich godzinach przeszukiwania stackoverflow znalazłem rozwiązanie działające z nowszymi wersjami Doctrina. Opiera się ono na wykorzystaniu dyskriminatora danej encji zamiast typu klasy (gdzie word jest właśnie nazwą dyskriminatora pożądanej encji, zgodnie z adnotacją DiscriminatorMap).

$qb->andWhere('w INSTANCE OF :type');
$qb->setParameter('type', 'word');

...i pomyśleć, że to tylko zwykłe zapytania SELECT.

Dwa tricki na wyjątki w PHP

Kontynując wątek o wyjątkach w PHP chcę Wam przedstawić dwa nowe tricki, które ostatnio odkryłem. Nie są one jakieś rewolucyjne, ale pomagają mi szybciej i efektywniej zalogować wszelkie sytuacje krytyczne w aplikacji.

Od wersji PHP 7.1 istnieje możliwość przechwytywania wielu typów wyjątków w ramach tego samego bloku catch. Wszystko to za sprawą składni analogicznej do unii ("|"), tak jak poniżej.

<?php

class MyException extends Exception { }

class MyOtherException extends Exception { }

class Test {
    public function testing() {
        try {
            throw new MyException();
        } catch (MyException | MyOtherException $e) {
            var_dump(get_class($e));
        }
    }
}

$foo = new Test;
$foo->testing();

?>
Źródło: https://www.php.net/manual/en/language.exceptions.php#example-294

Sporo czasu zajeło mi znalezienie jakiegoś łatwego i szybkiego sposobu do zalogowania wyjątku, tak by nie tracić podstawowych informacji do debugowania. Koniec końców postawiłem na prostotę przy użyciu metody toString() i kontekstu zgodnie z PSR3. Dzięki temu nie trzeba pisać boilerplate code, działa niezależenie od użytego w aplikacji logera i posiada informacje z komunikatem wyjątku, linią i plikiem jego wystąpienia. Dla przykładu.

try {
    throw new \Exception('Some exception');
} catch (\Exception $ex) {
    $this->logger->error(
        'Text messsage',
        [
            'ex' => $ex,
        ]
    );
}

Zalogowane jako
[2020-10-18 12:55:07] app.ERROR: Text messsage {"ex":"[object] (Exception(code: 0): Some exception at /home/dominik/application/src/Command/Auth.php:52)"} []

Dzięki, tyle na dzisiaj ;-)

Nuxt.js - Wprowadzenie

Witajcie po wakacyjnej przerwie. W dzisiejszym wpisie chcę się z Wami podzielić kilkoma moimi obserwacjami, które napotkałem ucząc się Nuxt.js i Vue przez właśnie wakacje.

Jak wygląda struktura plików w Nuxt.js?

  • Assets -> pliki takie jak SASS, LESS lub JavaScript.
  • Components -> reużywalne komponenty. np. navBar.vue.
  • Layout -> definicje wyglądu różnych elementów na stronie.
  • Middleware -> przechowuje funkcje, które mają się wykonać przed renderowaniem strony lub grupy stron.
  • Pages -> pliki vue opisujące działanie konkretnej strony.
  • Plugins -> kod JS, który ma się wykonać przed renderowaniem komponentów i stron.
  • nuxt.config.js -> globalna konfiguracja naszej aplikacji.

Ruting

To w jaki sposób będzie się odbywać komunikacja między przeglądarką a aplikacją zależy właśnie od tego mechanizmu. Jest to swego rodzaju nakładka na vue-router. Każda strona powinna znaleźć się w folderze pages i do niej automatycznie zostanie stworzony ruting. Jeśli plik nazywa się about.vue adres to naszadomena/about. By tworzyć ruting dynamiczny (z podaniem dodatkowych parametrów np. id) nazwa pliku musi zawierać prefiks "_". Dla przykładu pages/users/_id.vue zostanie przetłumaczony na np. naszadomena/users/1. Z kolei z poziomu strony linki tworzymy analogicznie do <nuxt-link to="Nazwa rutingu">Przyjazna nazwa</nuxt-link>.

API

Pracując z Nuxt.js musimy pamiętać o różnicy między renderowaniem po stronie serwera a renderowaniem po stronie klienta. Gdy jesteśmy na poziomie serwera (Node.js) nie mamy dostępu do takich obiektów jak window czy document, które są dostępne po stronie przeglądarki. Zatem gdy jest taka konieczność, część logiki możemy przenieść na klienta wykorzystując w tym celu metodę beforeMount() lub mounted(), tak jak poniżej:

beforeMount{
window.alert('hello');
}
mounted{
window.alert('hello');
}

Ogólnie rzecz ujmując w momencie gdy przeglądarka wysyła żądanie do serwera Nuxt.js generuje HTML i wykonuje metody asyncData(), nuxtServerInit() i fetch(). Następnie przeglądarka odbiera taką stronę i Vue.js zaczyna być uruchomiony. Po czym użytkownik wędruje po stronach używając komponentu całkowicie bez uderzania do serwera.

Różnica między asyncData() i fetch()

Na koniec muszę wspomnieć o moich problemach ze zrozumieniem kiedy wywoływać metodę asyncData() a kiedy fetch() z API. Różnica jest dosyć subtelna, nie mniej istotna. Metoda asyncData() jest wykonywana po stronie serwera przed renderowaniem, fetch() podobnie przy czym nie ustawia obiektów po pobraniu i dlatego lepiej go stosować wraz z Vuex.
Co ciekawe obie metody mogą być triggerowane gdy wymagają ich konkretne podstrony (tj. już po stronie klienta).

Tyle na dziś, więcej na
https://www.smashingmagazine.com/2020/04/getting-started-nuxt/
https://nuxtjs.org/

Przestrzenie nazw i moduły w ES6

O zaletach korzystania z przestrzeni nazw w takich językach jak C++, Java czy PHP chyba nie trzeba nikogo przekonywać. Właściwie w dużych projektach, zwłaszcza w takich co silnie wykorzystują zewnętrzne biblioteki odpowiednia separacja kodu jest niezbędna. W tym artykule będę chciał przedstawić jak się sprawy mają w JavaScript historycznie jak i w dobie standardu ECMAScript 6 (ES6).

Dla osób przyzwyczajonych do technologii C++, Java czy PHP może być pewnym szokiem, że przestrzenie nazw jako takie w JavaScript nie istnieją. Zamiast tego wykorzystuje się tu koncepcje modułów, chociażby za sprawą ES6, który je ustandaryzował. Przedtem obowiązywały standard AMD z jego implementacją require.js dla rozwiązań po stronie klienta oraz CommonJS dla środowiska Node.Js. Był jeszcze standard UMD, który próbował połączyć obydwa te rozwiązania. To co stanowiło siłę tych technologii to ograniczenie modułów do jednego pliku, automatycznie załączanie modułów na podstawie ich wzajemnych zależności czy możliwość wczytywania poszczególnych modułów z różnych bibliotek bez ładowania całości.

Przykład z http://papoldesign.pl/edukacja/requirejs-czym-amd-api-podstawy-uzycia-loadera/

requirejs.config({
paths: {
'jquery': 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min'
}
});

require(['jquery'], function ($) {

require(["one", "two", "three"], function (one, two, three) {
$('h1').html(three - one + two);
});
});

gdzie na początku ustalamy ścieżkę do biblioteki jQuery, następnie deklarujemy moduł wstrzykując jQuery w zmiennej "$" oraz dociągamy kolejne moduły (one.js, two.js ,three.js).

A jak to wygląda obecnie, w dobie ECMAScript 6?
Właściwie podobnie, moduły znajdują się w pliku js z tą różnicą, że jego wczytanie odbywa się przez słowo kluczowe import a deklaracja poprzez użycie export. Możemy eskportować zarówno funkcje, zmienne, stałe jak i klasy.

//plik pi.js
export let PI = () => {
return 3.14;
};

//plik main.js
import * as math from '~/static/pi.js'
console.log(math.PI());

To o czym warto pamiętać w kontekście modułów z ES6 to, że:

  • domyślnie stosują tryb strict,
  • są one wczytywane asynchronicznie,
  • zmienne lokalne widoczne są jedynie w obrębie modułu,
  • są wywoływane tylko raz,
  • można je też zadeklarować poprzez znacznik "<script type=module>".

Pewnym problemem może by wczytywanie dynamicznie modułów, powszechnie stosowanie rozwiązanie sprowadza się w takim przypadku do wykorzystania async/await

sync function load() {
const obj = await import("./functions.js");
module.smallText();
module.bigText();
}

const button = document.querySelector("button");
button.addEventListener("click", async () => {
const obj = await import("./functions.js");
module.smallText();
module.bigText();
});

Muszę przyznać, że ES6 naprawdę wymiata. Więcej pod https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

Dziedziczenie w JavaScript

Dzisiaj słów kilka o dziedziczeniu w JavaScript. Tym wpisem chciałbym zainaugurować serią artykułów na temat różnych smaczków tego języka. Mam nadzieje, że to mi się uda a i że wy odkryjecie w tym coś interesującego dla was samych.

JS posiada dziedziczenie oparte o prototypach, stanowi to odmienne podejście niż to znane nam z języków takich jak np. Java czy PHP. Główna różnica to na brak klas (chociaż standard ECMAScript 6 wprowadził je do języka to i tak „pod spodem” mamy do czynienia z prototypami. Co to takiego te prototypy? Upraszczając, to zdolność obiektu do posiadania referencji __proto__, która przechowuje właściwości i metody będące klonowane (skopiowane) do klasy potomnej. Każdy obiekt w JavaScipt zawiera taką referencje, jest to tak zwany obiekt nadrzędny. W momencie gdy "klasa" potomna nie posiada żądanej metody, to silnik JS sprawdza obiekt rodzica a następnie obiekt nadrzędny w celu uzyskania danej metody. Poniżej prosty przykład, który to obrazuje.

// konstruktor
function Book(name) {
this.name = name || 'Eloquent JavaScript';
};

//dodawanie do prototypu
Book.prototype.getName = function () {
  return this.name;
};

// utworzenie obiektu
var book = new Book();

// dziedziczenie
var technicalBook = Object.create(book); //Object.create istnieje od standardu ECMAScript 5

console.log(technicalBook.getName()); // Zwróci Eloquent JavaScript

Z kolei od ECMAScript 6 moglibyśmy to zapisać w taki sposób

class Book {
  setName(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
}

class TechnicalBook extends Book {

}

let technicalBook = new TechnicalBook();
technicalBook.setName('Eloquent JavaScript');

console.log(technicalBook.getName()); // Zwróci Eloquent JavaScript
console.log(technicalBook.name); // Zwróci Eloquent JavaScript

Tyle na dzisiaj, a wy którą składnie bardziej preferujecie?