PHP服务器发送事件:从另一个文件触发自定义事件

PHP服务器发送事件:从另一个文件触发自定义事件,php,server-sent-events,Php,Server Sent Events,我需要通知客户端是否有一些客户端对数据库进行了更改。我认为它是单向连接(它只是发送事件的服务器),所以我不需要WebSoCusits。 逻辑如下: 客户机获取公共API(比如说/API.php?action=add&payload=…),并通过此获取对数据库进行更改 mysqli_result返回true后,应触发服务器发送事件,通知其他客户端(或其服务人员)数据库已更改 警告是: 我的SSE逻辑被放在一个单独的文件(/SSE.php)中,以避免API逻辑混乱,并侦听单独的端点client

我需要通知客户端是否有一些客户端对数据库进行了更改。我认为它是单向连接(它只是发送事件的服务器),所以我不需要WebSoCusits。 逻辑如下:

  • 客户机获取公共API(比如说
    /API.php?action=add&payload=…
    ),并通过此获取对数据库进行更改

  • mysqli_result
    返回true后,应触发服务器发送事件,通知其他客户端(或其服务人员)数据库已更改

警告是:

  • 我的SSE逻辑被放在一个单独的文件(
    /SSE.php
    )中,以避免API逻辑混乱,并侦听单独的端点clientside。因此,我需要从
    api.php
    触发SSE,并将结果推送到所有客户端

  • sse.php
    中,我定义了一个push函数,该函数将来自
    api.php
    的消息作为参数。不管我怎么做,这个函数从来没有被调用过

  • 我强烈希望避免创建守护进程和任何类型的无限循环。但是如果没有它们,客户端侦听器(
    eventSource
    )的连接每3秒关闭并重新打开一次

这一切的当前状态:

api.php

<?php
header("Access-Control-Allow-Origin: *");
include 'connection.php';       // mysqli credentials and connection object
include_once 'functions.php';   // logics

$action = isset($_GET['a']) ? $_GET['a'] : 'fail';
switch ($action) {
    case "add":
        $result = api__handle_add(); // adds data to DB and returns stringified success/failure result
        require "./sse.php";         // file with SSE logic with ev__ namespace
        ev__send_data($result);      // this defined function should send message to clients
        break;
    // ...
    case "testquery":
        require "./sse.php";
        ev__send_data("Test event!!! Ololo");
        break;
    case "fail": default:
        header('HTTP/1.1 403 Forbidden', true, 403);
}
<?php
header("Access-Control-Allow-Origin: *");
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");
header("Connection: keep-alive");

$_fired = false;   // flag checking for is anything happening right now

while (1) {        // the infinite loop I strongly want to get rid of
    if (!$_fired) {
        usleep(5000);  // nothing to send if nothing is fired, but I should keep the connection alive somehow
    }  else {
        // here I should call 'ev__send_data($message_from_api_php)'
        // thus I require this file to `api.php` and try to call 'ev__send_data' from there.
    }
    ob_flush();
    flush();
}

function ev__send_data(string $msg) {
    global $_fired;
    $_fired = true;
    echo "data: $msg". PHP_EOL;
    echo PHP_EOL;
    ob_flush();
    flush();
    usleep(1000);
    $_fired = false;
}
客户端实现是典型的,
onopen
onerror
onmessage
处理程序是
eventSource
的处理程序。我想我不需要把它带到这里,因为它与我在这里看到的数千个例子以及其他来源没有区别

总而言之:我想从SSE文件外部触发SSE并从外部传递数据,我想去掉
while(1){}
,只在真正发生事件时才发出事件。我遇到了成千上万的教程和主题,除了关于如何每n秒向客户端发送服务器时间之类的典型“教程案例”之外,什么都没有找到。而且我认为我不需要WebSocket(考虑到我的本地开发环境是Win10+XAMPP,我的prod环境将类似于Linux,我不认为我可以在没有第三方依赖项的情况下实现我自己的WebSocket,我不想安装这些依赖项)

我有机会吗

编辑

我发现了一个稍微不同的方法。现在,我的服务器事件生成依赖于MySQL数据库中单独的
flags
表中设置的布尔标志。当我需要发出一个事件时,API会更改definite标志(在本例中,当一个客户端提交了数据库中的一些更改时,API还会查询
标志
表,并将标志
DB_CHANGED
设置为
1
)。并且
sse.php
在无限循环的每次迭代中(每5秒)对
flags
表进行查询,并根据flags的状态发出事件,发出后,通过另一个查询将相应的flag设置为
0
。这有点管用

当前
sse.php
示例:

<?php
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");

require_once "connection.php";  // mysqli connection object
set_time_limit(600);

stream();

function stream() {
    global $connection;

    while (1) {          // oh that infinite loop
        $flags = [];     // there will be fetched flags from DB
        $flags_sqli = $connection->query("SELECT * FROM `flags`");
        while ($flag = $flags_sqli->fetch_assoc()) {
            $flags[$flag['key_']] = ev__h__to_bool_or_string($flag['value_']);
        }

        if ($flags['ON_TEST']) {
            ev__send_data("Test event!!! Ololo");
            ev__update_flag("ON_TEST", "0");
        } elseif ($flags['ON_DB_ADD']) {
            ev__handle_add();
        } else {
            echo PHP_EOL;      // keep connection alive - push single "end of line"
        }

        ob_flush();
        flush();
        sleep(5);    // interval per iteration - 5 secs
    }
}

function ev__send_data(string $msg) : void {
    echo "data: ".date("d-m-Y")." ".date("H:i:s").PHP_EOL;
    echo "data: $msg".PHP_EOL;
    echo PHP_EOL;
}

function ev__handle_add() : void {
    global $connection;
    $table = $connection->query('SELECT `value_` FROM `flags` WHERE `key_` = "LAST_ADDED_TABLE"')->fetch_row()[0];
    $query = "SELECT * FROM `banners_".$table."` WHERE `id` = (SELECT MAX(`id`) FROM `banners_".$table."`)";
    $row = $connection->query($query)->fetch_assoc();

    echo "event: db_add_row".PHP_EOL;
    echo 'data: { "manager": '.$row['manager_id'].', "banner_name": "'.$row['name'].'", "time": "'.date("d-m-Y").' '.date("H:i:s").'" }'.PHP_EOL;
    echo PHP_EOL;

    ev__update_flag("ON_DB_ADD", "0");
}

function ev__update_flag(string $key, string $value) {
    global $connection;
    $query = "UPDATE `flags` SET `value_` = '$value' WHERE `flags`.`key_` = '$key';";
    $connection->query($query);
}

function ev__h__to_bool_or_string($value) {
    return $value === "0" || $value === "1"
        ? boolval(intval($value))
        : $value;
}