Realtime w PHP z użyciem Firestore.
git clone https://github.com/cocoders/phpers-summit-2019.git
drive: https://bit.ly/2kvkR2p
slajdy: https://slides.com/leszekprabucki/phpers-summit-2019
Po co nam przetwarzanie w czasie rzeczywistym?
PHP - ograniczona możliwość
Jakie możliwości mamy w PHPie odnośnie przetwarzania?
Firestore, Ratchet, Mercure, Pusher, Websocekts, ...
Nasz wybór Firebase (Firestore) - bardzo łatwo zintegrować z PHP i Angularem
Czym jest firebase/firestore?
Firebase vs Firestore
Nasz projekt
#language: pl
Funkcja:
Jako klient chciałbym móc zobaczyć aktualne menu piwa
Żeby nie chodzić bez sensu do baru
Scenariusz: Nowe piwo w menu
Gdy barman doda nowe piwa:
| nazwa | opis |
| California | California, czyli AIPA w naszym wykonaniu... |
Wtedy klient powinien zobaczyć 1 piwo
#language: pl
Funkcja:
Jako klient chciałbym móc zobaczyć aktualne menu piwa
Żeby nie chodzić bez sensu do baru
Scenariusz: Piwo dostępne w menu się skończyło
Zakładając że barman dodał piwa:
| nazwa | opis |
| California | California, czyli AIPA w naszym wykonaniu. |
| Harry | Harry wyjechał na studia do USA. Jako anglik pijał herbatę earl grey. |
| Nonsens Pils | Klasyczny pils z małym twistem w postaci herbaty earl grey. | |
Kiedy barman stwierdzi, że piwo "California" się skończyło
Wtedy klient powinien zobaczyć 2 piwa
Implementacja PHP
Nowy projekt firebase
Nowy projekt firebase
Nowy projekt firebase
Nowy projekt firebase
Nowy projekt firebase
Klucze
RPC framework - remote procedure call
Stworzony przez googla
gRPC
https://grpc.io
Zaczynamy od Dockera który ma gRPC na pokładzie
docker-compose.yml
version: '3.7'
services:
php:
image: cocoders/phpers-summit-2019-symfony
environment:
- GOOGLE_APPLICATION_CREDENTIALS=/var/www/beer/key/key.json
networks:
- default
- nginx-proxy
volumes:
- .:/var/www/beer
expose:
- 8000
environment:
- VIRTUAL_HOST=beer.local
- VIRTUAL_PORT=8000
angular:
image: cocoders/phpers-summit-2019-angular
volumes:
- .:/var/www/beer
networks:
- default
- nginx-proxy
expose:
- 4200
environment:
- VIRTUAL_HOST=frontend.beer.local
- VIRTUAL_PORT=4200
nginx-proxy:
image: jwilder/nginx-proxy:alpine
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
networks:
- default
- nginx-proxy
whoami:
image: jwilder/whoami
networks:
- default
- nginx-proxy
networks:
nginx-proxy:
external:
name: nginx-proxy
version: '3.7'
services:
php:
image: cocoders/phpers-summit-2019-symfony
environment:
- GOOGLE_APPLICATION_CREDENTIALS=/var/www/beer/key/key.json
networks:
- default
- nginx-proxy
volumes:
- .:/var/www/beer
expose:
- 8000
environment:
- VIRTUAL_HOST=beer.local
- VIRTUAL_PORT=8000
// ...
version: '3.7'
services:
// php
angular:
image: cocoders/phpers-summit-2019-angular
volumes:
- .:/var/www/beer
networks:
- default
- nginx-proxy
expose:
- 4200
environment:
- VIRTUAL_HOST=frontend.beer.local
- VIRTUAL_PORT=4200
// ...
version: '3.7'
services:
// php and angular
nginx-proxy:
image: jwilder/nginx-proxy:alpine
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
networks:
- default
- nginx-proxy
whoami:
image: jwilder/whoami
networks:
- default
- nginx-proxy
Dodajemy do /etc/hosts *
127.0.0.1 frontend.beer.local beer.local
Inicjalizacja projektu
* Na linuxach z obsługą make możemy wykonać polecenie
"sudo make init"
docker network create nginx-proxy
Inicjalizacja projektu
docker-compose up -d
Konfiguracja kluczy dla projektu firebase w Symfony
version: '3.7'
services:
php:
image: cocoders/phpers-summit-2019-symfony
environment:
- GOOGLE_APPLICATION_CREDENTIALS=/var/www/beer/key/key.json
networks:
- default
- nginx-proxy
volumes:
- .:/var/www/beer
expose:
- 8000
environment:
- VIRTUAL_HOST=beer.local
- VIRTUAL_PORT=8000
// ...
Zobaczmy composer.json
<?php
declare(strict_types=1);
namespace App\BeerMenu\Model;
final class Beer
{
private string $name;
private string $description;
public function __construct(string $name, string $description)
{
$this->name = $name;
$this->description = $description;
}
public function name(): string
{
return $this->name;
}
public function description(): string
{
return $this->description;
}
}
<?php
declare(strict_types=1);
namespace App\BeerMenu\Model;
interface Beers
{
public function inStock(Beer $beer): void;
public function stockOut(string $beerName): void;
}
<?php
declare(strict_types=1);
namespace App\BeerMenu\Query;
interface CurrentMenuQuery
{
public function count(): int;
}
<?php
declare(strict_types=1);
namespace App\BeerMenu\Infrastructure\Firestore;
use App\BeerMenu\Model\Beer;
use App\BeerMenu\Model\Beers as BeersInterface;
use Google\Cloud\Firestore\FirestoreClient;
final class Beers implements BeersInterface
{
public function inStock(Beer $beer): void
{
$firestore = new FirestoreClient();
$docRef = $firestore->collection('beers')->document($beer->name());
$docRef->set([
'name' => $beer->name(),
'description' => $beer->description(),
]);
}
public function stockOut(string $beerName): void
{
}
}
symfony console make:command app:beer:add
Dodajmy do naszego polecenia użycie CurrentMenuQuery::count()
<?php
declare(strict_types=1);
namespace App\BeerMenu\Infrastructure\Firestore;
use App\BeerMenu\Query\CurrentMenuQuery as CurrentMenuQueryInterface;
use Google\Cloud\Firestore\FirestoreClient;
final class CurrentMenuQuery implements CurrentMenuQueryInterface
{
public function count(): int
{
// implements this
// hint: https://cloud.google.com/firestore/docs/query-data/get-data
}
}
Zaimplementujmy
App\BeerMenu\Infrastructure\Firestore\Users::stockOut
<?php
declare(strict_types=1);
namespace App\BeerMenu\Infrastructure\Firestore;
use App\BeerMenu\Model\Beer;
use App\BeerMenu\Model\Beers as BeersInterface;
use Google\Cloud\Firestore\FirestoreClient;
final class Beers implements BeersInterface
{
public function inStock(Beer $beer): void
{
$firestore = new FirestoreClient();
$docRef = $firestore->collection('beers')->document($beer->name());
$docRef->set([
'name' => $beer->name(),
'description' => $beer->description(),
]);
}
public function stockOut(string $beerName): void
{
// implement this
// hint: https://cloud.google.com/firestore/docs/manage-data/delete-data
}
}
symfony console make:command app:beer:remove
Implementacja Angular
npm install @angular/fire firebase --save
Konfiguracja w połączenia do firebase w pliku environment
export const environment = {
production: false,
firebase: {
apiKey: '<your-key>',
authDomain: '<your-project-authdomain>',
databaseURL: '<your-database-URL>',
projectId: '<your-project-id>',
storageBucket: '<your-storage-bucket>',
messagingSenderId: '<your-messaging-sender-id>'
}
};
import { Component, Input } from '@angular/core';
import { Beer } from '../beer';
@Component({
selector: 'app-beer',
templateUrl: './beer.component.html',
styleUrls: ['./beer.component.scss']
})
export class BeerComponent {
@Input() beer: Beer;
}
export interface Beer {
name: string;
description: string;
}
<ng-container *ngIf="beer">
<figure>
<img src="assets/beers/{{ beer.name | photoName }}.png"
alt="{{ beer.description }}">
<figcaption><h3>{{beer.name}}</h3></figcaption>
</figure>
<p>{{beer.description}}</p>
</ng-container>
import { Pipe, PipeTransform } from '@angular/core';
import { Beer } from '../beer';
@Pipe({
name: 'photoName'
})
export class PhotoNamePipe implements PipeTransform {
transform(value: string, ...args: any[]): any {
if (!value) {
return '';
}
return value.replace(' ', '_').toLowerCase();
}
}
Problemy
Trudne testowanie
Odcinamy infrastrukturę w testach
Pierwsza próba - tabelka w phpie
java -jar $HOME/.cache/firebase/emulators/cloud-firestore-emulator-v1.3.0.jar --host=127.0.0.1
Zmiana podejścia
Umieszczenie realtime w swojej architekturze
"Skakanie"
Reguły security
Dzięki!
Pora na piwo!
Realtime w PHP z użyciem Firestore.
By Leszek Prabucki
Realtime w PHP z użyciem Firestore.
- 1,201