Push-уведомления для iOS и Android через PHP-сервер
Push-уведомления позволяют сообщать пользователю о новом сообщении, изменении статуса заказа, событии в приложении или завершении фоновой операции. Мобильный клиент получает специальный токен устройства, а PHP-сервер передаёт его в сервис доставки уведомлений. Такой подход подходит для нативных приложений на Kotlin и Swift, а также для решений на Delphi и FireMonkey.
Для Android основным транспортом служит Firebase Cloud Messaging, или FCM. В экосистеме Apple используется Apple Push Notification service, или APNs. На практике можно построить единую серверную логику через Firebase, который умеет отправлять сообщения и на Android, и на iOS, либо разделить интеграцию и обращаться к FCM и APNs напрямую.
Архитектура обмена данными
Схема состоит из четырёх компонентов: мобильного приложения, PHP API, сервиса push-доставки и базы данных. При первом запуске приложение запрашивает разрешение на уведомления, получает device token и передаёт его серверу вместе с идентификатором пользователя, платформой и версией приложения.
PHP-сервер сохраняет токен в таблице устройств. Когда появляется событие, сервер выбирает активные токены пользователя и формирует сообщение. Затем запрос отправляется в FCM или APNs. Сервис доставки самостоятельно определяет, когда передать уведомление устройству, учитывая его подключение к сети и системные ограничения.
Для хранения токенов удобно использовать отдельную таблицу:
CREATE TABLE push_devices (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
platform VARCHAR(16) NOT NULL,
token TEXT NOT NULL,
app_version VARCHAR(32),
is_active BOOLEAN NOT NULL DEFAULT TRUE,
last_seen_at DATETIME NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);
Один пользователь может иметь несколько телефонов и компьютеров, поэтому токен нельзя хранить непосредственно в строке пользователя. При повторной регистрации нужно обновлять существующую запись, а при выходе из аккаунта деактивировать устройство или удалять связь с пользователем.
Подготовка мобильного приложения
Android-приложение подключается к Firebase через файл конфигурации проекта и библиотеку Firebase Messaging. После регистрации приложение получает FCM-токен. Он может измениться после переустановки, очистки данных, восстановления резервной копии или внутренних изменений Firebase, поэтому обработчик обновления токена должен отправлять новое значение в PHP API.
В iOS приложение регистрируется в APNs, получает системный device token, а затем передаёт его серверной части. При использовании Firebase SDK приложение может работать с FCM-токеном, который затем связывается с APNs-токеном внутри Firebase. Для показа уведомлений в iOS требуется запросить разрешения пользователя и настроить фоновые режимы, если приложению нужны тихие служебные сообщения.
API регистрации устройства должен принимать авторизованный запрос, а не доверять произвольному user_id из тела запроса. Пользователь определяется по сессии или JWT-токену:
$input = json_decode(file_get_contents('php://input'), true);
$deviceToken = trim($input['token'] ?? '');
$platform = $input['platform'] ?? '';
if ($deviceToken === '' || !in_array($platform, ['android', 'ios'], true)) {
http_response_code(422);
echo json_encode(['error' => 'Некорректные данные']);
exit;
}
// Идентификатор берётся из проверенного токена авторизации.
$userId = $authenticatedUserId;
// Сохранение или обновление устройства выполняется через подготовленный запрос.
В приложении следует разделять видимые уведомления и data-сообщения. Первые показываются системой, а вторые передают данные для обработки внутри приложения. Для iOS поведение зависит от разрешений, состояния приложения и параметров aps, поэтому серверу нельзя считать факт отправки гарантией отображения сообщения.
Отправка сообщений через Firebase
Для современного FCM используется HTTP v1 API. PHP-сервер авторизуется с помощью сервисного аккаунта Google и отправляет OAuth 2.0 access token. Файл ключа сервисного аккаунта нельзя размещать в открытом каталоге сайта или включать в репозиторий. Его хранят в защищённом хранилище или передают приложению через переменные окружения.
В запросе указываются токен устройства, заголовок, текст и дополнительные данные. Поле data удобно использовать для идентификатора заказа, типа события или ссылки на экран приложения:
function sendFcm(string $projectId, string $accessToken, string $deviceToken): string
{
$url = "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send";
$payload = [
'message' => [
'token' => $deviceToken,
'notification' => [
'title' => 'Изменение статуса',
'body' => 'Ваш заказ передан в доставку'
],
'data' => [
'event' => 'order_updated',
'order_id' => '4815'
],
'android' => [
'priority' => 'high'
],
'apns' => [
'payload' => [
'aps' => [
'sound' => 'default'
]
]
]
]
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $accessToken,
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10
]);
$result = curl_exec($ch);
curl_close($ch);
return $result ?: '';
}
Access token обычно создаётся через библиотеку Google API для PHP и кешируется на ограниченное время. При массовой рассылке запросы следует выполнять через очередь, а не внутри веб-запроса пользователя. Это сокращает задержку ответа API и позволяет повторять временно неудачные попытки.
FCM возвращает ошибки, которые нужно анализировать. Недействительный или удалённый токен следует пометить как неактивный. Ошибки авторизации и превышения лимитов требуют отдельной обработки, а временные сетевые сбои можно повторить с увеличивающейся задержкой.
Прямая работа с Apple Push Notification service
Если требуется полный контроль над iOS-интеграцией, PHP может обращаться к APNs напрямую по HTTP/2. Для этого создаётся ключ Apple Developer с идентификатором Key ID, Team ID и идентификатором приложения Bundle ID. Сервер формирует JWT, подписанный алгоритмом ES256, и передаёт его в заголовке authorization.
В запросе APNs токен устройства входит в URL, а в заголовке указываются apns-topic и тип push-сообщения. Для обычного видимого уведомления полезная нагрузка имеет такой вид:
{
"aps": {
"alert": {
"title": "Изменение статуса",
"body": "Заказ готов к получению"
},
"sound": "default"
},
"order_id": "4815"
}
HTTP-клиент PHP должен поддерживать HTTP/2 и TLS. Для рабочей среды используется адрес https://api.push.apple.com, для тестирования с sandbox-токенами — https://api.sandbox.push.apple.com. Нельзя смешивать окружения: токен, выданный для sandbox, не будет корректно работать в production.
Код ответа APNs необходимо сохранять вместе с телом ответа. Ошибка BadDeviceToken означает, что токен недействителен для выбранного окружения, а Unregistered указывает на удалённое приложение или сменившийся токен. Поле apns-expiration задаёт срок, после которого сообщение перестаёт быть актуальным, а apns-priority влияет на срочность доставки.
Надёжность, безопасность и контроль доставки
Push-сообщение не является надёжным хранилищем данных. Пользователь может отключить разрешения, телефон может быть выключен, а система — ограничить фоновую активность. Поэтому уведомление должно содержать короткий текст и идентификатор события, а приложение после открытия должно запросить актуальное состояние с PHP-сервера.
Для фоновых синхронизаций применяются тихие push-сообщения с параметром content-available. Их доставка не гарантируется, поэтому критические операции нельзя строить только на таком механизме. На сервере полезно хранить журнал отправок: время, платформу, идентификатор события, ответ провайдера и результат обработки.
Сервисный ключ FCM, приватный ключ APNs и OAuth-учётные данные должны находиться вне публичной директории. API регистрации устройства защищают HTTPS, проверкой авторизации, ограничением частоты запросов и валидацией длины токена. Токены не следует выводить в обычные логи, поскольку они относятся к идентификаторам конкретных устройств.
Перед выпуском приложения нужно проверить холодный старт, открытие нужного экрана после нажатия, повторную регистрацию токена, выход из аккаунта, удаление приложения и работу в обоих окружениях Apple. Для серверной части полезно реализовать очередь заданий, повторную отправку временных ошибок и автоматическую деактивацию токенов, которые провайдер признал недействительными. Такая архитектура одинаково применима для PHP-приложения, связанного с мобильным клиентом на Kotlin, Swift или Delphi FireMonkey.