create-prisma-php-app 3.5.4 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,145 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Lib\Middleware;
6
+
7
+ final class CorsMiddleware
8
+ {
9
+ /** Entry point */
10
+ public static function handle(?array $overrides = null): void
11
+ {
12
+ // Not a CORS request
13
+ $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
14
+ if ($origin === '') {
15
+ return;
16
+ }
17
+
18
+ // Resolve config (env → overrides)
19
+ $cfg = self::buildConfig($overrides);
20
+
21
+ // Not allowed? Do nothing (browser will block)
22
+ if (!self::isAllowedOrigin($origin, $cfg['allowedOrigins'])) {
23
+ return;
24
+ }
25
+
26
+ // Compute which value to send for Access-Control-Allow-Origin
27
+ // If credentials are disabled and '*' is in list, we can send '*'
28
+ $sendWildcard = (!$cfg['allowCredentials'] && self::listHasWildcard($cfg['allowedOrigins']));
29
+ $allowOriginValue = $sendWildcard ? '*' : self::normalize($origin);
30
+
31
+ // Vary for caches
32
+ header('Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers');
33
+
34
+ header('Access-Control-Allow-Origin: ' . $allowOriginValue);
35
+ if ($cfg['allowCredentials']) {
36
+ header('Access-Control-Allow-Credentials: true');
37
+ }
38
+
39
+ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
40
+ // Preflight response
41
+ $requestedHeaders = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] ?? '';
42
+ $allowedHeaders = $cfg['allowedHeaders'] !== ''
43
+ ? $cfg['allowedHeaders']
44
+ : ($requestedHeaders ?: 'Content-Type, Authorization, X-Requested-With');
45
+
46
+ header('Access-Control-Allow-Methods: ' . $cfg['allowedMethods']);
47
+ header('Access-Control-Allow-Headers: ' . $allowedHeaders);
48
+ if ($cfg['maxAge'] > 0) {
49
+ header('Access-Control-Max-Age: ' . (string) $cfg['maxAge']);
50
+ }
51
+
52
+ // Optional: Private Network Access preflights (Chrome)
53
+ if (!empty($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK'])) {
54
+ header('Access-Control-Allow-Private-Network: true');
55
+ }
56
+
57
+ http_response_code(204);
58
+ header('Content-Length: 0');
59
+ exit;
60
+ }
61
+
62
+ // Simple/actual request
63
+ if ($cfg['exposeHeaders'] !== '') {
64
+ header('Access-Control-Expose-Headers: ' . $cfg['exposeHeaders']);
65
+ }
66
+ }
67
+
68
+ /** Read env + normalize + apply overrides */
69
+ private static function buildConfig(?array $overrides): array
70
+ {
71
+ $allowed = self::parseList($_ENV['CORS_ALLOWED_ORIGINS'] ?? '');
72
+ $cfg = [
73
+ 'allowedOrigins' => $allowed,
74
+ 'allowCredentials' => filter_var($_ENV['CORS_ALLOW_CREDENTIALS'] ?? 'false', FILTER_VALIDATE_BOOLEAN),
75
+ 'allowedMethods' => $_ENV['CORS_ALLOWED_METHODS'] ?? 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
76
+ 'allowedHeaders' => trim($_ENV['CORS_ALLOWED_HEADERS'] ?? ''),
77
+ 'exposeHeaders' => trim($_ENV['CORS_EXPOSE_HEADERS'] ?? ''),
78
+ 'maxAge' => (int)($_ENV['CORS_MAX_AGE'] ?? 86400),
79
+ ];
80
+
81
+ if (is_array($overrides)) {
82
+ foreach ($overrides as $k => $v) {
83
+ if (array_key_exists($k, $cfg)) {
84
+ $cfg[$k] = $v;
85
+ }
86
+ }
87
+ }
88
+
89
+ // Normalize patterns
90
+ $cfg['allowedOrigins'] = array_map([self::class, 'normalize'], $cfg['allowedOrigins']);
91
+ return $cfg;
92
+ }
93
+
94
+ /** CSV or JSON array → array<string> */
95
+ private static function parseList(string $raw): array
96
+ {
97
+ $raw = trim($raw);
98
+ if ($raw === '') return [];
99
+
100
+ if ($raw[0] === '[') {
101
+ $arr = json_decode($raw, true);
102
+ if (is_array($arr)) {
103
+ return array_values(array_filter(array_map('strval', $arr), 'strlen'));
104
+ }
105
+ }
106
+ return array_values(array_filter(array_map('trim', explode(',', $raw)), 'strlen'));
107
+ }
108
+
109
+ private static function normalize(string $origin): string
110
+ {
111
+ return rtrim($origin, '/');
112
+ }
113
+
114
+ private static function isAllowedOrigin(string $origin, array $list): bool
115
+ {
116
+ $o = self::normalize($origin);
117
+
118
+ foreach ($list as $pattern) {
119
+ $p = self::normalize($pattern);
120
+
121
+ // literal "*"
122
+ if ($p === '*') return true;
123
+
124
+ // allow literal "null" for file:// or sandboxed if explicitly listed
125
+ if ($o === 'null' && strtolower($p) === 'null') return true;
126
+
127
+ // wildcard like https://*.example.com
128
+ if (strpos($p, '*') !== false) {
129
+ $regex = '/^' . str_replace('\*', '[^.]+', preg_quote($p, '/')) . '$/i';
130
+ if (preg_match($regex, $o)) return true;
131
+ } else {
132
+ if (strcasecmp($p, $o) === 0) return true;
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+
138
+ private static function listHasWildcard(array $list): bool
139
+ {
140
+ foreach ($list as $p) {
141
+ if (trim($p) === '*') return true;
142
+ }
143
+ return false;
144
+ }
145
+ }
@@ -2,26 +2,117 @@
2
2
 
3
3
  declare(strict_types=1);
4
4
 
5
- require __DIR__ . '/../../../vendor/autoload.php';
6
- require_once __DIR__ . '/../../../settings/paths.php';
5
+ namespace Lib\Websocket;
7
6
 
8
- use Dotenv\Dotenv;
9
-
10
- Dotenv::createImmutable(DOCUMENT_PATH)->load();
7
+ $root = dirname(__DIR__, 3);
8
+ require $root . '/vendor/autoload.php';
9
+ require_once $root . '/settings/paths.php';
11
10
 
11
+ use Dotenv\Dotenv;
12
12
  use Ratchet\Server\IoServer;
13
13
  use Ratchet\Http\HttpServer;
14
14
  use Ratchet\WebSocket\WsServer;
15
15
  use Lib\Websocket\ConnectionManager;
16
+ use Throwable;
17
+
18
+ // ── Load .env (optional) and timezone ─────────────────────────────────────────
19
+ if (file_exists(DOCUMENT_PATH . '/.env')) {
20
+ Dotenv::createImmutable(DOCUMENT_PATH)->safeLoad();
21
+ }
22
+ date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'UTC');
23
+
24
+ // ── Tiny argv parser: allows --host=0.0.0.0 --port=8080 ──────────────────────
25
+ $cli = [];
26
+ foreach ($argv ?? [] as $arg) {
27
+ if (preg_match('/^--([^=]+)=(.*)$/', $arg, $m)) {
28
+ $cli[$m[1]] = $m[2];
29
+ }
30
+ }
31
+
32
+ // ── Resolve settings (env → cli defaults) ────────────────────────────────────
33
+ $appName = $_ENV['WS_NAME'] ?? 'prisma-php-ws';
34
+ $appVer = $_ENV['WS_VERSION'] ?? '0.0.1';
35
+ $host = $cli['host'] ?? ($_ENV['WS_HOST'] ?? '127.0.0.1');
36
+ $port = (int)($cli['port'] ?? ($_ENV['WS_PORT'] ?? 8080));
37
+ $verbose = filter_var($cli['verbose'] ?? ($_ENV['WS_VERBOSE'] ?? 'true'), FILTER_VALIDATE_BOOLEAN);
38
+
39
+ // ── Console helpers ──────────────────────────────────────────────────────────
40
+ $color = static fn(string $t, string $c) => "\033[{$c}m{$t}\033[0m";
41
+ $info = static fn(string $t) => $color($t, '1;36');
42
+ $ok = static fn(string $t) => $color($t, '32');
43
+ $warn = static fn(string $t) => $color($t, '33');
44
+ $err = static fn(string $t) => $color($t, '31');
16
45
 
17
- $server = IoServer::factory(
18
- new HttpServer(
19
- new WsServer(
20
- new ConnectionManager()
21
- )
22
- ),
23
- 8080
46
+ // ── Preflight: check if port is free (nice error if not) ─────────────────────
47
+ $probe = @stream_socket_server("tcp://{$host}:{$port}", $errno, $errstr);
48
+ if ($probe === false) {
49
+ fwrite(STDERR, $err("✖ Port {$port} on {$host} is not available: {$errstr}\n"));
50
+ exit(1);
51
+ }
52
+ fclose($probe);
53
+
54
+ // ── Build app ────────────────────────────────────────────────────────────────
55
+ $manager = new ConnectionManager(); // your app component
56
+ $server = IoServer::factory(
57
+ new HttpServer(new WsServer($manager)),
58
+ $port,
59
+ $host
24
60
  );
25
61
 
26
- echo "WebSocket server started on port 8080...\n";
27
- $server->run();
62
+ $pid = getmypid() ?: 0;
63
+ $url = "ws://{$host}:{$port}";
64
+ $ts = date('Y-m-d H:i:s');
65
+
66
+ echo PHP_EOL;
67
+ echo $info("⚡ {$appName} (v{$appVer})") . PHP_EOL;
68
+ echo $warn("→ WebSocket server starting…") . PHP_EOL;
69
+ echo " Host: {$host}" . PHP_EOL;
70
+ echo " Port: {$port}" . PHP_EOL;
71
+ echo " URL: {$url}" . PHP_EOL;
72
+ echo " PID: {$pid}" . PHP_EOL;
73
+ echo " Started: {$ts}" . PHP_EOL;
74
+
75
+ // ── Graceful shutdown & periodic logs (if loop available) ────────────────────
76
+ $loop = property_exists($server, 'loop') ? $server->loop : null;
77
+ if ($loop instanceof \React\EventLoop\LoopInterface) {
78
+ // Periodic stats every 60s
79
+ $loop->addPeriodicTimer(60, function () use ($ok) {
80
+ $mem = function_exists('memory_get_usage') ? number_format(memory_get_usage(true) / 1048576, 2) . ' MB' : 'n/a';
81
+ $msg = "✓ Heartbeat — memory: {$mem}";
82
+ echo $ok($msg) . PHP_EOL;
83
+ });
84
+
85
+ // Signal handlers (needs pcntl)
86
+ if (function_exists('pcntl_signal') && method_exists($loop, 'addSignal')) {
87
+ $stop = function (int $sig) use ($warn, $loop) {
88
+ echo PHP_EOL . $warn("⏹ Caught signal {$sig}. Shutting down…") . PHP_EOL;
89
+ $loop->stop();
90
+ };
91
+ $loop->addSignal(SIGINT, $stop);
92
+ $loop->addSignal(SIGTERM, $stop);
93
+ }
94
+ }
95
+
96
+ // ── Run ──────────────────────────────────────────────────────────────────────
97
+ try {
98
+ echo $ok("✓ Listening on {$url}") . PHP_EOL;
99
+ if ($verbose) {
100
+ // Basic error/exception logging
101
+ set_error_handler(function ($severity, $message, $file, $line) use ($err) {
102
+ // Respect @-silence
103
+ if (!(error_reporting() & $severity)) return;
104
+ fwrite(STDERR, $err("PHP Error [{$severity}] {$message} @ {$file}:{$line}\n"));
105
+ });
106
+ set_exception_handler(function (Throwable $e) use ($err) {
107
+ fwrite(STDERR, $err('Uncaught Exception: ' . $e->getMessage() . "\n" . $e->getTraceAsString() . "\n"));
108
+ });
109
+ }
110
+
111
+ $server->run(); // blocks until loop->stop()
112
+
113
+ echo PHP_EOL . $ok('✔ Server stopped.') . PHP_EOL;
114
+ exit(0);
115
+ } catch (Throwable $e) {
116
+ fwrite(STDERR, $err('✖ Server error: ' . $e->getMessage()) . PHP_EOL);
117
+ exit(1);
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "3.5.4",
3
+ "version": "3.6.1",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,28 +0,0 @@
1
- @echo off
2
- set PORT=8080
3
- set "PHP_PATH=php"
4
- set "SERVER_SCRIPT_PATH= src\Lib\Websocket\websocket-server.php"
5
-
6
- echo [INFO] Checking for processes using port %PORT%...
7
- netstat -aon | findstr :%PORT%
8
-
9
- for /f "tokens=5" %%a in ('netstat -aon ^| findstr :%PORT%') do (
10
- echo [INFO] Found PID: %%a
11
- taskkill /F /PID %%a
12
- if %ERRORLEVEL% == 0 (
13
- echo [SUCCESS] Killed process %%a.
14
- ) else (
15
- echo [ERROR] Failed to kill process %%a.
16
- )
17
- )
18
-
19
- :: Wait to ensure the port is freed
20
- timeout /t 2 >nul
21
-
22
- echo [INFO] Starting WebSocket server on port %PORT%...
23
- %PHP_PATH% %SERVER_SCRIPT_PATH%
24
- if %ERRORLEVEL% == 0 (
25
- echo [SUCCESS] WebSocket server started.
26
- ) else (
27
- echo [ERROR] Failed to start WebSocket server.
28
- )