Czego nauczę się w tym poradniku?
- Jak stworzyć rejestr dla wysłanych wiadomości push
- Jak wyświetlić historię wiadomości push wysłanych na dane urządzenie mobilne
- Jak wysłać wiadomość do kilku urządzeń jednocześnie
Wymagania
- System z rodziny UNIX/Linux
- Serwer Apache2 z PHP w wersji 7.1
- 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
Ten poradnik, jest kolejnym z serii Web Development. Poprzednio opisany został proces implementacji aplikacji konsolowej do zarządzania wysyłką powiadomień push do aplikacji mobilnych.
W dzisiejszym artykule, do istniejących już funkcjonalności, zostaną dopisane nowe, rozszerzające możliwości aplikacji o bardziej szczegółową obsługę wiadomości, wyświetlanie historii wysłanych wiadomości i zdolność do wysyłki wiadomości do kilku urządzeń mobilnych jednocześnie.
Informacja: Proces tworzenia modułu
PtrioMessageBundle
został opisany szczegółowo w poprzednim artykule z serii Web Development. Jeżeli nie posiadasz kodu źródłowego, dowiedz się jak go utworzyć lub pobierz przykładowy projekt z repozytorium git.
Jak utworzyć usługę do zarządzania wiadomościami?
Usługa managera wiadomości pozwoli na:
- przechowywanie wiadomości w bazie danych
- wyświetlenie histori wiadomości wysłanych do urządzenia
Informacja: Na początku należy wykonać deklaracje dla obiektów przechowujących dane na temat wiadomości.
Klasa wiadomości
Każda wiadomość posiadać będzie cztery parametry: identyfikator, treść, odbiorcę oraz datę wysyłki wiadomości.
Odbiorcą wiadomości będzie urządzenie mobilne o zdefiniowanej nazwie i tokenie FCM.
Warstwa abstrakcyjna
Zacznijmy od interfejsu. W katalogu src/Ptrio/MessageBundle/Model
należy utworzyć plik o nazwie MessageInterface.php
.
<?php
// src/Ptrio/MessageBundle/Model/MessageInterface.php
namespace App\Ptrio\MessageBundle\Model;
interface MessageInterface
{
public function getId(): int;
public function setBody(string $body);
public function getBody(): string;
public function setDevice(DeviceInterface $device);
public function getDevice(): DeviceInterface;
public function setSentAt(\DateTime $sentAt);
public function getSentAt(): ?\DateTime;
}
W kolejnym kroku, w tym samym katalogu powinna zostać utworzona klasa abstrakcyjna Message
(plik Message.php
), implementująca interfejs MessageInterface
.
<?php
// src/Ptrio/MessageBundle/Model/Message.php
namespace App\Ptrio\MessageBundle\Model;
abstract class Message implements MessageInterface
{
/**
* @var int
*/
protected $id;
/**
* @var string
*/
protected $body;
/**
* @var DeviceInterface
*/
protected $device;
/**
* @var \DateTime
*/
protected $sentAt;
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return string
*/
public function getBody(): string
{
return $this->body;
}
/**
* @param string $body
*/
public function setBody(string $body)
{
$this->body = $body;
}
/**
* @return DeviceInterface
*/
public function getDevice(): DeviceInterface
{
return $this->device;
}
/**
* @param DeviceInterface $device
*/
public function setDevice(DeviceInterface $device)
{
$this->device = $device;
}
/**
* @return \DateTime|null
*/
public function getSentAt(): ?\DateTime
{
return $this->sentAt;
}
/**
* @param \DateTime $sentAt
*/
public function setSentAt(\DateTime $sentAt)
{
$this->sentAt = $sentAt;
}
}
Klasa właściwa
Klasa encji wiadomości Message
znajdować się będzie w katalogu src/Ptrio/MessageBundle/Entity
, a w niej instrukcje potrzebne do utworzenia reprezentacji wiadomości w bazie danych.
<?php
// src/Ptrio/MessageBundle/Entity/Message.php
namespace App\Ptrio\MessageBundle\Entity;
use App\Ptrio\MessageBundle\Model\Message as BaseMessage;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Message extends BaseMessage
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $body;
/**
* @ORM\ManyToOne(targetEntity="Device")
* @ORM\JoinColumn(name="device_id", referencedColumnName="id")
*/
protected $device;
/**
* @ORM\Column(type="date")
*/
protected $sentAt;
}
Następnie należy wykonać migracje bazy danych, aby zaktualizować strukturę tabel.
Klasa managera wiadomości
Potrzebna jest usługa odpowiedzialna za zarządzanie wiadomościami. Obiekt managera wiadomości będzie mógł utworzyć nową wiadomość i zapisać ją w bazie danych oraz odszukać wiadomości wysłane na konkretne urządzenie mobilne.
Warstwa abstrakcyjna
Plik interfejsu MessageManagerInterface.php
należy uzupełnić kodem, który zlokalizowany jest poniżej.
<?php
// src/Ptrio/MessageBundle/Model/MessageManagerInterface.php
namespace App\Ptrio\MessageBundle\Model;
interface MessageManagerInterface
{
public function createMessage(): MessageInterface;
public function updateMessage(MessageInterface $message);
public function findMessagesByDevice(DeviceInterface $device): array;
public function getClass();
}
Następnie należy utworzyć abstrakcyjną klasę MessageManager
implementującą interfejs MessageManagerInterface
.
<?php
// src/Ptrio/MessageBundle/Model/MessageManager.php
namespace App\Ptrio\MessageBundle\Model;
abstract class MessageManager implements MessageManagerInterface
{
public function createMessage(): MessageInterface
{
$class = $this->getClass();
return new $class;
}
}
Klasa właściwa
W katalogu src/Ptrio/MessageBundle/Doctrine
należy utworzyć plik o nazwie MessageManager.php
.
<?php
// src/Ptrio/MessageBundle/Doctrine/MessageManager.php
namespace App\Ptrio\MessageBundle\Doctrine;
use App\Ptrio\MessageBundle\Model\MessageInterface;
use App\Ptrio\MessageBundle\Model\MessageManager as BaseMessageManager;
use Doctrine\Common\Persistence\ObjectManager;
class MessageManager extends BaseMessageManager
{
/**
* @var ObjectManager
*/
private $objectManager;
/**
* @var \Doctrine\Common\Persistence\ObjectRepository
*/
private $repository;
/**
* @var string
*/
private $class;
/**
* MessageManager constructor.
* @param ObjectManager $objectManager
* @param string $class
*/
public function __construct(
ObjectManager $objectManager,
string $class
)
{
$this->objectManager = $objectManager;
$this->repository = $objectManager->getRepository($class);
$metadata = $objectManager->getClassMetadata($class);
$this->class = $metadata->getName();
}
/**
* @param MessageInterface $message
*/
public function updateMessage(MessageInterface $message)
{
$this->objectManager->persist($message);
$this->objectManager->flush();
}
/**
* @return string
*/
public function getClass()
{
return $this->class;
}
/**
* @param DeviceInterface $device
* @return array
*/
public function findMessagesByDevice(DeviceInterface $device): array
{
return $this->repository->findBy(['device' => $device]);
}
}
Konfiguracja usługi managera
Do pliku src/Ptrio/MessageBundle/Resources/config/services.yaml
należy dodać informacje na temat konfiguracji managera wiadomości.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.message_manager:
class: 'App\Ptrio\MessageBundle\Doctrine\MessageManager'
arguments:
- '@doctrine.orm.entity_manager'
- 'App\Ptrio\MessageBundle\Entity\Message'
Implementacja usługi do wyświetlania historii wiadomości
Aktualizacja klasy do wysyłki wiadomości
Przed rozpoczęciem implementacji usługi należy zaktualizować klasę SendMessageCommand
. Klasa musi zostać dostosowana tak, aby podczas wysyłki wiadomości były one też rejestrowane w bazie danych. W tym celu zostanie wykorzystana usługa managera wiadomości.
Wewnątrz klasy należy dodać deklarację dla zmiennej dla obiektu typu MessageManagerInterface
.
// other class scope variables
private $messageManager;
Do argumentów konstruktora należy dodać MessageManagerInterface $messageManager
, a następnie upewnić się, że obiekt managera wiadomości zapisywany jest do zmiennej $this->messageManager
.
public function __construct(
ClientInterface $client,
DeviceManagerInterface $deviceManager,
MessageManagerInterface $messageManager // add this argument
)
{
$this->client = $client;
$this->deviceManager = $deviceManager;
$this->messageManager = $messageManager; // add this line
parent::__construct();
}
W metodzie execute(InputInterface $input, OutputInterface $output)
, wewnątrz warunku if ($device = $this->deviceManager->findDeviceByName($recipient))
należy dodać polecenia odpowiedzialne za umieszczenie wpisu o wiadomości w bazie danych.
$message = $this->messageManager->createMessage();
$message->setBody($messageBody);
$message->setDevice($device);
$message->setSentAt(new \DateTime('now'));
$this->messageManager->updateMessage($message);
Aktualizacja pliku services.yaml
Należy zaktualizować konfigurację dla usługi komendy ptrio_message.send_message_command
, dodając usługę @ptrio_message.message_manager
jako argument.
ptrio_message.send_message_command:
class: 'App\Ptrio\MessageBundle\Command\SendMessageCommand'
arguments:
- '@ptrio_message.firebase_client'
- '@ptrio_message.device_manager'
- '@ptrio_message.message_manager' # add this argument
tags:
- { name: 'console.command' }
Utworzenie klasy komendy do wyświetlania historii wiadomości
Obiekt klasy ListDeviceMessagesCommand
będzie odpowiedzialny za odnalezienie wpisów dla danego urządzenia mobilnego i wyświetlenie wyniku w postaci tabeli.
<?php
// src/Ptrio/MessageBundle/Command/ListDeviceMessagesCommand.php
namespace App\Ptrio\MessageBundle\Command;
use App\Ptrio\MessageBundle\Model\DeviceManagerInterface;
use App\Ptrio\MessageBundle\Model\MessageInterface;
use App\Ptrio\MessageBundle\Model\MessageManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ListDeviceMessagesCommand extends Command
{
private $messageManager;
private $deviceManager;
public static $defaultName = 'ptrio:message:list-device-messages';
public function __construct(
MessageManagerInterface $messageManager,
DeviceManagerInterface $deviceManager
)
{
$this->messageManager = $messageManager;
$this->deviceManager = $deviceManager;
parent::__construct();
}
protected function configure()
{
$this->setDefinition([
new InputArgument('device-name', InputArgument::REQUIRED),
]);
}
public function execute(InputInterface $input, OutputInterface $output)
{
$deviceName = $input->getArgument('device-name');
if ($device = $this->deviceManager->findDeviceByName($deviceName)) {
$deviceMessages = $this->messageManager->findMessagesByDevice($device);
$io = new SymfonyStyle($input, $output);
$tableHeader = ['Device Name', 'Message Body', 'Sent At'];
$tableBody = [];
foreach ($deviceMessages as $message) {
/** @var MessageInterface $message */
$tableBody[] = [$message->getDevice()->getName(), $message->getBody(), $message->getSentAt()->format('Y-m-d H:i')];
}
$io->table($tableHeader, $tableBody);
}
}
}
Aktualizacja pliku konfiguracji usług
Dodatkowo do pliku services.yaml
należy dodać konfigurację dla usługi ListDeviceMessagesCommand
.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.list_device_messages_command:
class: 'App\Ptrio\MessageBundle\Command\ListDeviceMessagesCommand'
arguments:
- '@ptrio_message.message_manager'
- '@ptrio_message.device_manager'
tags:
- { name: 'console.command' }
Jak wyświetlić historię wiadomości push wysłanych na dane urządzenie mobilne?
Wiadomości można wyświetlić za pomocą polecenia ptrio:message:list-device-messages iphone-piotr
, gdzie przekazywany argument to nazwa urządzenia mobilnego.
$ php bin/console ptrio:message:list-device-messages iphone-piotr
Przykładowa lista wiadomości jest widoczna poniżej.
-------------- ----------------------------------- ---------------------
Device Name Message Body Sent At
-------------- ----------------------------------- ---------------------
iphone-piotr Remember, the meeting is at 10am! 2018-03-13 23:59
iphone-piotr Open your mail! 2018-03-13 00:09
iphone-piotr Are you not forgetting something? 2018-03-13 00:17
-------------- ----------------------------------- ---------------------
Informacja: Należy wysłać kilka testowych wiadomości za pomocą polecenia
ptrio:message:send-message
aby wpisy pojawiły się w rejestrze.
Jak wysłać wiadomość do kilku urządzeń jednocześnie?
Aby dodać funkcjonalność wysyłki wiadomości push do kilku urządzeń jednocześnie, koniecznym jest rozszerzenie usługi managera urządzeń o możliwość wyszukiwania wielu urządzeń w bazie. Następnie zostanie zaktualizowana klasa SendMessageCommand
aby przyjmować tablicę elementów jako nazwy urządzeń.
Rozbudowa usługi managera urządzeń
Należy rozpocząć od utworzenia katalogu src/Ptrio/MessageBundle/Repository
.
Utworzenie repozytorium
Plik interfejsu DeviceRepositoryInterface.php
powinien zostać dodany do poprzednio utworzonego katalogu.
<?php
// src/Ptrio/MessageBundle/Repository/DeviceRepositoryInterface.php
namespace App\Ptrio\MessageBundle\Repository;
use Doctrine\Common\Persistence\ObjectRepository;
interface DeviceRepositoryInterface extends ObjectRepository
{
public function findDevicesByNames(array $deviceNames): array;
}
Następny krok to utworzenie pliku klasy repozytorium DeviceRepository.php
.
<?php
// src/Ptrio/MessageBundle/Repository/DeviceRepository.php
namespace App\Ptrio\MessageBundle\Repository;
use Doctrine\ORM\{
EntityManagerInterface, EntityRepository
};
class DeviceRepository extends
EntityRepository implements
DeviceRepositoryInterface
{
public function __construct(EntityManagerInterface $em, string $class)
{
parent::__construct($em, $em->getClassMetadata($class));
}
public function findDevicesByNames(array $deviceNames): array
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb
->select('d')
->from($this->getEntityName(), 'd')
;
for ($i = 0; $i < count($deviceNames); $i++) {
$qb
->orWhere($qb->expr()->eq('d.name', ':param_'.$i))
->setParameter(':param_'.$i, $deviceNames[$i])
;
}
return $qb->getQuery()->getResult();
}
}
Do pliku src/Ptrio/MessageBundle/Resources/config/services.yaml
należy dodać parameter ptrio_message.model.device.class
.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
parameters:
ptrio_message.model.device.class: 'App\Ptrio\MessageBundle\Entity\Device'
W kolejnym kroku wymagane jest dodanie deklaracji dla usługi managera urządzeń.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.device_repository:
class: 'App\Ptrio\MessageBundle\Repository\DeviceRepository'
arguments:
- '@doctrine.orm.entity_manager'
- '%ptrio_message.model.device.class%'
Następnie wartość App\Ptrio\MessageBundle\Entity\Device
w argumentach usługi ptrio_message.device_manager
powinna zostać zaktualizowana na wartość odpowiadającą wcześniej utworzonemu parametrowi ptrio_message.model.device.class
.
ptrio_message.device_manager:
class: 'App\Ptrio\MessageBundle\Doctrine\DeviceManager'
arguments:
- '@doctrine.orm.entity_manager'
- '%ptrio_message.model.device.class%' # replace this line
- '@ptrio_message.device_repository'
Aktualizacja usługi managera urządzeń
Do interfejsu DeviceManagerInterface
powinna zostać deklaracja metody findDevicesByNames(array $deviceNames)
.
public function findDevicesByNames(array $deviceNames);
Do konstruktora klasy DeviceManager
należy dodać argument DeviceRepositoryInterface $repository
.
public function __construct(
// other arguments
DeviceRepositoryInterface $repository
)
Wewnątrz metody konstruktora konieczna jest zamiana pozycji $objectManager->getRepository($class);
na $this->repository = $repository;
public function __construct(
ObjectManager $objectManager,
string $class,
DeviceRepositoryInterface $repository
)
{
$this->objectManager = $objectManager;
$this->repository = $repository; // this line has changed
$metadata = $objectManager->getClassMetadata($class);
$this->class = $metadata->getName();
}
Następnie do klasy należy dodać metodę findDevicesByNames(array $deviceNames)
.
/**
* @param array $deviceNames
* @return array
*/
public function findDevicesByNames(array $deviceNames)
{
return $this->repository->findDevicesByNames($deviceNames);
}
Na koniec, wymagana jest aktualizacja pliku src/Ptrio/MessageBundle/Resources/config/services.yaml
. Należy dodać @ptrio_message.device_repository
do listy argumentów usługi ptrio_message.device_manager
.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.device_manager:
class: 'App\Ptrio\MessageBundle\Doctrine\DeviceManager'
arguments:
- '@doctrine.orm.entity_manager'
- '%ptrio_message.model.device.class%'
- '@ptrio_message.device_repository' # add this line
Aktualizacja klasy SendMessageCommand
Argument recipient
należy zamienić na device-names
. Dodatkowo konieczna jest zmiana typu argumentu na InputArgument::IS_ARRAY
.
protected function configure()
{
$this
->setDefinition([
// other arguments
new InputArgument('device-names', InputArgument::IS_ARRAY), // replaced `recipient` with `device-names`
]);
}
Zawartość metody execute(InputInterface $input, OutputInterface $output)
należy zaktualizować. Ze względu na fakt, że komenda ptrio:message:send-message
przyjmuje teraz listę urządzeń w argumencie device-names
, do obsłużenia polecenia wysyłki zostanie wykorzystana metoda managera urządzeń $this->deviceManager->findDevicesByNames(array $deviceNames)
.
protected function execute(InputInterface $input, OutputInterface $output)
{
$messageBody = $input->getArgument('body');
$deviceNames = $input->getArgument('device-names');
$devices = $this->deviceManager->findDevicesByNames($deviceNames);
foreach ($devices as $device) {
/** @var DeviceInterface $device */
$message = $this->messageManager->createMessage();
$message->setBody($messageBody);
$message->setDevice($device);
$message->setSentAt(new \DateTime('now'));
$this->messageManager->updateMessage($message);
$response = $this->client->sendMessage($messageBody, $device->getToken());
$output->writeln('Message successfully sent do device `'.$device->getName().'`.');
$output->writeln('Response: '.$response);
}
}
Wysyłka wiadomości do kilku urządzeń jednocześnie
Wiadomość można wysłać do wybranych urządzeń za pomocą polecenia piotr:message:send-message 'My Message text' iphone-piotr redmi-ewa
, gdzie pierwszy argument to treść wiadomości, a pozostałe dwa to nazwy urządzeń w bazie danych.
$ php bin/console piotr:message:send-message 'Hi all, team meeting in 15 minutes!' iphone-piotr redmi-ewa
Dla każdego zapytania do FCM zwracana jest odpowiedź z serwera. Rezultat i dodatkowe informacje są wyświetlane w narzędziu terminal.
Message successfully sent do device `iphone-piotr`.
Response: {"multicast_id":7721001967451123181,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1521029321425249%7167cd087167cd08"}]}
Message successfully sent do device `redmi-ewa`.
Response: {"multicast_id":5554868321735047816,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1521029322087330%7167cd087167cd08"}]}
Historię wiadomości można przejrzeć ponownie, aby zobaczyć dodane wpisy.
php bin/console piotr:message:list-device-messages iphone-piotr
Historia wiadomości wysłanych na urządzenie iphone-piotr
.
-------------- ------------------------------------- ---------------------
Device Name Message Body Sent At
-------------- ------------------------------------- ---------------------
iphone-piotr Remember, the meeting is at 10am! 2018-03-13 23:59
iphone-piotr Open your mail! 2018-03-13 00:09
iphone-piotr Are you not forgetting something? 2018-03-13 00:17
iphone-piotr Hi all, team meeting in 15 minutes! 2018-03-14 13:33
-------------- ------------------------------------- ---------------------
Historia wiadomości wysłanych na urządzenie redmi-ewa
.
------------- ------------------------------------- ---------------------
Device Name Message Body Sent At
------------- ------------------------------------- ---------------------
xiaomi-ewa Hi all, team meeting in 15 minutes! 2018-03-14 13:33
------------- ------------------------------------- ---------------------
Repozytorium kodu można znaleźć na https://github.com/ptrio42/message-bundle-demo-part-2.
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