Czego nauczę się w tym poradniku?
- Jak utworzyć klienta do wysyłki powiadomień przez Firebase Cloud Messaging
- Jak zarządzać listą urządzeń do wysyłki powiadomień
- Jak wysłać powiadomienie push z aplikacji serwerowej na urządzenie mobilne
Wymagania
- System z rodziny UNIX/Linux
- Serwer Apache2 z PHP w wersji co najmniej 7
- Baza danych MySQL
- Projekt wykonany w framework'u Symfony
- Dowolny edytor tekstowy
- Istniejący projekt w Firebase
- Narzędzie composer
Poziom trudności
- Średni
Treść poradnika
Wskazówki opisywane w niniejszym poradniku, pozwolą osobie zainteresowanej pozyskać wiedzę do skutecznej wysyłki powiadomień push do aplikacji iOS, Android oraz web za pomocą aplikacji serwerowej utworzonej we framework'u Symfony.
Jak utworzyć klienta do wysyłki powiadomień przez Firebase Cloud Messaging?
Aby wysłać powiadomienie za pomocą FCM, koniecznym będzie utworzenie klienta do komunikacji z serwerami Firebase.
W pierwszej kolejności należy zainstalować bibliotekę guzzlehttp
w projekcie, za pomocą narzędzia Composer.
$ composer require guzzlehttp/guzzle
Narzędzie Composer zainstaluje wszelkie wymagane zależności.
Następnie, w celu utrzymania porządku w strukturze projektu, powinien zostać utworzony nowy bundle, w którym znajdował się będzie kod klienta FCM.
Informacja: Do tworzenia klienta został wykorzystany framework Symfony4, w którym proces tworzenia nowych bundle'i różni się od tego w Symfony3 i wersji wcześniejszych. Po więcej informacji na temat procesu generowania bundle'i w Symfony3.x, należy udać się do https://symfony.com/doc/3.4/bundles.html.
Należy rozpocząć od utworzenia katalogu src/Ptrio/MessageBundle
, gdzie Ptrio to nazwa wydawcy aplikacji. Można ją zastąpić swoją własną. Następnie w katalogu src/Ptrio/MessageBundle
, koniecznym jest utworzenie pliku PtrioMessageBundle.php
.
Do utworzonego pliku, powinna zostać dodana poniższa treść.
<?php
// src/Ptrio/MessageBundle/PtrioMessageBundle.php
namespace App\Ptrio\MessageBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class PtrioMessageBundle extends Bundle
{
}
Do pliku config/bundles.php
, wewnątrz tablicy zwracanej przez algorytm, należy dodać poniższą linię.
App\Ptrio\MessageBundle\PtrioMessageBundle::class => ['all' => true],
W kolejnym kroku powinien zostać utworzony katalog Client
wewnątrz katalogu src/Ptrio/MessageBundle
.
W katalogu src/Ptrio/MessageBundle/Client
powinien zostać utworzony plik interfejsu ClientInterface.php
. Do pliku należy wkleić poniższy kod.
<?php
// src/Ptrio/MessageBundle/Client/ClientInterface.php
namespace App\Ptrio\MessageBundle\Client;
interface ClientInterface
{
public function sendMessage(string $messageBody, string $recipient);
}
Jak widać na powyższym przykładzie, metoda sendMessage
przyjmować będzie dwa argumenty typu string: treść wysyłanej wiadomości oraz jej odbiorcę.
Następnie należy utworzyć plik FirebaseClient.php
w katalogu
src/Ptrio/MessageBundle/Client
i dodać do niego kod, który znajduje się poniżej.
<?php
namespace App\Ptrio\MessageBundle\Client;
use GuzzleHttp\Client;
class FirebaseClient extends Client implements ClientInterface
{
public function __construct(string $apiURL, string $serverKey)
{
parent::__construct([
'base_uri' => $apiURL,
'headers' => [
'Authorization' => 'key='.$serverKey,
],
]);
}
public function sendMessage(string $messageBody, string $recipient): string
{
$response = $this->post('/fcm/send', [
'json' => [
'to' => $recipient,
'notification' => [
'body' => $messageBody,
],
],
]);
return (string) $response->getBody();
}
}
Konfiguracja klienta, czyli parametry dotyczące adresu Firebase api i klucza serwerowego, znajdować się będą w odrębnym pliku konfiguracyjnym yaml dla utworzonego wcześniej bundle. Zanim jednak konfiguracja zostanie zaimplementowana, koniecznym jest utworzenie katalogu src/Ptrio/MessageBundle/DependencyInjection
.
Wewnątrz katalogu należy utworzyć dwa pliki: Configuration.php
oraz PtrioMessageExtension.php
.
Do pliku Configuration.php
należy wkleić poniższy kod.
<?php
// src/Ptrio/MessageBundle/DependencyInjection/Configuration.php
namespace App\Ptrio\MessageBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('ptrio_message');
$rootNode
->children()
->arrayNode('firebase')
->children()
->scalarNode('api_url')->isRequired()->cannotBeEmpty()->end()
->scalarNode('server_key')->isRequired()->cannotBeEmpty()->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}
Następnie, do pliku PtrioMessageExtension.php
należy dodać zawartość przedstawioną poniżej.
<?php
// src/Ptrio/MessageBundle/DependencyInjection/PtrioMessageExtension.php
namespace App\Ptrio\MessageBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class PtrioMessageExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));
$loader->load('services.yaml');
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('ptrio_message.api_url', $config['firebase']['api_url']);
$container->setParameter('ptrio_message.server_key', $config['firebase']['server_key']);
}
}
Kolejnym wymaganym krokiem jest utworzenie katalogu src/Ptrio/MessageBundle/Resources/config
i stworzenie wewnątrz tego katalogu pliku services.yaml
.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
services:
ptrio_message.firebase_client:
class: 'App\Ptrio\MessageBundle\Client\FirebaseClient'
arguments:
- '%ptrio_message.api_url%'
- '%ptrio_message.server_key%'
W pliku config/services.yaml
należy ustawić wartość autowire
na false
.
Następnie w katalogu config/packages
należy utworzyć plik ptrio_message.yaml
i wkleić do niego poniższy kod.
# config/packages/ptrio_message.yaml
ptrio_message:
firebase:
api_url: "https://fcm.googleapis.com"
server_key: "AAAARwFTzY4:APA91bFxbg9rBw75kLqSZkbcXZdmVcHmBnimI0dMPkrQn4iWGa9CF226Vua8zd3sxypEy48KZKCZ90M2q03oAnTXt3Z3cOE_Pp-cWqUuACUhh3r0GYDjUI3IXjwwP4c-8WWLc5Iw2iMu"
Informacja: wartość server_key
należy zamienić na tę odpowiadającą kluczowi serwerowemu w projekcie na Firebase.
Zarządzanie listą dostępnych urządzeń
Lista urządzeń, na które będzie można wysyłać powiadomienia push, przechowywana będzie w bazie danych MySQL. Mapowanie klasy na tabelę w bazie danych odbywać się będzie z zastosowaniem Doctrine ORM.
Doctrine można zainstalować za pomocą narzędzia Composer.
$ composer require doctrine
W pliku .env
należy zdefiniować parametry połączenia z bazą danych.
DATABASE_URL=mysql://db_user:[email protected]:3306/db_name
W kolejnym kroku, należy utworzyć katalog src\Ptrio\MessageBundle\Model
, a wewnątrz niego plik interfejsu DeviceInterface.php
.
<?php
// src/Ptrio/MessageBundle/Model/DeviceInterface.php
namespace App\Ptrio\MessageBundle\Model;
interface DeviceInterface
{
public function getId(): int;
public function setName(string $name);
public function getName(): string;
public function setToken(string $fcmToken);
public function getToken(): string;
}
W następnym kroku, koniecznym jest utworzenie pliku Device.php
w katalogu src/Ptrio/MessageBundle/Model
z poniższą zawartością.
<?php
// src/Ptrio/MessageBundle/Model/Device.php
namespace App\Ptrio\MessageBundle\Model;
abstract class Device implements DeviceInterface
{
protected $id;
protected $name;
protected $token;
public function getId(): int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name)
{
$this->name = $name;
}
public function getToken(): string
{
return $this->token;
}
public function setToken(string $token)
{
$this->token = $token;
}
}
Następnie powinien zostać utworzony katalog src/Ptrio/MessageBundle/Entity
, a w nim plik encji Device.php
.
<?php
// src/Ptrio/MessageBundle/Entity/Device.php
namespace App\Ptrio\MessageBundle\Entity;
use App\Ptrio\MessageBundle\Model\Device as BaseDevice;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Device extends BaseDevice
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $name;
/**
* @ORM\Column(type="string")
*/
protected $token;
}
Następnie można wykonać migracje bazy danych.
$ php bin/console doctrine:migration:diff
$ php bin/console doctrine:migration:migrate
Potrzebna będzie jeszcze usługa umożliwiająca dodawanie i usuwanie nowych urządzeń oraz ich wyszukiwanie po nazwie. Nazywać się będzie ona DeviceManager
. Zarządzanie usługą będzie możliwe za pomocą komend linii poleceń.
Najpierw powinien zostać utworzony plik interfejsu DeviceManagerInterface.php
w katalogu src\Ptrio\MessageBundle\Model
.
<?php
// src/Ptrio/MessageBundle/Model/DeviceManagerInterface.php
namespace App\Ptrio\MessageBundle\Model;
interface DeviceManagerInterface
{
public function createDevice(): DeviceInterface;
public function updateDevice(DeviceInterface $device);
public function removeDevice(DeviceInterface $device);
public function getClass(): string;
public function findDeviceByName(string $name);
public function findDeviceBy(array $criteria);
}
Następnie, w tym samym katalogu, należy utworzyć plik DeviceManager.php
.
<?php
// src/Ptrio/MessageBundle/Model/DeviceManager.php
namespace App\Ptrio\MessageBundle\Model;
abstract class DeviceManager implements DeviceManagerInterface
{
public function createDevice(): DeviceInterface
{
$class = $this->getClass();
return new $class;
}
public function findDeviceByName(string $name)
{
return $this->findDeviceBy(['name' => $name]);
}
}
Klasa właściwa dla DeviceManager
znajdować się będzie w pliku src/Ptrio/MessageBundle/Doctrine/DeviceManager.php
.
<?php
// src/Ptrio/MessageBundle/Doctrine/DeviceManager.php
namespace App\Ptrio\MessageBundle\Doctrine;
use App\Ptrio\MessageBundle\Model\DeviceInterface;
use App\Ptrio\MessageBundle\Model\DeviceManager as BaseDeviceManager;
use Doctrine\Common\Persistence\ObjectManager;
class DeviceManager extends BaseDeviceManager
{
private $objectManager;
private $repository;
private $class;
public function __construct(ObjectManager $objectManager, string $class)
{
$this->objectManager = $objectManager;
$this->repository = $objectManager->getRepository($class);
$metadata = $objectManager->getClassMetadata($class);
$this->class = $metadata->getName();
}
public function getClass(): string
{
return $this->class;
}
public function findDeviceBy(array $criteria)
{
return $this->repository->findOneBy($criteria);
}
public function updateDevice(DeviceInterface $device)
{
$this->objectManager->persist($device);
$this->objectManager->flush();
}
public function removeDevice(DeviceInterface $device)
{
$this->objectManager->remove($device);
$this->objectManager->flush();
}
}
W kolejnym kroku należy skonfigurować usługę managera w pliku src/Ptrio/MessageBundle/Resources/config/services.yaml
.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.device_manager:
class: 'App\Ptrio\MessageBundle\Doctrine\DeviceManager'
arguments:
- '@doctrine.orm.entity_manager'
- 'App\Ptrio\MessageBundle\Entity\Device'
Aby dodawać nowe urządzenia, należy utworzyć plik komendy AddDeviceCommand.php
w katalogu src/Ptrio/MessageBundle/Command
.
<?php
// src/Ptrio/MessageBundle/Command/AddDeviceCommand.php
namespace App\Ptrio\MessageBundle\Command;
use App\Ptrio\MessageBundle\Model\DeviceManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AddDeviceCommand extends Command
{
public static $defaultName = 'ptrio:message:add-device';
private $deviceManager;
public function __construct(DeviceManagerInterface $deviceManager)
{
$this->deviceManager = $deviceManager;
parent::__construct();
}
protected function configure()
{
$this->setDefinition([
new InputArgument('name', InputArgument::REQUIRED),
new InputArgument('token', InputArgument::REQUIRED),
]);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
$token = $input->getArgument('token');
if (null === $this->deviceManager->findDeviceByName($name)) {
$device = $this->deviceManager->createDevice();
$device->setName($name);
$device->setToken($token);
$this->deviceManager->updateDevice($device);
$output->writeln('Device `'.$name.'` created!');
} else {
$output->writeln('Device with this name already exists!');
}
}
}
Za usuwanie istniejących urządzeń z bazy, odpowiadać będzie klasa znajdująca się w pliku src/Ptrio/MessageBundle/Command/RemoveDeviceCommand.php
.
<?php
// src/Ptrio/MessageBundle/Command/RemoveDeviceCommand.php
namespace App\Ptrio\MessageBundle\Command;
use App\Ptrio\MessageBundle\Model\DeviceManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class RemoveDeviceCommand extends Command
{
public static $defaultName = 'ptrio:message:remove-device';
private $deviceManager;
public function __construct(DeviceManagerInterface $deviceManager)
{
$this->deviceManager = $deviceManager;
parent::__construct();
}
protected function configure()
{
$this->setDefinition([
new InputArgument('name', InputArgument::REQUIRED),
]);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
if ($device = $this->deviceManager->findDeviceByName($name)) {
$this->deviceManager->removeDevice($device);
$output->writeln('Device `'.$name.'` removed!');
} else {
$output->writeln('Device with this name does not exist!');
}
}
}
Definicje komend AddDeviceCommand
i RemoveDeviceCommand
należy dodać do pliku src/Ptrio/MessageBundle/Resources/config/services.yml
.
# src/Ptrio/MessageBundle/Resources/config/services.yml
ptrio_message.add_message_command:
class: 'App\Ptrio\MessageBundle\Command\AddDeviceCommand'
arguments:
- '@ptrio_message.device_manager'
tags:
- { name: 'console.command' }
ptrio_message.remove_message_command:
class: 'App\Ptrio\MessageBundle\Command\RemoveDeviceCommand'
arguments:
- '@ptrio_message.device_manager'
tags:
- { name: 'console.command' }
Powiadomienia wysyłane będą z wiersza poleceń Symfony CLI (Command Line Interface). Należy więc utworzyć plik src/Ptrio/MessageBundle/Command/SendMessageCommand.php
z poniższą zawartością.
<?php
// src/Ptrio/MessageBundle/Command/SendMessageCommand.php
namespace App\Ptrio\MessageBundle\Command;
use App\Ptrio\MessageBundle\Client\ClientInterface;
use App\Ptrio\MessageBundle\Model\DeviceManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class SendMessageCommand extends Command
{
private $client;
private $deviceManager;
protected static $defaultName = 'ptrio:message:send-message';
public function __construct(ClientInterface $client, DeviceManagerInterface $deviceManager)
{
$this->client = $client;
$this->deviceManager = $deviceManager;
parent::__construct();
}
protected function configure()
{
$this
->setDefinition([
new InputArgument('body', InputArgument::REQUIRED),
new InputArgument('recipient', InputArgument::REQUIRED),
]);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$messageBody = $input->getArgument('body');
$recipient = $input->getArgument('recipient');
if ($device = $this->deviceManager->findDeviceByName($recipient)) {
$response = $this->client->sendMessage($messageBody, $device->getToken());
$output->writeln('Response: '.$response);
} else {
$output->writeln('No device found!');
}
}
}
Do pliku src/Ptrio/MessageBundle/Resources/config/services.yaml
należy również dodać definicję komendy.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.send_message_command:
class: 'App\Ptrio\MessageBundle\Command\SendMessageCommand'
arguments:
- '@ptrio_message.firebase_client'
- '@ptrio_message.device_manager'
tags:
- { name: 'console.command' }
Jak wysłać powiadomienie push z aplikacji serwerowej na urządzenie mobilne?
Wysyłka powiadomienia na urządzenie mobilne odbywa się za pomocą wcześniej zdefiniowanego polecenia ptrio:message:send-message
, które przyjmuje dwa argumenty. Pierwszy to treść powiadomienia, a drugi to nazwa urządzenia mobilnego.
Urządzenie mobilne można dodać do listy za pomocą polecenia ptrio:message:add-device
.
$ php bin/console ptrio:message:add-device iphone-piotr dN0NEC9aN_w:APA91bHNPa5jsTpkyM2-9WnUtRHcxqGKqsawMnYP71-xjEH3iCS18REHbtIgwzB3L_NlCfNCqMD8qa3iaIO1m1pFMf53kg9IYWr9bipDVwGyN6oQHI_GJ1ZUWQdsi8IVsYBgtdBQ6wlI
Rezultat:
Device `iphone-piotr` created!
Informacja: Ciąg znaków po nazwie urządzenia, czyli FCM Token urządzenia należy zastąpić swoim własnym.
Dodane wcześniej urządzenie można usunąć za pomocą polecenia ptrio:message:remove-device
.
$ php bin/console ptrio:message:remove-device iphone-piotr
Wynik:
Device `iphone-piotr` removed!
Powiadomienie push można wysłać na urządzenie mobilne, stosując komendę ptrio:message:send-message
.
$ php bin/console ptrio:message:send-message 'Hello world!' iphone-piotr
W przypadku gdy wiadomość zostanie wysłana poprawnie, rezultat podobny do poniższego zostanie wyświetlony.
Response: {"multicast_id":5653186898440485701,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1520875479975905%7167cd087167cd08"}]}
Jednocześnie na docelowym urządzeniu mobilnym pojawi się powiadomienie push.
Repozytorium kodu można znaleźć na https://github.com/ptrio42/message-bundle-demo.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
Congratulations! This post has been upvoted from the communal account, @minnowsupport, by piotr42 from the Minnow Support Project. It's a witness project run by aggroed, ausbitbank, teamsteem, theprophet0, someguy123, neoxian, followbtcnews, and netuoso. The goal is to help Steemit grow by supporting Minnows. Please find us at the Peace, Abundance, and Liberty Network (PALnet) Discord Channel. It's a completely public and open space to all members of the Steemit community who voluntarily choose to be there.
If you would like to delegate to the Minnow Support Project you can do so by clicking on the following links: 50SP, 100SP, 250SP, 500SP, 1000SP, 5000SP.
Be sure to leave at least 50SP undelegated on your account.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit