rzaripov.kz

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.