diff --git a/application/extra/chat.php b/application/extra/chat.php new file mode 100644 index 0000000..81d8160 --- /dev/null +++ b/application/extra/chat.php @@ -0,0 +1,22 @@ + [ + 'shop' => [ + 'room_id' => 'admin', + ], + ], + 'basic' => [ + 'allocate' => 'busy', + 'auto_customer_service' => '1', + 'last_customer_service' => '1', + ], + 'system' => [ + 'inside_host' => '127.0.0.1', + 'inside_port' => '9292', + 'port' => '2222', + 'ssl' => 'reverse_proxy', + 'ssl_cert' => '', + 'ssl_key' => '', + ], +]; \ No newline at end of file diff --git a/composer.json b/composer.json index c0a92bc..1c119e9 100755 --- a/composer.json +++ b/composer.json @@ -30,7 +30,8 @@ "ext-curl": "*", "ext-pdo": "*", "ext-bcmath": "*", - "phpseclib/phpseclib": "*" + "phpseclib/phpseclib": "*", + "workerman/phpsocket.io": "^2.2" }, "config": { "preferred-install": "dist", diff --git a/public/.well-known/acme-challenge/YeuNin52_myb0ao7-Q6oVNq1WbWPxd3TYpFzLglr2E4 b/public/.well-known/acme-challenge/YeuNin52_myb0ao7-Q6oVNq1WbWPxd3TYpFzLglr2E4 new file mode 100644 index 0000000..d054c4a --- /dev/null +++ b/public/.well-known/acme-challenge/YeuNin52_myb0ao7-Q6oVNq1WbWPxd3TYpFzLglr2E4 @@ -0,0 +1 @@ +YeuNin52_myb0ao7-Q6oVNq1WbWPxd3TYpFzLglr2E4.Y-Wf0Wz0fQw2z-90a64NwX_EKH1xHYPID6S8FanXT10 \ No newline at end of file diff --git a/public/storage/decorate/20221115/028e21433349279a5bfd75394ee98fe4.png b/public/storage/decorate/20221115/028e21433349279a5bfd75394ee98fe4.png new file mode 100644 index 0000000..8d6dc6c Binary files /dev/null and b/public/storage/decorate/20221115/028e21433349279a5bfd75394ee98fe4.png differ diff --git a/public/storage/decorate/20221115/045e097320773c23321a2d3a33a9da0b.png b/public/storage/decorate/20221115/045e097320773c23321a2d3a33a9da0b.png new file mode 100644 index 0000000..27301ae Binary files /dev/null and b/public/storage/decorate/20221115/045e097320773c23321a2d3a33a9da0b.png differ diff --git a/public/storage/decorate/20221115/046f2fddfc4f6778ff890a62547d8a1a.png b/public/storage/decorate/20221115/046f2fddfc4f6778ff890a62547d8a1a.png new file mode 100644 index 0000000..ca7f77d Binary files /dev/null and b/public/storage/decorate/20221115/046f2fddfc4f6778ff890a62547d8a1a.png differ diff --git a/public/storage/decorate/20221115/0be1df1ea8d1d248a1b8eaea3309a732.png b/public/storage/decorate/20221115/0be1df1ea8d1d248a1b8eaea3309a732.png new file mode 100644 index 0000000..43f8c69 Binary files /dev/null and b/public/storage/decorate/20221115/0be1df1ea8d1d248a1b8eaea3309a732.png differ diff --git a/public/storage/decorate/20221115/0c37315a83f9424a4717ef684984c9c0.png b/public/storage/decorate/20221115/0c37315a83f9424a4717ef684984c9c0.png new file mode 100644 index 0000000..16aeb4a Binary files /dev/null and b/public/storage/decorate/20221115/0c37315a83f9424a4717ef684984c9c0.png differ diff --git a/public/storage/decorate/20221115/1130ab158b1c31b1e1d356fa78bb5f4a.gif b/public/storage/decorate/20221115/1130ab158b1c31b1e1d356fa78bb5f4a.gif new file mode 100644 index 0000000..554d226 Binary files /dev/null and b/public/storage/decorate/20221115/1130ab158b1c31b1e1d356fa78bb5f4a.gif differ diff --git a/public/storage/decorate/20221115/113eb988efc1fe17cb90cdf5311e5b63.png b/public/storage/decorate/20221115/113eb988efc1fe17cb90cdf5311e5b63.png new file mode 100644 index 0000000..5b7c39a Binary files /dev/null and b/public/storage/decorate/20221115/113eb988efc1fe17cb90cdf5311e5b63.png differ diff --git a/public/storage/decorate/20221115/18b5f444e19f31e421d0485d45befeb8.gif b/public/storage/decorate/20221115/18b5f444e19f31e421d0485d45befeb8.gif new file mode 100644 index 0000000..16fb35f Binary files /dev/null and b/public/storage/decorate/20221115/18b5f444e19f31e421d0485d45befeb8.gif differ diff --git a/public/storage/decorate/20221115/215f0aa658e271b3018f2d421bea694f.png b/public/storage/decorate/20221115/215f0aa658e271b3018f2d421bea694f.png new file mode 100644 index 0000000..c47094f Binary files /dev/null and b/public/storage/decorate/20221115/215f0aa658e271b3018f2d421bea694f.png differ diff --git a/public/storage/decorate/20221115/22a4bf60a2cf92bd6d2f300914673d21.png b/public/storage/decorate/20221115/22a4bf60a2cf92bd6d2f300914673d21.png new file mode 100644 index 0000000..7df5656 Binary files /dev/null and b/public/storage/decorate/20221115/22a4bf60a2cf92bd6d2f300914673d21.png differ diff --git a/public/storage/decorate/20221115/26dff8fb21473e219c6f024fc6a5e39a.png b/public/storage/decorate/20221115/26dff8fb21473e219c6f024fc6a5e39a.png new file mode 100644 index 0000000..8979127 Binary files /dev/null and b/public/storage/decorate/20221115/26dff8fb21473e219c6f024fc6a5e39a.png differ diff --git a/public/storage/decorate/20221115/283592b4d4f74d84b530035fa7265d73.png b/public/storage/decorate/20221115/283592b4d4f74d84b530035fa7265d73.png new file mode 100644 index 0000000..8ff9553 Binary files /dev/null and b/public/storage/decorate/20221115/283592b4d4f74d84b530035fa7265d73.png differ diff --git a/public/storage/decorate/20221115/29559cc94ff33b1f39f570c5f7bc37c1.png b/public/storage/decorate/20221115/29559cc94ff33b1f39f570c5f7bc37c1.png new file mode 100644 index 0000000..0de898f Binary files /dev/null and b/public/storage/decorate/20221115/29559cc94ff33b1f39f570c5f7bc37c1.png differ diff --git a/public/storage/decorate/20221115/2a3992a220f306d129ac1659304694bd.gif b/public/storage/decorate/20221115/2a3992a220f306d129ac1659304694bd.gif new file mode 100644 index 0000000..207c799 Binary files /dev/null and b/public/storage/decorate/20221115/2a3992a220f306d129ac1659304694bd.gif differ diff --git a/public/storage/decorate/20221115/35730af2c38c4f13e83c3be53b463333.png b/public/storage/decorate/20221115/35730af2c38c4f13e83c3be53b463333.png new file mode 100644 index 0000000..de1b85a Binary files /dev/null and b/public/storage/decorate/20221115/35730af2c38c4f13e83c3be53b463333.png differ diff --git a/public/storage/decorate/20221115/3c7eb06563d1cf28b4b34bb0d1647659.png b/public/storage/decorate/20221115/3c7eb06563d1cf28b4b34bb0d1647659.png new file mode 100644 index 0000000..b067ec3 Binary files /dev/null and b/public/storage/decorate/20221115/3c7eb06563d1cf28b4b34bb0d1647659.png differ diff --git a/public/storage/decorate/20221115/3fbe0412147a5d7f1c3116219ed39d32.png b/public/storage/decorate/20221115/3fbe0412147a5d7f1c3116219ed39d32.png new file mode 100644 index 0000000..9007789 Binary files /dev/null and b/public/storage/decorate/20221115/3fbe0412147a5d7f1c3116219ed39d32.png differ diff --git a/public/storage/decorate/20221115/408f106f7e8d709156a45b6a96ea6d9c.png b/public/storage/decorate/20221115/408f106f7e8d709156a45b6a96ea6d9c.png new file mode 100644 index 0000000..4a4a04b Binary files /dev/null and b/public/storage/decorate/20221115/408f106f7e8d709156a45b6a96ea6d9c.png differ diff --git a/public/storage/decorate/20221115/4782356b4587dc4f4a218f2540a0bafc.png b/public/storage/decorate/20221115/4782356b4587dc4f4a218f2540a0bafc.png new file mode 100644 index 0000000..ebe579f Binary files /dev/null and b/public/storage/decorate/20221115/4782356b4587dc4f4a218f2540a0bafc.png differ diff --git a/public/storage/decorate/20221115/495059abd025e7bb657c5983e3b30a52.png b/public/storage/decorate/20221115/495059abd025e7bb657c5983e3b30a52.png new file mode 100644 index 0000000..87903ec Binary files /dev/null and b/public/storage/decorate/20221115/495059abd025e7bb657c5983e3b30a52.png differ diff --git a/public/storage/decorate/20221115/4a9bde33fd13459833431db72282e91c.png b/public/storage/decorate/20221115/4a9bde33fd13459833431db72282e91c.png new file mode 100644 index 0000000..4ab380c Binary files /dev/null and b/public/storage/decorate/20221115/4a9bde33fd13459833431db72282e91c.png differ diff --git a/public/storage/decorate/20221115/4ce2fdff833f8a37de9a6d760deee994.gif b/public/storage/decorate/20221115/4ce2fdff833f8a37de9a6d760deee994.gif new file mode 100644 index 0000000..74a62e9 Binary files /dev/null and b/public/storage/decorate/20221115/4ce2fdff833f8a37de9a6d760deee994.gif differ diff --git a/public/storage/decorate/20221115/50e077b0a5df1c48dcd9be4e47b03324.png b/public/storage/decorate/20221115/50e077b0a5df1c48dcd9be4e47b03324.png new file mode 100644 index 0000000..3fe104a Binary files /dev/null and b/public/storage/decorate/20221115/50e077b0a5df1c48dcd9be4e47b03324.png differ diff --git a/public/storage/decorate/20221115/58fe6c2a400d6d18a43949f3d8c58021.png b/public/storage/decorate/20221115/58fe6c2a400d6d18a43949f3d8c58021.png new file mode 100644 index 0000000..a832f86 Binary files /dev/null and b/public/storage/decorate/20221115/58fe6c2a400d6d18a43949f3d8c58021.png differ diff --git a/public/storage/decorate/20221115/5cb05c3cb99058ad38477205f9ecdcbb.png b/public/storage/decorate/20221115/5cb05c3cb99058ad38477205f9ecdcbb.png new file mode 100644 index 0000000..7eae3ca Binary files /dev/null and b/public/storage/decorate/20221115/5cb05c3cb99058ad38477205f9ecdcbb.png differ diff --git a/public/storage/decorate/20221115/629ac892b94afeb3bd547c9f6d53e432.png b/public/storage/decorate/20221115/629ac892b94afeb3bd547c9f6d53e432.png new file mode 100644 index 0000000..9b61e64 Binary files /dev/null and b/public/storage/decorate/20221115/629ac892b94afeb3bd547c9f6d53e432.png differ diff --git a/public/storage/decorate/20221115/63cbe4b8088a28a129923b65f412fcb2.png b/public/storage/decorate/20221115/63cbe4b8088a28a129923b65f412fcb2.png new file mode 100644 index 0000000..693a733 Binary files /dev/null and b/public/storage/decorate/20221115/63cbe4b8088a28a129923b65f412fcb2.png differ diff --git a/public/storage/decorate/20221115/6586d6090fca4fab8fecbaeb53cb4656.png b/public/storage/decorate/20221115/6586d6090fca4fab8fecbaeb53cb4656.png new file mode 100644 index 0000000..7d46da8 Binary files /dev/null and b/public/storage/decorate/20221115/6586d6090fca4fab8fecbaeb53cb4656.png differ diff --git a/public/storage/decorate/20221115/6bfd03d0ad7f3d7f6ba7494c903cdc0c.png b/public/storage/decorate/20221115/6bfd03d0ad7f3d7f6ba7494c903cdc0c.png new file mode 100644 index 0000000..fec3ae0 Binary files /dev/null and b/public/storage/decorate/20221115/6bfd03d0ad7f3d7f6ba7494c903cdc0c.png differ diff --git a/public/storage/decorate/20221115/6d0a8c85ba41464b5493226c91c72459.png b/public/storage/decorate/20221115/6d0a8c85ba41464b5493226c91c72459.png new file mode 100644 index 0000000..fd9fcbf Binary files /dev/null and b/public/storage/decorate/20221115/6d0a8c85ba41464b5493226c91c72459.png differ diff --git a/public/storage/decorate/20221115/6e30b6357d3285d7d007ac308e7b2b12.png b/public/storage/decorate/20221115/6e30b6357d3285d7d007ac308e7b2b12.png new file mode 100644 index 0000000..4275556 Binary files /dev/null and b/public/storage/decorate/20221115/6e30b6357d3285d7d007ac308e7b2b12.png differ diff --git a/public/storage/decorate/20221115/70845d2cb5fc68882b27ad3de9a100e0.png b/public/storage/decorate/20221115/70845d2cb5fc68882b27ad3de9a100e0.png new file mode 100644 index 0000000..2152b96 Binary files /dev/null and b/public/storage/decorate/20221115/70845d2cb5fc68882b27ad3de9a100e0.png differ diff --git a/public/storage/decorate/20221115/76574bb482da1fd62539c0253f0152d6.png b/public/storage/decorate/20221115/76574bb482da1fd62539c0253f0152d6.png new file mode 100644 index 0000000..3d7f330 Binary files /dev/null and b/public/storage/decorate/20221115/76574bb482da1fd62539c0253f0152d6.png differ diff --git a/public/storage/decorate/20221115/7e7da5a9fe78d731c217f14922b627bc.png b/public/storage/decorate/20221115/7e7da5a9fe78d731c217f14922b627bc.png new file mode 100644 index 0000000..4dd70c6 Binary files /dev/null and b/public/storage/decorate/20221115/7e7da5a9fe78d731c217f14922b627bc.png differ diff --git a/public/storage/decorate/20221115/85e17aecf52b9f367353ceef450d18d0.png b/public/storage/decorate/20221115/85e17aecf52b9f367353ceef450d18d0.png new file mode 100644 index 0000000..860c115 Binary files /dev/null and b/public/storage/decorate/20221115/85e17aecf52b9f367353ceef450d18d0.png differ diff --git a/public/storage/decorate/20221115/92bf692d57b8fc2e76815ce6627ef1f9.png b/public/storage/decorate/20221115/92bf692d57b8fc2e76815ce6627ef1f9.png new file mode 100644 index 0000000..a3999e7 Binary files /dev/null and b/public/storage/decorate/20221115/92bf692d57b8fc2e76815ce6627ef1f9.png differ diff --git a/public/storage/decorate/20221115/9464fe770d388c7982df73b2d1b1d457.png b/public/storage/decorate/20221115/9464fe770d388c7982df73b2d1b1d457.png new file mode 100644 index 0000000..4f2af59 Binary files /dev/null and b/public/storage/decorate/20221115/9464fe770d388c7982df73b2d1b1d457.png differ diff --git a/public/storage/decorate/20221115/94eb324f16b6b48e65c4ea1cf7d3c1fd.png b/public/storage/decorate/20221115/94eb324f16b6b48e65c4ea1cf7d3c1fd.png new file mode 100644 index 0000000..63b52e2 Binary files /dev/null and b/public/storage/decorate/20221115/94eb324f16b6b48e65c4ea1cf7d3c1fd.png differ diff --git a/public/storage/decorate/20221115/968a4300d7b2e14794cdd11e140d4c68.png b/public/storage/decorate/20221115/968a4300d7b2e14794cdd11e140d4c68.png new file mode 100644 index 0000000..b12bb5a Binary files /dev/null and b/public/storage/decorate/20221115/968a4300d7b2e14794cdd11e140d4c68.png differ diff --git a/public/storage/decorate/20221115/9918d064eefa8fb1fda5715db2c248ab.png b/public/storage/decorate/20221115/9918d064eefa8fb1fda5715db2c248ab.png new file mode 100644 index 0000000..7a82cb5 Binary files /dev/null and b/public/storage/decorate/20221115/9918d064eefa8fb1fda5715db2c248ab.png differ diff --git a/public/storage/decorate/20221115/9ff8442f8cda57f88aac61059d7d3f21.png b/public/storage/decorate/20221115/9ff8442f8cda57f88aac61059d7d3f21.png new file mode 100644 index 0000000..2fea966 Binary files /dev/null and b/public/storage/decorate/20221115/9ff8442f8cda57f88aac61059d7d3f21.png differ diff --git a/public/storage/decorate/20221115/a44ee2a893e6b296efcd167fe0dc246d.png b/public/storage/decorate/20221115/a44ee2a893e6b296efcd167fe0dc246d.png new file mode 100644 index 0000000..3ad9a2a Binary files /dev/null and b/public/storage/decorate/20221115/a44ee2a893e6b296efcd167fe0dc246d.png differ diff --git a/public/storage/decorate/20221115/a83953b2f0db3f387584a78a041fccbf.png b/public/storage/decorate/20221115/a83953b2f0db3f387584a78a041fccbf.png new file mode 100644 index 0000000..112eec2 Binary files /dev/null and b/public/storage/decorate/20221115/a83953b2f0db3f387584a78a041fccbf.png differ diff --git a/public/storage/decorate/20221115/ac3bdfced7c4c5f17f03b48f6d3fa3ec.png b/public/storage/decorate/20221115/ac3bdfced7c4c5f17f03b48f6d3fa3ec.png new file mode 100644 index 0000000..d5b7f9b Binary files /dev/null and b/public/storage/decorate/20221115/ac3bdfced7c4c5f17f03b48f6d3fa3ec.png differ diff --git a/public/storage/decorate/20221115/aead1d736d0cddba13ffbbb5e8603629.png b/public/storage/decorate/20221115/aead1d736d0cddba13ffbbb5e8603629.png new file mode 100644 index 0000000..0fab64f Binary files /dev/null and b/public/storage/decorate/20221115/aead1d736d0cddba13ffbbb5e8603629.png differ diff --git a/public/storage/decorate/20221115/af8b36bc0745b57e8e18dba7732631cd.png b/public/storage/decorate/20221115/af8b36bc0745b57e8e18dba7732631cd.png new file mode 100644 index 0000000..1470edf Binary files /dev/null and b/public/storage/decorate/20221115/af8b36bc0745b57e8e18dba7732631cd.png differ diff --git a/public/storage/decorate/20221115/b043e999f27c299374bd7ab19deec2f1.png b/public/storage/decorate/20221115/b043e999f27c299374bd7ab19deec2f1.png new file mode 100644 index 0000000..c8982d1 Binary files /dev/null and b/public/storage/decorate/20221115/b043e999f27c299374bd7ab19deec2f1.png differ diff --git a/public/storage/decorate/20221115/b530150a466c8cda0a4cd5b29e2c8d11.png b/public/storage/decorate/20221115/b530150a466c8cda0a4cd5b29e2c8d11.png new file mode 100644 index 0000000..9a83057 Binary files /dev/null and b/public/storage/decorate/20221115/b530150a466c8cda0a4cd5b29e2c8d11.png differ diff --git a/public/storage/decorate/20221115/b5f6e0543a90f542095b468c98b1f8cd.png b/public/storage/decorate/20221115/b5f6e0543a90f542095b468c98b1f8cd.png new file mode 100644 index 0000000..7a8dcc5 Binary files /dev/null and b/public/storage/decorate/20221115/b5f6e0543a90f542095b468c98b1f8cd.png differ diff --git a/public/storage/decorate/20221115/c0dde28052c763691491a4dd7f5f0dc7.png b/public/storage/decorate/20221115/c0dde28052c763691491a4dd7f5f0dc7.png new file mode 100644 index 0000000..b271360 Binary files /dev/null and b/public/storage/decorate/20221115/c0dde28052c763691491a4dd7f5f0dc7.png differ diff --git a/public/storage/decorate/20221115/c25fb96bad1d53e61afb25cc9fe0b499.gif b/public/storage/decorate/20221115/c25fb96bad1d53e61afb25cc9fe0b499.gif new file mode 100644 index 0000000..ce504aa Binary files /dev/null and b/public/storage/decorate/20221115/c25fb96bad1d53e61afb25cc9fe0b499.gif differ diff --git a/public/storage/decorate/20221115/c47b048175b325b1e78f837a3b696794.png b/public/storage/decorate/20221115/c47b048175b325b1e78f837a3b696794.png new file mode 100644 index 0000000..4659d8f Binary files /dev/null and b/public/storage/decorate/20221115/c47b048175b325b1e78f837a3b696794.png differ diff --git a/public/storage/decorate/20221115/c92d07a10eec8850ab9a481638e5159b.gif b/public/storage/decorate/20221115/c92d07a10eec8850ab9a481638e5159b.gif new file mode 100644 index 0000000..2f92b1d Binary files /dev/null and b/public/storage/decorate/20221115/c92d07a10eec8850ab9a481638e5159b.gif differ diff --git a/public/storage/decorate/20221115/ce9cb18e6cd8dda71287195d97fc5c2d.png b/public/storage/decorate/20221115/ce9cb18e6cd8dda71287195d97fc5c2d.png new file mode 100644 index 0000000..6785aa9 Binary files /dev/null and b/public/storage/decorate/20221115/ce9cb18e6cd8dda71287195d97fc5c2d.png differ diff --git a/public/storage/decorate/20221115/d2107905b46fb6aabd98b6d75dff0b2f.gif b/public/storage/decorate/20221115/d2107905b46fb6aabd98b6d75dff0b2f.gif new file mode 100644 index 0000000..9ef97e8 Binary files /dev/null and b/public/storage/decorate/20221115/d2107905b46fb6aabd98b6d75dff0b2f.gif differ diff --git a/public/storage/decorate/20221115/d2b5d4d6eff681076b08b493bf5a5193.png b/public/storage/decorate/20221115/d2b5d4d6eff681076b08b493bf5a5193.png new file mode 100644 index 0000000..f0616eb Binary files /dev/null and b/public/storage/decorate/20221115/d2b5d4d6eff681076b08b493bf5a5193.png differ diff --git a/public/storage/decorate/20221115/d41c873b7d96f5874083c5a9565657c9.gif b/public/storage/decorate/20221115/d41c873b7d96f5874083c5a9565657c9.gif new file mode 100644 index 0000000..eb5fd4b Binary files /dev/null and b/public/storage/decorate/20221115/d41c873b7d96f5874083c5a9565657c9.gif differ diff --git a/public/storage/decorate/20221115/d49bc66b70c240bfa399f9f414bdbefa.png b/public/storage/decorate/20221115/d49bc66b70c240bfa399f9f414bdbefa.png new file mode 100644 index 0000000..870b89d Binary files /dev/null and b/public/storage/decorate/20221115/d49bc66b70c240bfa399f9f414bdbefa.png differ diff --git a/public/storage/decorate/20221115/ddc71411f2cd9ac0daf0e93fca64e8bb.png b/public/storage/decorate/20221115/ddc71411f2cd9ac0daf0e93fca64e8bb.png new file mode 100644 index 0000000..df7afcb Binary files /dev/null and b/public/storage/decorate/20221115/ddc71411f2cd9ac0daf0e93fca64e8bb.png differ diff --git a/public/storage/decorate/20221115/e65f9b968d33f335cdff6b5e0befef8e.png b/public/storage/decorate/20221115/e65f9b968d33f335cdff6b5e0befef8e.png new file mode 100644 index 0000000..63b9839 Binary files /dev/null and b/public/storage/decorate/20221115/e65f9b968d33f335cdff6b5e0befef8e.png differ diff --git a/public/storage/decorate/20221115/ee10ab2a0282712a0c6d20e619c1b8e8.png b/public/storage/decorate/20221115/ee10ab2a0282712a0c6d20e619c1b8e8.png new file mode 100644 index 0000000..b302292 Binary files /dev/null and b/public/storage/decorate/20221115/ee10ab2a0282712a0c6d20e619c1b8e8.png differ diff --git a/public/storage/decorate/20221115/f09ef51624e48d7d8c58cd602110c46e.png b/public/storage/decorate/20221115/f09ef51624e48d7d8c58cd602110c46e.png new file mode 100644 index 0000000..c7840d0 Binary files /dev/null and b/public/storage/decorate/20221115/f09ef51624e48d7d8c58cd602110c46e.png differ diff --git a/public/storage/decorate/20221115/f6c52397b662f42a29c4e0e6244b1b16.png b/public/storage/decorate/20221115/f6c52397b662f42a29c4e0e6244b1b16.png new file mode 100644 index 0000000..d34b180 Binary files /dev/null and b/public/storage/decorate/20221115/f6c52397b662f42a29c4e0e6244b1b16.png differ diff --git a/public/storage/decorate/20221115/f930c59d338a97a158ee53cb65bde082.png b/public/storage/decorate/20221115/f930c59d338a97a158ee53cb65bde082.png new file mode 100644 index 0000000..86cacd5 Binary files /dev/null and b/public/storage/decorate/20221115/f930c59d338a97a158ee53cb65bde082.png differ diff --git a/public/storage/decorate/20221115/fd5379d520a4a31a8e58b64da3a8790d.png b/public/storage/decorate/20221115/fd5379d520a4a31a8e58b64da3a8790d.png new file mode 100644 index 0000000..4b34d35 Binary files /dev/null and b/public/storage/decorate/20221115/fd5379d520a4a31a8e58b64da3a8790d.png differ diff --git a/public/storage/decorate/20221115/fd8c70d6121f801a333262bbb1459dfd.png b/public/storage/decorate/20221115/fd8c70d6121f801a333262bbb1459dfd.png new file mode 100644 index 0000000..2f2750b Binary files /dev/null and b/public/storage/decorate/20221115/fd8c70d6121f801a333262bbb1459dfd.png differ diff --git a/public/storage/default/20230110/fb5f5d3ade8990c789217b35fba27ef8.png b/public/storage/default/20230110/fb5f5d3ade8990c789217b35fba27ef8.png new file mode 100644 index 0000000..88d6b05 Binary files /dev/null and b/public/storage/default/20230110/fb5f5d3ade8990c789217b35fba27ef8.png differ diff --git a/public/uploads/20251022/1581418ab3b7131a11d398ce7d6bcbc9.png b/public/uploads/20251022/1581418ab3b7131a11d398ce7d6bcbc9.png new file mode 100644 index 0000000..3d7e261 Binary files /dev/null and b/public/uploads/20251022/1581418ab3b7131a11d398ce7d6bcbc9.png differ diff --git a/public/uploads/20251022/1c424c60b7fa6cd7f28fbbea55682b8a.png b/public/uploads/20251022/1c424c60b7fa6cd7f28fbbea55682b8a.png new file mode 100644 index 0000000..6d37fe5 Binary files /dev/null and b/public/uploads/20251022/1c424c60b7fa6cd7f28fbbea55682b8a.png differ diff --git a/public/uploads/20251022/4566b366604fdf49215cb1fb80e37335.png b/public/uploads/20251022/4566b366604fdf49215cb1fb80e37335.png new file mode 100644 index 0000000..82f228b Binary files /dev/null and b/public/uploads/20251022/4566b366604fdf49215cb1fb80e37335.png differ diff --git a/public/uploads/20251022/4b302bd0bfd187da3d76d05e1127d18c.png b/public/uploads/20251022/4b302bd0bfd187da3d76d05e1127d18c.png new file mode 100644 index 0000000..667993e Binary files /dev/null and b/public/uploads/20251022/4b302bd0bfd187da3d76d05e1127d18c.png differ diff --git a/public/uploads/20251022/a237bec8f02040f8f3af6ec31bc60543.png b/public/uploads/20251022/a237bec8f02040f8f3af6ec31bc60543.png new file mode 100644 index 0000000..24dae57 Binary files /dev/null and b/public/uploads/20251022/a237bec8f02040f8f3af6ec31bc60543.png differ diff --git a/public/uploads/20251022/cd8919a7436ebd5d9605cd9d4a750973.png b/public/uploads/20251022/cd8919a7436ebd5d9605cd9d4a750973.png new file mode 100644 index 0000000..fc84d67 Binary files /dev/null and b/public/uploads/20251022/cd8919a7436ebd5d9605cd9d4a750973.png differ diff --git a/public/uploads/20251022/ffe24eebd854389d191c5783f99d8c43.png b/public/uploads/20251022/ffe24eebd854389d191c5783f99d8c43.png new file mode 100644 index 0000000..dea56d2 Binary files /dev/null and b/public/uploads/20251022/ffe24eebd854389d191c5783f99d8c43.png differ diff --git a/public/uploads/20251028/a38cd3b6694e1b1f409172af506452e9.png b/public/uploads/20251028/a38cd3b6694e1b1f409172af506452e9.png new file mode 100644 index 0000000..dd29d1e Binary files /dev/null and b/public/uploads/20251028/a38cd3b6694e1b1f409172af506452e9.png differ diff --git a/thinkphp/.gitignore b/thinkphp/.gitignore old mode 100755 new mode 100644 diff --git a/thinkphp/.htaccess b/thinkphp/.htaccess old mode 100755 new mode 100644 diff --git a/thinkphp/.travis.yml b/thinkphp/.travis.yml old mode 100755 new mode 100644 diff --git a/thinkphp/CONTRIBUTING.md b/thinkphp/CONTRIBUTING.md old mode 100755 new mode 100644 diff --git a/thinkphp/LICENSE.txt b/thinkphp/LICENSE.txt old mode 100755 new mode 100644 diff --git a/thinkphp/README.md b/thinkphp/README.md old mode 100755 new mode 100644 diff --git a/thinkphp/base.php b/thinkphp/base.php old mode 100755 new mode 100644 diff --git a/thinkphp/codecov.yml b/thinkphp/codecov.yml old mode 100755 new mode 100644 diff --git a/thinkphp/composer.json b/thinkphp/composer.json old mode 100755 new mode 100644 index caf6c7e..c739abd --- a/thinkphp/composer.json +++ b/thinkphp/composer.json @@ -17,7 +17,10 @@ ], "require": { "php": ">=7.1.0", - "topthink/think-installer": "~1.0" + "topthink/think-installer": "~1.0", + "ext-fileinfo": "*", + "ext-mbstring": "*", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "4.8.*", diff --git a/thinkphp/console.php b/thinkphp/console.php old mode 100755 new mode 100644 diff --git a/thinkphp/convention.php b/thinkphp/convention.php old mode 100755 new mode 100644 diff --git a/thinkphp/helper.php b/thinkphp/helper.php old mode 100755 new mode 100644 diff --git a/thinkphp/lang/zh-cn.php b/thinkphp/lang/zh-cn.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php old mode 100755 new mode 100644 index a1b1f28..8504bb3 --- a/thinkphp/library/think/App.php +++ b/thinkphp/library/think/App.php @@ -74,7 +74,7 @@ class App * @return Response * @throws Exception */ - public static function run(Request $request = null) + public static function run(?Request $request = null) { $request = is_null($request) ? Request::instance() : $request; diff --git a/thinkphp/library/think/Build.php b/thinkphp/library/think/Build.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Cache.php b/thinkphp/library/think/Cache.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Collection.php b/thinkphp/library/think/Collection.php old mode 100755 new mode 100644 index 0ff1dfc..3c1f801 --- a/thinkphp/library/think/Collection.php +++ b/thinkphp/library/think/Collection.php @@ -263,7 +263,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria * @param callable|null $callback 回调函数 * @return static */ - public function filter(callable $callback = null) + public function filter(?callable $callback = null) { return new static(array_filter($this->items, $callback ?: null)); } @@ -317,7 +317,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria * @param callable|null $callback 回调函数 * @return static */ - public function sort(callable $callback = null) + public function sort(?callable $callback = null) { $items = $this->items; $callback = $callback ?: function ($a, $b) { diff --git a/thinkphp/library/think/Config.php b/thinkphp/library/think/Config.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Console.php b/thinkphp/library/think/Console.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php old mode 100755 new mode 100644 index 77225b7..b214c48 --- a/thinkphp/library/think/Controller.php +++ b/thinkphp/library/think/Controller.php @@ -50,7 +50,7 @@ class Controller * @access public * @param Request $request Request 对象 */ - public function __construct(Request $request = null) + public function __construct(?Request $request = null) { $this->view = View::instance(Config::get('template'), Config::get('view_replace_str')); $this->request = is_null($request) ? Request::instance() : $request; diff --git a/thinkphp/library/think/Cookie.php b/thinkphp/library/think/Cookie.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Db.php b/thinkphp/library/think/Db.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Debug.php b/thinkphp/library/think/Debug.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Env.php b/thinkphp/library/think/Env.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Error.php b/thinkphp/library/think/Error.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Exception.php b/thinkphp/library/think/Exception.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/File.php b/thinkphp/library/think/File.php old mode 100755 new mode 100644 index d2ed220..052f4d1 --- a/thinkphp/library/think/File.php +++ b/thinkphp/library/think/File.php @@ -150,12 +150,19 @@ class File extends SplFileObject */ protected function checkPath($path) { - if (is_dir($path) || mkdir($path, 0755, true)) { + if (is_dir($path)) { + return true; + } + + if (@mkdir($path, 0755, true)) { + return true; + } + + if (is_dir($path)) { return true; } $this->error = ['directory {:path} creation failed', ['path' => $path]]; - return false; } diff --git a/thinkphp/library/think/Hook.php b/thinkphp/library/think/Hook.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Lang.php b/thinkphp/library/think/Lang.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Loader.php b/thinkphp/library/think/Loader.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php old mode 100755 new mode 100644 index 92aca5b..6abed0c --- a/thinkphp/library/think/Model.php +++ b/thinkphp/library/think/Model.php @@ -537,6 +537,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess list($type, $param) = explode(':', $type, 2); } switch ($type) { + case 'string': + case 'bigint': + $value = (string) $value; + break; + case 'int': case 'integer': $value = (int) $value; break; @@ -547,6 +552,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $value = (float) number_format($value, $param, '.', ''); } break; + case 'bool': case 'boolean': $value = (bool) $value; break; @@ -670,6 +676,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess list($type, $param) = explode(':', $type, 2); } switch ($type) { + case 'string': + case 'bigint': + $value = (string) $value; + break; + case 'int': case 'integer': $value = (int) $value; break; @@ -680,6 +691,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $value = (float) number_format($value, $param, '.', ''); } break; + case 'bool': case 'boolean': $value = (bool) $value; break; diff --git a/thinkphp/library/think/Paginator.php b/thinkphp/library/think/Paginator.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Process.php b/thinkphp/library/think/Process.php old mode 100755 new mode 100644 index 6f3faa3..7348be4 --- a/thinkphp/library/think/Process.php +++ b/thinkphp/library/think/Process.php @@ -123,7 +123,7 @@ class Process * @throws \RuntimeException * @api */ - public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = []) + public function __construct($commandline, $cwd = null, ?array $env = null, $input = null, $timeout = 60, array $options = []) { if (!function_exists('proc_open')) { throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php old mode 100755 new mode 100644 index 4baf026..0721333 --- a/thinkphp/library/think/Request.php +++ b/thinkphp/library/think/Request.php @@ -521,19 +521,24 @@ class Request // 获取原始请求类型 return $this->server('REQUEST_METHOD') ?: 'GET'; } elseif (!$this->method) { - if (isset($_POST[Config::get('var_method')])) { - $method = strtoupper($_POST[Config::get('var_method')]); + $varMethod = Config::get('var_method'); + if ($varMethod && isset($_POST[$varMethod])) { + $method = strtoupper($_POST[$varMethod]); if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) { $this->method = $method; $this->{$this->method}($_POST); } else { $this->method = 'POST'; } - unset($_POST[Config::get('var_method')]); - } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { - $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + unset($_POST[$varMethod]); } else { - $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; + $method = $this->server('REQUEST_METHOD') ?: 'GET'; + $httpMethodOverride = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? ''); + if ($method === 'POST' && in_array($httpMethodOverride, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) { + $this->method = $httpMethodOverride; + } else { + $this->method = $method; + } } } return $this->method; diff --git a/thinkphp/library/think/Response.php b/thinkphp/library/think/Response.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Route.php b/thinkphp/library/think/Route.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Session.php b/thinkphp/library/think/Session.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Template.php b/thinkphp/library/think/Template.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Url.php b/thinkphp/library/think/Url.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/View.php b/thinkphp/library/think/View.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/Driver.php b/thinkphp/library/think/cache/Driver.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/driver/Lite.php b/thinkphp/library/think/cache/driver/Lite.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/driver/Memcache.php b/thinkphp/library/think/cache/driver/Memcache.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/thinkphp/library/think/cache/driver/Memcached.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/driver/Redis.php b/thinkphp/library/think/cache/driver/Redis.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/driver/Sqlite.php b/thinkphp/library/think/cache/driver/Sqlite.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/driver/Wincache.php b/thinkphp/library/think/cache/driver/Wincache.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/cache/driver/Xcache.php b/thinkphp/library/think/cache/driver/Xcache.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/config/driver/Ini.php b/thinkphp/library/think/config/driver/Ini.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/config/driver/Json.php b/thinkphp/library/think/config/driver/Json.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/config/driver/Xml.php b/thinkphp/library/think/config/driver/Xml.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/Command.php b/thinkphp/library/think/console/Command.php old mode 100755 new mode 100644 index d0caad2..0f8bfd0 --- a/thinkphp/library/think/console/Command.php +++ b/thinkphp/library/think/console/Command.php @@ -72,7 +72,7 @@ class Command * 设置控制台 * @param Console $console */ - public function setConsole(Console $console = null) + public function setConsole(?Console $console = null) { $this->console = $console; } diff --git a/thinkphp/library/think/console/Input.php b/thinkphp/library/think/console/Input.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/LICENSE b/thinkphp/library/think/console/LICENSE old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/Output.php b/thinkphp/library/think/console/Output.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/bin/README.md b/thinkphp/library/think/console/bin/README.md old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/bin/hiddeninput.exe b/thinkphp/library/think/console/bin/hiddeninput.exe old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/Build.php b/thinkphp/library/think/console/command/Build.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/Clear.php b/thinkphp/library/think/console/command/Clear.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/Help.php b/thinkphp/library/think/console/command/Help.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/Lists.php b/thinkphp/library/think/console/command/Lists.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/Make.php b/thinkphp/library/think/console/command/Make.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/make/Controller.php b/thinkphp/library/think/console/command/make/Controller.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/make/Model.php b/thinkphp/library/think/console/command/make/Model.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/make/stubs/controller.plain.stub b/thinkphp/library/think/console/command/make/stubs/controller.plain.stub old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/make/stubs/controller.stub b/thinkphp/library/think/console/command/make/stubs/controller.stub old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/make/stubs/model.stub b/thinkphp/library/think/console/command/make/stubs/model.stub old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/optimize/Autoload.php b/thinkphp/library/think/console/command/optimize/Autoload.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/optimize/Config.php b/thinkphp/library/think/console/command/optimize/Config.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/optimize/Route.php b/thinkphp/library/think/console/command/optimize/Route.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/optimize/Schema.php b/thinkphp/library/think/console/command/optimize/Schema.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/input/Argument.php b/thinkphp/library/think/console/input/Argument.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/input/Definition.php b/thinkphp/library/think/console/input/Definition.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/input/Option.php b/thinkphp/library/think/console/input/Option.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/Ask.php b/thinkphp/library/think/console/output/Ask.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/Descriptor.php b/thinkphp/library/think/console/output/Descriptor.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/Formatter.php b/thinkphp/library/think/console/output/Formatter.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/Question.php b/thinkphp/library/think/console/output/Question.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/descriptor/Console.php b/thinkphp/library/think/console/output/descriptor/Console.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/driver/Buffer.php b/thinkphp/library/think/console/output/driver/Buffer.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/driver/Console.php b/thinkphp/library/think/console/output/driver/Console.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/driver/Nothing.php b/thinkphp/library/think/console/output/driver/Nothing.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/formatter/Stack.php b/thinkphp/library/think/console/output/formatter/Stack.php old mode 100755 new mode 100644 index 4864a3f..214ea3a --- a/thinkphp/library/think/console/output/formatter/Stack.php +++ b/thinkphp/library/think/console/output/formatter/Stack.php @@ -28,7 +28,7 @@ class Stack * 构造方法 * @param Style|null $emptyStyle */ - public function __construct(Style $emptyStyle = null) + public function __construct(?Style $emptyStyle = null) { $this->emptyStyle = $emptyStyle ?: new Style(); $this->reset(); @@ -57,7 +57,7 @@ class Stack * @return Style * @throws \InvalidArgumentException */ - public function pop(Style $style = null) + public function pop(?Style $style = null) { if (empty($this->styles)) { return $this->emptyStyle; diff --git a/thinkphp/library/think/console/output/formatter/Style.php b/thinkphp/library/think/console/output/formatter/Style.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/question/Choice.php b/thinkphp/library/think/console/output/question/Choice.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/output/question/Confirmation.php b/thinkphp/library/think/console/output/question/Confirmation.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/controller/Rest.php b/thinkphp/library/think/controller/Rest.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/controller/Yar.php b/thinkphp/library/think/controller/Yar.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php old mode 100755 new mode 100644 index 578cc8f..53233e9 --- a/thinkphp/library/think/db/Connection.php +++ b/thinkphp/library/think/db/Connection.php @@ -406,7 +406,7 @@ abstract class Connection * @throws PDOException * @throws \Exception */ - public function execute($sql, $bind = [], Query $query = null) + public function execute($sql, $bind = [], ?Query $query = null) { $this->initConnect(true); if (!$this->linkID) { @@ -483,7 +483,7 @@ abstract class Connection if (PDO::PARAM_STR == $type) { $value = $this->quote($value); } elseif (PDO::PARAM_INT == $type) { - $value = (float) $value; + $value = sprintf("%d", $value); } // 判断占位符 $sql = is_numeric($key) ? @@ -738,7 +738,7 @@ abstract class Connection * @param array $sqlArray SQL批处理指令 * @return boolean */ - public function batchQuery($sqlArray = [], $bind = [], Query $query = null) + public function batchQuery($sqlArray = [], $bind = [], ?Query $query = null) { if (!is_array($sqlArray)) { return false; diff --git a/thinkphp/library/think/db/Expression.php b/thinkphp/library/think/db/Expression.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php old mode 100755 new mode 100644 index 4aa74eb..89fc028 --- a/thinkphp/library/think/db/Query.php +++ b/thinkphp/library/think/db/Query.php @@ -62,7 +62,7 @@ class Query * @param Connection $connection 数据库对象实例 * @param Model $model 模型对象 */ - public function __construct(Connection $connection = null, $model = null) + public function __construct(?Connection $connection = null, $model = null) { $this->connection = $connection ?: Db::connect([], true); $this->prefix = $this->connection->getConfig('prefix'); diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/builder/Pgsql.php b/thinkphp/library/think/db/builder/Pgsql.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/builder/Sqlite.php b/thinkphp/library/think/db/builder/Sqlite.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/builder/Sqlsrv.php b/thinkphp/library/think/db/builder/Sqlsrv.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/connector/Mysql.php b/thinkphp/library/think/db/connector/Mysql.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/connector/Pgsql.php b/thinkphp/library/think/db/connector/Pgsql.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/connector/Sqlite.php b/thinkphp/library/think/db/connector/Sqlite.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/connector/Sqlsrv.php b/thinkphp/library/think/db/connector/Sqlsrv.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/connector/pgsql.sql b/thinkphp/library/think/db/connector/pgsql.sql old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/exception/BindParamException.php b/thinkphp/library/think/db/exception/BindParamException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/exception/DataNotFoundException.php b/thinkphp/library/think/db/exception/DataNotFoundException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/db/exception/ModelNotFoundException.php b/thinkphp/library/think/db/exception/ModelNotFoundException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/debug/Console.php b/thinkphp/library/think/debug/Console.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/debug/Html.php b/thinkphp/library/think/debug/Html.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/ClassNotFoundException.php b/thinkphp/library/think/exception/ClassNotFoundException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/DbException.php b/thinkphp/library/think/exception/DbException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/ErrorException.php b/thinkphp/library/think/exception/ErrorException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/Handle.php b/thinkphp/library/think/exception/Handle.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/HttpException.php b/thinkphp/library/think/exception/HttpException.php old mode 100755 new mode 100644 index 01a27fc..6f2807c --- a/thinkphp/library/think/exception/HttpException.php +++ b/thinkphp/library/think/exception/HttpException.php @@ -16,7 +16,7 @@ class HttpException extends \RuntimeException private $statusCode; private $headers; - public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) + public function __construct($statusCode, $message = null, ?\Exception $previous = null, array $headers = [], $code = 0) { $this->statusCode = $statusCode; $this->headers = $headers; diff --git a/thinkphp/library/think/exception/HttpResponseException.php b/thinkphp/library/think/exception/HttpResponseException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/PDOException.php b/thinkphp/library/think/exception/PDOException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/RouteNotFoundException.php b/thinkphp/library/think/exception/RouteNotFoundException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/TemplateNotFoundException.php b/thinkphp/library/think/exception/TemplateNotFoundException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/ThrowableError.php b/thinkphp/library/think/exception/ThrowableError.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/exception/ValidateException.php b/thinkphp/library/think/exception/ValidateException.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/log/driver/Socket.php b/thinkphp/library/think/log/driver/Socket.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/log/driver/Test.php b/thinkphp/library/think/log/driver/Test.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/Collection.php b/thinkphp/library/think/model/Collection.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/Merge.php b/thinkphp/library/think/model/Merge.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/Pivot.php b/thinkphp/library/think/model/Pivot.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/Relation.php b/thinkphp/library/think/model/Relation.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/thinkphp/library/think/model/relation/BelongsTo.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/relation/HasManyThrough.php b/thinkphp/library/think/model/relation/HasManyThrough.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/relation/HasOne.php b/thinkphp/library/think/model/relation/HasOne.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/thinkphp/library/think/model/relation/MorphTo.php old mode 100755 new mode 100644 index 7d45265..c029800 --- a/thinkphp/library/think/model/relation/MorphTo.php +++ b/thinkphp/library/think/model/relation/MorphTo.php @@ -137,10 +137,11 @@ class MorphTo extends Relation /** * 移除关联查询参数 + * @param true $option * @access public * @return $this */ - public function removeOption() + public function removeOption($option = true) { return $this; } diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/thinkphp/library/think/model/relation/OneToOne.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/paginator/driver/Bootstrap.php b/thinkphp/library/think/paginator/driver/Bootstrap.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/process/Builder.php b/thinkphp/library/think/process/Builder.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/process/Utils.php b/thinkphp/library/think/process/Utils.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/process/exception/Failed.php b/thinkphp/library/think/process/exception/Failed.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/process/exception/Timeout.php b/thinkphp/library/think/process/exception/Timeout.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/process/pipes/Pipes.php b/thinkphp/library/think/process/pipes/Pipes.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/process/pipes/Unix.php b/thinkphp/library/think/process/pipes/Unix.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/process/pipes/Windows.php b/thinkphp/library/think/process/pipes/Windows.php old mode 100755 new mode 100644 index 1b8b0d4..64aebd4 --- a/thinkphp/library/think/process/pipes/Windows.php +++ b/thinkphp/library/think/process/pipes/Windows.php @@ -49,7 +49,7 @@ class Windows extends Pipes if (is_resource($input)) { $this->input = $input; } else { - $this->inputBuffer = $input; + $this->inputBuffer = (string) $input; } } diff --git a/thinkphp/library/think/response/Json.php b/thinkphp/library/think/response/Json.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/response/Jsonp.php b/thinkphp/library/think/response/Jsonp.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/response/Redirect.php b/thinkphp/library/think/response/Redirect.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/response/View.php b/thinkphp/library/think/response/View.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/response/Xml.php b/thinkphp/library/think/response/Xml.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/session/driver/Memcache.php b/thinkphp/library/think/session/driver/Memcache.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/session/driver/Memcached.php b/thinkphp/library/think/session/driver/Memcached.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/session/driver/Redis.php b/thinkphp/library/think/session/driver/Redis.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/template/TagLib.php b/thinkphp/library/think/template/TagLib.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/template/driver/File.php b/thinkphp/library/think/template/driver/File.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/template/taglib/Cx.php b/thinkphp/library/think/template/taglib/Cx.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/view/driver/Php.php b/thinkphp/library/think/view/driver/Php.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/view/driver/Think.php b/thinkphp/library/think/view/driver/Think.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/traits/controller/Jump.php b/thinkphp/library/traits/controller/Jump.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/traits/model/SoftDelete.php b/thinkphp/library/traits/model/SoftDelete.php old mode 100755 new mode 100644 diff --git a/thinkphp/library/traits/think/Instance.php b/thinkphp/library/traits/think/Instance.php old mode 100755 new mode 100644 diff --git a/thinkphp/logo.png b/thinkphp/logo.png old mode 100755 new mode 100644 diff --git a/thinkphp/phpunit.xml b/thinkphp/phpunit.xml old mode 100755 new mode 100644 diff --git a/thinkphp/start.php b/thinkphp/start.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/.gitignore b/thinkphp/tests/.gitignore old mode 100755 new mode 100644 diff --git a/thinkphp/tests/README.md b/thinkphp/tests/README.md old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/config.php b/thinkphp/tests/application/config.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/database.php b/thinkphp/tests/application/database.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/index/controller/Index.php b/thinkphp/tests/application/index/controller/Index.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/route.php b/thinkphp/tests/application/route.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/views/display.html b/thinkphp/tests/application/views/display.html old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/views/display.phtml b/thinkphp/tests/application/views/display.phtml old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/views/extend.html b/thinkphp/tests/application/views/extend.html old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/views/extend2.html b/thinkphp/tests/application/views/extend2.html old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/views/include.html b/thinkphp/tests/application/views/include.html old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/views/include2.html b/thinkphp/tests/application/views/include2.html old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/views/layout.html b/thinkphp/tests/application/views/layout.html old mode 100755 new mode 100644 diff --git a/thinkphp/tests/application/views/layout2.html b/thinkphp/tests/application/views/layout2.html old mode 100755 new mode 100644 diff --git a/thinkphp/tests/conf/memcached.ini b/thinkphp/tests/conf/memcached.ini old mode 100755 new mode 100644 diff --git a/thinkphp/tests/conf/redis.ini b/thinkphp/tests/conf/redis.ini old mode 100755 new mode 100644 diff --git a/thinkphp/tests/conf/timezone.ini b/thinkphp/tests/conf/timezone.ini old mode 100755 new mode 100644 diff --git a/thinkphp/tests/mock.php b/thinkphp/tests/mock.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/baseTest.php b/thinkphp/tests/thinkphp/baseTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/appTest.php b/thinkphp/tests/thinkphp/library/think/appTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/behavior/One.php b/thinkphp/tests/thinkphp/library/think/behavior/One.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/behavior/Three.php b/thinkphp/tests/thinkphp/library/think/behavior/Three.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/behavior/Two.php b/thinkphp/tests/thinkphp/library/think/behavior/Two.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/buildTest.php b/thinkphp/tests/thinkphp/library/think/buildTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/cacheTestCase.php b/thinkphp/tests/thinkphp/library/think/cache/driver/cacheTestCase.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/fileTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/fileTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/liteTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/liteTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/memcacheTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/memcacheTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/memcachedTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/memcachedTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/cache/driver/redisTest.php b/thinkphp/tests/thinkphp/library/think/cache/driver/redisTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/cacheTest.php b/thinkphp/tests/thinkphp/library/think/cacheTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/config/ConfigInitTrait.php b/thinkphp/tests/thinkphp/library/think/config/ConfigInitTrait.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.ini b/thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.ini old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.json b/thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.json old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.xml b/thinkphp/tests/thinkphp/library/think/config/driver/fixtures/config.xml old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/iniTest.php b/thinkphp/tests/thinkphp/library/think/config/driver/iniTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/jsonTest.php b/thinkphp/tests/thinkphp/library/think/config/driver/jsonTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/config/driver/xmlTest.php b/thinkphp/tests/thinkphp/library/think/config/driver/xmlTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/configTest.php b/thinkphp/tests/thinkphp/library/think/configTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/controller/.gitignore b/thinkphp/tests/thinkphp/library/think/controller/.gitignore old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/controllerTest.php b/thinkphp/tests/thinkphp/library/think/controllerTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/cookieTest.php b/thinkphp/tests/thinkphp/library/think/cookieTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/db/driver/.gitignore b/thinkphp/tests/thinkphp/library/think/db/driver/.gitignore old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/dbTest.php b/thinkphp/tests/thinkphp/library/think/dbTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/debugTest.php b/thinkphp/tests/thinkphp/library/think/debugTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/exceptionTest.php b/thinkphp/tests/thinkphp/library/think/exceptionTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/hookTest.php b/thinkphp/tests/thinkphp/library/think/hookTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/lang/lang.php b/thinkphp/tests/thinkphp/library/think/lang/lang.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/langTest.php b/thinkphp/tests/thinkphp/library/think/langTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/loader/test/Hello.php b/thinkphp/tests/thinkphp/library/think/loader/test/Hello.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/loaderTest.php b/thinkphp/tests/thinkphp/library/think/loaderTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/log/driver/fileTest.php b/thinkphp/tests/thinkphp/library/think/log/driver/fileTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/logTest.php b/thinkphp/tests/thinkphp/library/think/logTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/model/.gitignore b/thinkphp/tests/thinkphp/library/think/model/.gitignore old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/paginateTest.php b/thinkphp/tests/thinkphp/library/think/paginateTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/requestTest.php b/thinkphp/tests/thinkphp/library/think/requestTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/responseTest.php b/thinkphp/tests/thinkphp/library/think/responseTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/routeTest.php b/thinkphp/tests/thinkphp/library/think/routeTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/session/.gitignore b/thinkphp/tests/thinkphp/library/think/session/.gitignore old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/sessionTest.php b/thinkphp/tests/thinkphp/library/think/sessionTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/template/driver/.gitignore b/thinkphp/tests/thinkphp/library/think/template/driver/.gitignore old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/template/taglib/cxTest.php b/thinkphp/tests/thinkphp/library/think/template/taglib/cxTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/templateTest.php b/thinkphp/tests/thinkphp/library/think/templateTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/urlTest.php b/thinkphp/tests/thinkphp/library/think/urlTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/validateTest.php b/thinkphp/tests/thinkphp/library/think/validateTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/view/driver/.gitignore b/thinkphp/tests/thinkphp/library/think/view/driver/.gitignore old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/view/theme/index/template.html b/thinkphp/tests/thinkphp/library/think/view/theme/index/template.html old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/think/viewTest.php b/thinkphp/tests/thinkphp/library/think/viewTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/traits/controller/jumpTest.php b/thinkphp/tests/thinkphp/library/traits/controller/jumpTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/traits/model/softDeleteTest.php b/thinkphp/tests/thinkphp/library/traits/model/softDeleteTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tests/thinkphp/library/traits/think/instanceTest.php b/thinkphp/tests/thinkphp/library/traits/think/instanceTest.php old mode 100755 new mode 100644 diff --git a/thinkphp/tpl/default_index.tpl b/thinkphp/tpl/default_index.tpl old mode 100755 new mode 100644 diff --git a/thinkphp/tpl/dispatch_jump.tpl b/thinkphp/tpl/dispatch_jump.tpl old mode 100755 new mode 100644 diff --git a/thinkphp/tpl/page_trace.tpl b/thinkphp/tpl/page_trace.tpl old mode 100755 new mode 100644 diff --git a/thinkphp/tpl/think_exception.tpl b/thinkphp/tpl/think_exception.tpl old mode 100755 new mode 100644 diff --git a/vendor/autoload.php b/vendor/autoload.php index 66b8843..c43180e 100755 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -2,24 +2,6 @@ // autoload.php @generated by Composer -if (PHP_VERSION_ID < 50600) { - if (!headers_sent()) { - header('HTTP/1.1 500 Internal Server Error'); - } - $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; - if (!ini_get('display_errors')) { - if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { - fwrite(STDERR, $err); - } elseif (!headers_sent()) { - echo $err; - } - } - trigger_error( - $err, - E_USER_ERROR - ); -} - require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInitf3106b6ef3260b6914241eab0bed11c1::getLoader(); diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index a72151c..247294d 100755 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -42,79 +42,30 @@ namespace Composer\Autoload; */ class ClassLoader { - /** @var \Closure(string):void */ - private static $includeFile; - - /** @var ?string */ private $vendorDir; // PSR-4 - /** - * @var array[] - * @psalm-var array> - */ private $prefixLengthsPsr4 = array(); - /** - * @var array[] - * @psalm-var array> - */ private $prefixDirsPsr4 = array(); - /** - * @var array[] - * @psalm-var array - */ private $fallbackDirsPsr4 = array(); // PSR-0 - /** - * @var array[] - * @psalm-var array> - */ private $prefixesPsr0 = array(); - /** - * @var array[] - * @psalm-var array - */ private $fallbackDirsPsr0 = array(); - /** @var bool */ private $useIncludePath = false; - - /** - * @var string[] - * @psalm-var array - */ private $classMap = array(); - - /** @var bool */ private $classMapAuthoritative = false; - - /** - * @var bool[] - * @psalm-var array - */ private $missingClasses = array(); - - /** @var ?string */ private $apcuPrefix; - /** - * @var self[] - */ private static $registeredLoaders = array(); - /** - * @param ?string $vendorDir - */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; - self::initializeIncludeClosure(); } - /** - * @return string[] - */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { @@ -124,47 +75,28 @@ class ClassLoader return array(); } - /** - * @return array[] - * @psalm-return array> - */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } - /** - * @return array[] - * @psalm-return array - */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } - /** - * @return array[] - * @psalm-return array - */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } - /** - * @return string[] Array of classname => path - * @psalm-return array - */ public function getClassMap() { return $this->classMap; } /** - * @param string[] $classMap Class to filename map - * @psalm-param array $classMap - * - * @return void + * @param array $classMap Class to filename map */ public function addClassMap(array $classMap) { @@ -179,11 +111,9 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories - * - * @return void + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories */ public function add($prefix, $paths, $prepend = false) { @@ -226,13 +156,11 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException - * - * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { @@ -276,10 +204,8 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 base directories - * - * @return void + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories */ public function set($prefix, $paths) { @@ -294,12 +220,10 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException - * - * @return void */ public function setPsr4($prefix, $paths) { @@ -319,8 +243,6 @@ class ClassLoader * Turns on searching the include path for class files. * * @param bool $useIncludePath - * - * @return void */ public function setUseIncludePath($useIncludePath) { @@ -343,8 +265,6 @@ class ClassLoader * that have not been registered with the class map. * * @param bool $classMapAuthoritative - * - * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { @@ -365,8 +285,6 @@ class ClassLoader * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix - * - * @return void */ public function setApcuPrefix($apcuPrefix) { @@ -387,8 +305,6 @@ class ClassLoader * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not - * - * @return void */ public function register($prepend = false) { @@ -408,8 +324,6 @@ class ClassLoader /** * Unregisters this instance as an autoloader. - * - * @return void */ public function unregister() { @@ -424,18 +338,15 @@ class ClassLoader * Loads the given class or interface. * * @param string $class The name of the class - * @return true|null True if loaded, null otherwise + * @return bool|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { - $includeFile = self::$includeFile; - $includeFile($file); + includeFile($file); return true; } - - return null; } /** @@ -490,11 +401,6 @@ class ClassLoader return self::$registeredLoaders; } - /** - * @param string $class - * @param string $ext - * @return string|false - */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -560,26 +466,14 @@ class ClassLoader return false; } - - /** - * @return void - */ - private static function initializeIncludeClosure() - { - if (self::$includeFile !== null) { - return; - } - - /** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - */ - self::$includeFile = \Closure::bind(static function($file) { - include $file; - }, null, null); - } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; } diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 2052022..fe69fa6 100755 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -1,396 +1,831 @@ - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ + + + + + + + + + namespace Composer; use Composer\Autoload\ClassLoader; use Composer\Semver\VersionParser; -/** - * This class is copied in every Composer installed project and available to all - * - * See also https://getcomposer.org/doc/07-runtime.md#installed-versions - * - * To require its presence, you can require `composer-runtime-api ^2.0` - * - * @final - */ + + + + + + + class InstalledVersions { - /** - * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to - * @internal - */ - private static $selfDir = null; +private static $installed = array ( + 'root' => + array ( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'aliases' => + array ( + ), + 'reference' => '5769442de60965346d4098544e88c3d20a2755fe', + 'name' => 'fastadminnet/fastadmin', + ), + 'versions' => + array ( + 'composer/pcre' => + array ( + 'pretty_version' => '3.3.2', + 'version' => '3.3.2.0', + 'aliases' => + array ( + ), + 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', + ), + 'easywechat-composer/easywechat-composer' => + array ( + 'pretty_version' => '1.4.1', + 'version' => '1.4.1.0', + 'aliases' => + array ( + ), + 'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd', + ), + 'ezyang/htmlpurifier' => + array ( + 'pretty_version' => 'v4.19.0', + 'version' => '4.19.0.0', + 'aliases' => + array ( + ), + 'reference' => 'b287d2a16aceffbf6e0295559b39662612b77fcf', + ), + 'fastadminnet/fastadmin' => + array ( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'aliases' => + array ( + ), + 'reference' => '5769442de60965346d4098544e88c3d20a2755fe', + ), + 'fastadminnet/fastadmin-addons' => + array ( + 'pretty_version' => '1.4.2', + 'version' => '1.4.2.0', + 'aliases' => + array ( + ), + 'reference' => '14af178a62fb4cc897f954fa9d7d53798ad2cf37', + ), + 'fastadminnet/fastadmin-mailer' => + array ( + 'pretty_version' => 'v2.1.1', + 'version' => '2.1.1.0', + 'aliases' => + array ( + ), + 'reference' => 'bca635ac5f564ed6688d818d215021ffb0813746', + ), + 'guzzlehttp/guzzle' => + array ( + 'pretty_version' => '7.9.2', + 'version' => '7.9.2.0', + 'aliases' => + array ( + ), + 'reference' => 'd281ed313b989f213357e3be1a179f02196ac99b', + ), + 'guzzlehttp/promises' => + array ( + 'pretty_version' => '2.3.0', + 'version' => '2.3.0.0', + 'aliases' => + array ( + ), + 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', + ), + 'guzzlehttp/psr7' => + array ( + 'pretty_version' => '2.8.0', + 'version' => '2.8.0.0', + 'aliases' => + array ( + ), + 'reference' => '21dc724a0583619cd1652f673303492272778051', + ), + 'maennchen/zipstream-php' => + array ( + 'pretty_version' => '2.4.0', + 'version' => '2.4.0.0', + 'aliases' => + array ( + ), + 'reference' => '3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3', + ), + 'markbaker/complex' => + array ( + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'aliases' => + array ( + ), + 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', + ), + 'markbaker/matrix' => + array ( + 'pretty_version' => '3.0.1', + 'version' => '3.0.1.0', + 'aliases' => + array ( + ), + 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', + ), + 'monolog/monolog' => + array ( + 'pretty_version' => '2.10.0', + 'version' => '2.10.0.0', + 'aliases' => + array ( + ), + 'reference' => '5cf826f2991858b54d5c3809bee745560a1042a7', + ), + 'myclabs/php-enum' => + array ( + 'pretty_version' => '1.8.4', + 'version' => '1.8.4.0', + 'aliases' => + array ( + ), + 'reference' => 'a867478eae49c9f59ece437ae7f9506bfaa27483', + ), + 'nelexa/zip' => + array ( + 'pretty_version' => '4.0.2', + 'version' => '4.0.2.0', + 'aliases' => + array ( + ), + 'reference' => '88a1b6549be813278ff2dd3b6b2ac188827634a7', + ), + 'overtrue/pinyin' => + array ( + 'pretty_version' => '3.0.6', + 'version' => '3.0.6.0', + 'aliases' => + array ( + ), + 'reference' => '3b781d267197b74752daa32814d3a2cf5d140779', + ), + 'overtrue/socialite' => + array ( + 'pretty_version' => '2.0.24', + 'version' => '2.0.24.0', + 'aliases' => + array ( + ), + 'reference' => 'ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec', + ), + 'overtrue/wechat' => + array ( + 'pretty_version' => '4.9.0', + 'version' => '4.9.0.0', + 'aliases' => + array ( + ), + 'reference' => '92791f5d957269c633b9aa175f842f6006f945b1', + ), + 'paragonie/constant_time_encoding' => + array ( + 'pretty_version' => 'v3.1.3', + 'version' => '3.1.3.0', + 'aliases' => + array ( + ), + 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', + ), + 'paragonie/random_compat' => + array ( + 'pretty_version' => 'v9.99.100', + 'version' => '9.99.100.0', + 'aliases' => + array ( + ), + 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', + ), + 'phpoffice/phpspreadsheet' => + array ( + 'pretty_version' => '1.30.1', + 'version' => '1.30.1.0', + 'aliases' => + array ( + ), + 'reference' => 'fa8257a579ec623473eabfe49731de5967306c4c', + ), + 'phpseclib/phpseclib' => + array ( + 'pretty_version' => '3.0.47', + 'version' => '3.0.47.0', + 'aliases' => + array ( + ), + 'reference' => '9d6ca36a6c2dd434765b1071b2644a1c683b385d', + ), + 'pimple/pimple' => + array ( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'aliases' => + array ( + ), + 'reference' => 'a94b3a4db7fb774b3d78dad2315ddc07629e1bed', + ), + 'psr/cache' => + array ( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'aliases' => + array ( + ), + 'reference' => '213f9dbc5b9bfbc4f8db86d2838dc968752ce13b', + ), + 'psr/cache-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0|2.0', + ), + ), + 'psr/container' => + array ( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'aliases' => + array ( + ), + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + ), + 'psr/event-dispatcher' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + ), + 'psr/event-dispatcher-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/http-client' => + array ( + 'pretty_version' => '1.0.3', + 'version' => '1.0.3.0', + 'aliases' => + array ( + ), + 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', + ), + 'psr/http-client-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/http-factory' => + array ( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'aliases' => + array ( + ), + 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', + ), + 'psr/http-factory-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/http-message' => + array ( + 'pretty_version' => '1.1', + 'version' => '1.1.0.0', + 'aliases' => + array ( + ), + 'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba', + ), + 'psr/http-message-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/log' => + array ( + 'pretty_version' => '1.1.4', + 'version' => '1.1.4.0', + 'aliases' => + array ( + ), + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', + ), + 'psr/log-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0.0 || 2.0.0 || 3.0.0', + ), + ), + 'psr/simple-cache' => + array ( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'aliases' => + array ( + ), + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + ), + 'psr/simple-cache-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0|2.0', + ), + ), + 'ralouphie/getallheaders' => + array ( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'aliases' => + array ( + ), + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + ), + 'symfony/cache' => + array ( + 'pretty_version' => 'v5.4.46', + 'version' => '5.4.46.0', + 'aliases' => + array ( + ), + 'reference' => '0fe08ee32cec2748fbfea10c52d3ee02049e0f6b', + ), + 'symfony/cache-contracts' => + array ( + 'pretty_version' => 'v2.5.4', + 'version' => '2.5.4.0', + 'aliases' => + array ( + ), + 'reference' => '517c3a3619dadfa6952c4651767fcadffb4df65e', + ), + 'symfony/cache-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0|2.0', + ), + ), + 'symfony/deprecation-contracts' => + array ( + 'pretty_version' => 'v3.0.2', + 'version' => '3.0.2.0', + 'aliases' => + array ( + ), + 'reference' => '26954b3d62a6c5fd0ea8a2a00c0353a14978d05c', + ), + 'symfony/event-dispatcher' => + array ( + 'pretty_version' => 'v5.4.45', + 'version' => '5.4.45.0', + 'aliases' => + array ( + ), + 'reference' => '72982eb416f61003e9bb6e91f8b3213600dcf9e9', + ), + 'symfony/event-dispatcher-contracts' => + array ( + 'pretty_version' => 'v3.0.2', + 'version' => '3.0.2.0', + 'aliases' => + array ( + ), + 'reference' => '7bc61cc2db649b4637d331240c5346dcc7708051', + ), + 'symfony/event-dispatcher-implementation' => + array ( + 'provided' => + array ( + 0 => '2.0', + ), + ), + 'symfony/finder' => + array ( + 'pretty_version' => 'v6.0.19', + 'version' => '6.0.19.0', + 'aliases' => + array ( + ), + 'reference' => '5cc9cac6586fc0c28cd173780ca696e419fefa11', + ), + 'symfony/http-foundation' => + array ( + 'pretty_version' => 'v5.4.48', + 'version' => '5.4.48.0', + 'aliases' => + array ( + ), + 'reference' => '3f38b8af283b830e1363acd79e5bc3412d055341', + ), + 'symfony/polyfill-mbstring' => + array ( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'aliases' => + array ( + ), + 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', + ), + 'symfony/polyfill-php73' => + array ( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'aliases' => + array ( + ), + 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', + ), + 'symfony/polyfill-php80' => + array ( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'aliases' => + array ( + ), + 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', + ), + 'symfony/psr-http-message-bridge' => + array ( + 'pretty_version' => 'v2.3.1', + 'version' => '2.3.1.0', + 'aliases' => + array ( + ), + 'reference' => '581ca6067eb62640de5ff08ee1ba6850a0ee472e', + ), + 'symfony/service-contracts' => + array ( + 'pretty_version' => 'v3.0.2', + 'version' => '3.0.2.0', + 'aliases' => + array ( + ), + 'reference' => 'd78d39c1599bd1188b8e26bb341da52c3c6d8a66', + ), + 'symfony/var-exporter' => + array ( + 'pretty_version' => 'v6.0.19', + 'version' => '6.0.19.0', + 'aliases' => + array ( + ), + 'reference' => 'df56f53818c2d5d9f683f4ad2e365ba73a3b69d2', + ), + 'topthink/framework' => + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + 0 => '9999999-dev', + ), + 'reference' => '9a2e7c2a1b6302afb61035c99c85bf0cfe0c52ec', + ), + 'topthink/think-captcha' => + array ( + 'pretty_version' => 'v1.0.9', + 'version' => '1.0.9.0', + 'aliases' => + array ( + ), + 'reference' => '9be9dd7e61c7fa3c478c4b92910d7230b94d0d23', + ), + 'topthink/think-helper' => + array ( + 'pretty_version' => 'v1.0.7', + 'version' => '1.0.7.0', + 'aliases' => + array ( + ), + 'reference' => '5f92178606c8ce131d36b37a57c58eb71e55f019', + ), + 'topthink/think-installer' => + array ( + 'pretty_version' => 'v1.0.14', + 'version' => '1.0.14.0', + 'aliases' => + array ( + ), + 'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1', + ), + 'topthink/think-queue' => + array ( + 'pretty_version' => 'v1.1.6', + 'version' => '1.1.6.0', + 'aliases' => + array ( + ), + 'reference' => '250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245', + ), + 'workerman/channel' => + array ( + 'pretty_version' => 'v1.2.3', + 'version' => '1.2.3.0', + 'aliases' => + array ( + ), + 'reference' => '5edb0008eae35bf2da7218d911042abd23aa4370', + ), + 'workerman/phpsocket.io' => + array ( + 'pretty_version' => 'v2.2.0', + 'version' => '2.2.0.0', + 'aliases' => + array ( + ), + 'reference' => '0ba306b380e016f447f9860db95fcc1c7553fb91', + ), + 'workerman/workerman' => + array ( + 'pretty_version' => 'v4.2.1', + 'version' => '4.2.1.0', + 'aliases' => + array ( + ), + 'reference' => 'cafb5a43d93d7d30a16b32a57948581cca993562', + ), + ), +); +private static $canGetVendors; +private static $installedByVendor = array(); - /** - * @var mixed[]|null - * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null - */ - private static $installed; - /** - * @var bool - */ - private static $installedIsLocalDir; - /** - * @var bool|null - */ - private static $canGetVendors; - /** - * @var array[] - * @psalm-var array}> - */ - private static $installedByVendor = array(); - /** - * Returns a list of all package names which are present, either by being installed, replaced or provided - * - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackages() - { - $packages = array(); - foreach (self::getInstalled() as $installed) { - $packages[] = array_keys($installed['versions']); - } - if (1 === \count($packages)) { - return $packages[0]; - } - return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); - } - - /** - * Returns a list of all package names with a specific type e.g. 'library' - * - * @param string $type - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackagesByType($type) - { - $packagesByType = array(); - - foreach (self::getInstalled() as $installed) { - foreach ($installed['versions'] as $name => $package) { - if (isset($package['type']) && $package['type'] === $type) { - $packagesByType[] = $name; - } - } - } - - return $packagesByType; - } - - /** - * Checks whether the given package is installed - * - * This also returns true if the package name is provided or replaced by another package - * - * @param string $packageName - * @param bool $includeDevRequirements - * @return bool - */ - public static function isInstalled($packageName, $includeDevRequirements = true) - { - foreach (self::getInstalled() as $installed) { - if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; - } - } - - return false; - } - - /** - * Checks whether the given package satisfies a version constraint - * - * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: - * - * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') - * - * @param VersionParser $parser Install composer/semver to have access to this class and functionality - * @param string $packageName - * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package - * @return bool - */ - public static function satisfies(VersionParser $parser, $packageName, $constraint) - { - $constraint = $parser->parseConstraints((string) $constraint); - $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); - - return $provided->matches($constraint); - } - - /** - * Returns a version constraint representing all the range(s) which are installed for a given package - * - * It is easier to use this via isInstalled() with the $constraint argument if you need to check - * whether a given version of a package is installed, and not just whether it exists - * - * @param string $packageName - * @return string Version constraint usable with composer/semver - */ - public static function getVersionRanges($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - $ranges = array(); - if (isset($installed['versions'][$packageName]['pretty_version'])) { - $ranges[] = $installed['versions'][$packageName]['pretty_version']; - } - if (array_key_exists('aliases', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); - } - if (array_key_exists('replaced', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); - } - if (array_key_exists('provided', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); - } - - return implode(' || ', $ranges); - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['version'])) { - return null; - } - - return $installed['versions'][$packageName]['version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getPrettyVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['pretty_version'])) { - return null; - } - - return $installed['versions'][$packageName]['pretty_version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference - */ - public static function getReference($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['reference'])) { - return null; - } - - return $installed['versions'][$packageName]['reference']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. - */ - public static function getInstallPath($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @return array - * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} - */ - public static function getRootPackage() - { - $installed = self::getInstalled(); - - return $installed[0]['root']; - } - - /** - * Returns the raw installed.php data for custom implementations - * - * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. - * @return array[] - * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} - */ - public static function getRawData() - { - @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = include __DIR__ . '/installed.php'; - } else { - self::$installed = array(); - } - } - - return self::$installed; - } - - /** - * Returns the raw data of all installed.php which are currently loaded for custom implementations - * - * @return array[] - * @psalm-return list}> - */ - public static function getAllRawData() - { - return self::getInstalled(); - } - - /** - * Lets you reload the static array from another file - * - * This is only useful for complex integrations in which a project needs to use - * this class but then also needs to execute another project's autoloader in process, - * and wants to ensure both projects have access to their version of installed.php. - * - * A typical case would be PHPUnit, where it would need to make sure it reads all - * the data it needs from this class, then call reload() with - * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure - * the project in which it runs can then also use this class safely, without - * interference between PHPUnit's dependencies and the project's dependencies. - * - * @param array[] $data A vendor/composer/installed.php data set - * @return void - * - * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data - */ - public static function reload($data) - { - self::$installed = $data; - self::$installedByVendor = array(); - - // when using reload, we disable the duplicate protection to ensure that self::$installed data is - // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, - // so we have to assume it does not, and that may result in duplicate data being returned when listing - // all installed packages for example - self::$installedIsLocalDir = false; - } - - /** - * @return string - */ - private static function getSelfDir() - { - if (self::$selfDir === null) { - self::$selfDir = strtr(__DIR__, '\\', '/'); - } - - return self::$selfDir; - } - - /** - * @return array[] - * @psalm-return list}> - */ - private static function getInstalled() - { - if (null === self::$canGetVendors) { - self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); - } - - $installed = array(); - $copiedLocalDir = false; - - if (self::$canGetVendors) { - $selfDir = self::getSelfDir(); - foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { - $vendorDir = strtr($vendorDir, '\\', '/'); - if (isset(self::$installedByVendor[$vendorDir])) { - $installed[] = self::$installedByVendor[$vendorDir]; - } elseif (is_file($vendorDir.'/composer/installed.php')) { - /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ - $required = require $vendorDir.'/composer/installed.php'; - self::$installedByVendor[$vendorDir] = $required; - $installed[] = $required; - if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { - self::$installed = $required; - self::$installedIsLocalDir = true; - } - } - if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { - $copiedLocalDir = true; - } - } - } - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ - $required = require __DIR__ . '/installed.php'; - self::$installed = $required; - } else { - self::$installed = array(); - } - } - - if (self::$installed !== array() && !$copiedLocalDir) { - $installed[] = self::$installed; - } - - return $installed; - } +public static function getInstalledPackages() +{ +$packages = array(); +foreach (self::getInstalled() as $installed) { +$packages[] = array_keys($installed['versions']); +} + +if (1 === \count($packages)) { +return $packages[0]; +} + +return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); +} + + + + + + + + + +public static function isInstalled($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (isset($installed['versions'][$packageName])) { +return true; +} +} + +return false; +} + + + + + + + + + + + + + + +public static function satisfies(VersionParser $parser, $packageName, $constraint) +{ +$constraint = $parser->parseConstraints($constraint); +$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + +return $provided->matches($constraint); +} + + + + + + + + + + +public static function getVersionRanges($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +$ranges = array(); +if (isset($installed['versions'][$packageName]['pretty_version'])) { +$ranges[] = $installed['versions'][$packageName]['pretty_version']; +} +if (array_key_exists('aliases', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); +} +if (array_key_exists('replaced', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); +} +if (array_key_exists('provided', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); +} + +return implode(' || ', $ranges); +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getVersion($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['version'])) { +return null; +} + +return $installed['versions'][$packageName]['version']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getPrettyVersion($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['pretty_version'])) { +return null; +} + +return $installed['versions'][$packageName]['pretty_version']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getReference($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['reference'])) { +return null; +} + +return $installed['versions'][$packageName]['reference']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getRootPackage() +{ +$installed = self::getInstalled(); + +return $installed[0]['root']; +} + + + + + + + + +public static function getRawData() +{ +@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + +return self::$installed; +} + + + + + + + +public static function getAllRawData() +{ +return self::getInstalled(); +} + + + + + + + + + + + + + + + + + + + +public static function reload($data) +{ +self::$installed = $data; +self::$installedByVendor = array(); +} + + + + + +private static function getInstalled() +{ +if (null === self::$canGetVendors) { +self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); +} + +$installed = array(); + +if (self::$canGetVendors) { +foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { +if (isset(self::$installedByVendor[$vendorDir])) { +$installed[] = self::$installedByVendor[$vendorDir]; +} elseif (is_file($vendorDir.'/composer/installed.php')) { +$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; +} +} +} + +$installed[] = self::$installed; + +return $installed; +} } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 016c54b..ec20b9a 100755 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -2,7 +2,7 @@ // autoload_classmap.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php index 84f457c..d517fc2 100755 --- a/vendor/composer/autoload_files.php +++ b/vendor/composer/autoload_files.php @@ -2,21 +2,22 @@ // autoload_files.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', - '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php', - 'f7e3d8cd19cf23ce3883a6a51d791b77' => $vendorDir . '/fastadminnet/fastadmin-addons/src/common.php', 'f0e7e63bbb278a92db02393536748c5f' => $vendorDir . '/overtrue/wechat/src/Kernel/Support/Helpers.php', '6747f579ad6817f318cc3a7e7a0abb93' => $vendorDir . '/overtrue/wechat/src/Kernel/Helpers.php', + 'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php', '1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php', 'cc56288302d9df745d97c934d6a6e5f0' => $vendorDir . '/topthink/think-queue/src/common.php', + 'f7e3d8cd19cf23ce3883a6a51d791b77' => $vendorDir . '/fastadminnet/fastadmin-addons/src/common.php', ); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php index be11dd8..93ac1e9 100755 --- a/vendor/composer/autoload_namespaces.php +++ b/vendor/composer/autoload_namespaces.php @@ -2,7 +2,7 @@ // autoload_namespaces.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 0d2dd52..2e82543 100755 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -2,16 +2,18 @@ // autoload_psr4.php @generated by Composer -$vendorDir = dirname(__DIR__); +$vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( 'think\\helper\\' => array($vendorDir . '/topthink/think-helper/src'), 'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'), 'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'), - 'think\\' => array($vendorDir . '/fastadminnet/fastadmin-addons/src', $baseDir . '/thinkphp/library/think', $vendorDir . '/topthink/think-queue/src'), + 'think\\' => array($baseDir . '/thinkphp/library/think', $vendorDir . '/topthink/think-queue/src', $vendorDir . '/fastadminnet/fastadmin-addons/src'), + 'phpseclib3\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'), 'addons\\' => array($baseDir . '/addons'), 'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'), + 'Workerman\\' => array($vendorDir . '/workerman/workerman'), 'Tx\\' => array($vendorDir . '/fastadminnet/fastadmin-mailer/src'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), @@ -27,13 +29,15 @@ return array( 'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'), 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), - 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'), 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'PhpZip\\' => array($vendorDir . '/nelexa/zip/src'), 'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'), + 'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'), + 'PHPSocketIO\\' => array($vendorDir . '/workerman/phpsocket.io/src'), 'Overtrue\\Socialite\\' => array($vendorDir . '/overtrue/socialite/src'), 'Overtrue\\Pinyin\\' => array($vendorDir . '/overtrue/pinyin/src'), 'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'), @@ -46,4 +50,5 @@ return array( 'EasyWeChatComposer\\' => array($vendorDir . '/easywechat-composer/easywechat-composer/src'), 'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'), 'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'), + 'Channel\\' => array($vendorDir . '/workerman/channel/src'), ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 6d7f80e..5e7805d 100755 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -25,26 +25,51 @@ class ComposerAutoloaderInitf3106b6ef3260b6914241eab0bed11c1 require __DIR__ . '/platform_check.php'; spl_autoload_register(array('ComposerAutoloaderInitf3106b6ef3260b6914241eab0bed11c1', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInitf3106b6ef3260b6914241eab0bed11c1', 'loadClassLoader')); - require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1::getInitializer($loader)); + $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } $loader->register(true); - $filesToLoad = \Composer\Autoload\ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1::$files; - $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { - if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - - require $file; - } - }, null, null); - foreach ($filesToLoad as $fileIdentifier => $file) { - $requireFile($fileIdentifier, $file); + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequiref3106b6ef3260b6914241eab0bed11c1($fileIdentifier, $file); } return $loader; } } + +function composerRequiref3106b6ef3260b6914241eab0bed11c1($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 2c95cb2..f993b86 100755 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -9,17 +9,18 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 public static $files = array ( '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', - '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php', - 'f7e3d8cd19cf23ce3883a6a51d791b77' => __DIR__ . '/..' . '/fastadminnet/fastadmin-addons/src/common.php', 'f0e7e63bbb278a92db02393536748c5f' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Support/Helpers.php', '6747f579ad6817f318cc3a7e7a0abb93' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Helpers.php', + 'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php', '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php', 'cc56288302d9df745d97c934d6a6e5f0' => __DIR__ . '/..' . '/topthink/think-queue/src/common.php', + 'f7e3d8cd19cf23ce3883a6a51d791b77' => __DIR__ . '/..' . '/fastadminnet/fastadmin-addons/src/common.php', ); public static $prefixLengthsPsr4 = array ( @@ -30,6 +31,10 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 'think\\captcha\\' => 14, 'think\\' => 6, ), + 'p' => + array ( + 'phpseclib3\\' => 11, + ), 'a' => array ( 'addons\\' => 7, @@ -38,6 +43,10 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 array ( 'ZipStream\\' => 10, ), + 'W' => + array ( + 'Workerman\\' => 10, + ), 'T' => array ( 'Tx\\' => 3, @@ -68,6 +77,8 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 'Psr\\Cache\\' => 10, 'PhpZip\\' => 7, 'PhpOffice\\PhpSpreadsheet\\' => 25, + 'ParagonIE\\ConstantTime\\' => 23, + 'PHPSocketIO\\' => 12, ), 'O' => array ( @@ -95,6 +106,7 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 array ( 'Composer\\Pcre\\' => 14, 'Complex\\' => 8, + 'Channel\\' => 8, ), ); @@ -113,9 +125,13 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 ), 'think\\' => array ( - 0 => __DIR__ . '/..' . '/fastadminnet/fastadmin-addons/src', - 1 => __DIR__ . '/../..' . '/thinkphp/library/think', - 2 => __DIR__ . '/..' . '/topthink/think-queue/src', + 0 => __DIR__ . '/../..' . '/thinkphp/library/think', + 1 => __DIR__ . '/..' . '/topthink/think-queue/src', + 2 => __DIR__ . '/..' . '/fastadminnet/fastadmin-addons/src', + ), + 'phpseclib3\\' => + array ( + 0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib', ), 'addons\\' => array ( @@ -125,6 +141,10 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 array ( 0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src', ), + 'Workerman\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/workerman', + ), 'Tx\\' => array ( 0 => __DIR__ . '/..' . '/fastadminnet/fastadmin-mailer/src', @@ -187,8 +207,8 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 ), 'Psr\\Http\\Message\\' => array ( - 0 => __DIR__ . '/..' . '/psr/http-factory/src', - 1 => __DIR__ . '/..' . '/psr/http-message/src', + 0 => __DIR__ . '/..' . '/psr/http-message/src', + 1 => __DIR__ . '/..' . '/psr/http-factory/src', ), 'Psr\\Http\\Client\\' => array ( @@ -214,6 +234,14 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 array ( 0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet', ), + 'ParagonIE\\ConstantTime\\' => + array ( + 0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src', + ), + 'PHPSocketIO\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/phpsocket.io/src', + ), 'Overtrue\\Socialite\\' => array ( 0 => __DIR__ . '/..' . '/overtrue/socialite/src', @@ -262,6 +290,10 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1 array ( 0 => __DIR__ . '/..' . '/markbaker/complex/classes/src', ), + 'Channel\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/channel/src', + ), ); public static $prefixesPsr0 = array ( diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 66e2d6e..9c3f371 100755 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -199,24 +199,30 @@ }, { "name": "fastadminnet/fastadmin-addons", - "version": "1.4.3", - "version_normalized": "1.4.3.0", + "version": "1.4.2", + "version_normalized": "1.4.2.0", "source": { "type": "git", "url": "https://github.com/fastadminnet/fastadmin-addons.git", - "reference": "b7e371254f97fae7e9232984a746ffa58b64504e" + "reference": "14af178a62fb4cc897f954fa9d7d53798ad2cf37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fastadminnet/fastadmin-addons/zipball/b7e371254f97fae7e9232984a746ffa58b64504e", - "reference": "b7e371254f97fae7e9232984a746ffa58b64504e", - "shasum": "" + "url": "https://api.github.com/repos/fastadminnet/fastadmin-addons/zipball/14af178a62fb4cc897f954fa9d7d53798ad2cf37", + "reference": "14af178a62fb4cc897f954fa9d7d53798ad2cf37", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "nelexa/zip": "^3.3 || ^4.0", - "php": ">=7.1.0" + "php": ">=7.0.0" }, - "time": "2025-06-17T03:35:34+00:00", + "time": "2025-06-03T03:09:28+00:00", "type": "library", "extra": { "think-config": { @@ -250,7 +256,7 @@ "homepage": "https://github.com/fastadminnet/fastadmin-addons", "support": { "issues": "https://github.com/fastadminnet/fastadmin-addons/issues", - "source": "https://github.com/fastadminnet/fastadmin-addons/tree/v1.4.3" + "source": "https://github.com/fastadminnet/fastadmin-addons/tree/v1.4.2" }, "install-path": "../fastadminnet/fastadmin-addons" }, @@ -314,23 +320,29 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.0", - "version_normalized": "7.10.0.0", + "version": "7.9.2", + "version_normalized": "7.9.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "shasum": "" + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -351,7 +363,7 @@ "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, - "time": "2025-08-23T22:36:01+00:00", + "time": "2024-07-24T11:22:20+00:00", "type": "library", "extra": { "bamarni-bin": { @@ -423,7 +435,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -648,39 +660,41 @@ }, { "name": "maennchen/zipstream-php", - "version": "3.1.2", - "version_normalized": "3.1.2.0", + "version": "2.4.0", + "version_normalized": "2.4.0.0", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" + "reference": "3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", - "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", - "shasum": "" + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3", + "reference": "3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "ext-mbstring": "*", - "ext-zlib": "*", - "php-64bit": "^8.2" + "myclabs/php-enum": "^1.5", + "php": "^8.0", + "psr/http-message": "^1.0" }, "require-dev": { - "brianium/paratest": "^7.7", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.16", - "guzzlehttp/guzzle": "^7.5", + "friendsofphp/php-cs-fixer": "^3.9", + "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.5", - "phpunit/phpunit": "^11.0", - "vimeo/psalm": "^6.0" + "php-coveralls/php-coveralls": "^2.4", + "phpunit/phpunit": "^8.5.8 || ^9.4.2", + "vimeo/psalm": "^5.0" }, - "suggest": { - "guzzlehttp/psr7": "^2.4", - "psr/http-message": "^2.0" - }, - "time": "2025-01-27T12:07:53+00:00", + "time": "2022-12-08T12:29:14+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -717,12 +731,16 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.4.0" }, "funding": [ { "url": "https://github.com/maennchen", "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" } ], "install-path": "../maennchen/zipstream-php" @@ -945,6 +963,78 @@ ], "install-path": "../monolog/monolog" }, + { + "name": "myclabs/php-enum", + "version": "1.8.4", + "version_normalized": "1.8.4.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "time": "2022-08-04T09:53:51+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.4" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "install-path": "../myclabs/php-enum" + }, { "name": "nelexa/zip", "version": "4.0.2", @@ -1345,18 +1435,24 @@ }, { "name": "phpoffice/phpspreadsheet", - "version": "1.30.0", - "version_normalized": "1.30.0.0", + "version": "1.30.1", + "version_normalized": "1.30.1.0", "source": { "type": "git", "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "2f39286e0136673778b7a142b3f0d141e43d1714" + "reference": "fa8257a579ec623473eabfe49731de5967306c4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/2f39286e0136673778b7a142b3f0d141e43d1714", - "reference": "2f39286e0136673778b7a142b3f0d141e43d1714", - "shasum": "" + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "composer/pcre": "^1||^2||^3", @@ -1377,7 +1473,7 @@ "maennchen/zipstream-php": "^2.1 || ^3.0", "markbaker/complex": "^3.0", "markbaker/matrix": "^3.0", - "php": "^7.4 || ^8.0", + "php": ">=7.4.0 <8.5.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" @@ -1402,7 +1498,7 @@ "mpdf/mpdf": "Option for rendering PDF with PDF Writer", "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" }, - "time": "2025-08-10T06:28:02+00:00", + "time": "2025-10-26T16:01:04+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1448,7 +1544,7 @@ ], "support": { "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.0" + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1" }, "install-path": "../phpoffice/phpspreadsheet" }, @@ -1839,24 +1935,30 @@ }, { "name": "psr/http-factory", - "version": "1.1.0", - "version_normalized": "1.1.0.0", + "version": "1.0.2", + "version_normalized": "1.0.2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "shasum": "" + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { - "php": ">=7.1", + "php": ">=7.0.0", "psr/http-message": "^1.0 || ^2.0" }, - "time": "2024-04-15T12:06:14+00:00", + "time": "2023-04-10T20:10:41+00:00", "type": "library", "extra": { "branch-alias": { @@ -1879,7 +1981,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "description": "Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -1891,33 +1993,39 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, "install-path": "../psr/http-factory" }, { "name": "psr/http-message", - "version": "2.0", - "version_normalized": "2.0.0.0", + "version": "1.1", + "version_normalized": "1.1.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "shasum": "" + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "php": "^7.2 || ^8.0" }, - "time": "2023-04-04T09:54:51+00:00", + "time": "2023-04-04T09:50:52+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.1.x-dev" } }, "installation-source": "dist", @@ -1933,7 +2041,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "homepage": "http://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -1947,7 +2055,7 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" + "source": "https://github.com/php-fig/http-message/tree/1.1" }, "install-path": "../psr/http-message" }, @@ -2289,23 +2397,29 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", - "version_normalized": "3.6.0.0", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", - "shasum": "" + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { - "php": ">=8.1" + "php": ">=8.0.2" }, - "time": "2024-09-25T14:21:43+00:00", + "time": "2022-01-02T09:55:41+00:00", "type": "library", "extra": { "thanks": { @@ -2313,7 +2427,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.0-dev" } }, "installation-source": "dist", @@ -2339,7 +2453,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" }, "funding": [ { @@ -2447,24 +2561,33 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", - "version_normalized": "3.6.0.0", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", - "shasum": "" + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { - "php": ">=8.1", + "php": ">=8.0.2", "psr/event-dispatcher": "^1" }, - "time": "2024-09-25T14:21:43+00:00", + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "time": "2022-01-02T09:55:41+00:00", "type": "library", "extra": { "thanks": { @@ -2472,7 +2595,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.0-dev" } }, "installation-source": "dist", @@ -2506,7 +2629,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.2" }, "funding": [ { @@ -2526,26 +2649,29 @@ }, { "name": "symfony/finder", - "version": "v7.3.2", - "version_normalized": "7.3.2.0", + "version": "v6.0.19", + "version_normalized": "6.0.19.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + "reference": "5cc9cac6586fc0c28cd173780ca696e419fefa11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", - "shasum": "" + "url": "https://api.github.com/repos/symfony/finder/zipball/5cc9cac6586fc0c28cd173780ca696e419fefa11", + "reference": "5cc9cac6586fc0c28cd173780ca696e419fefa11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { - "php": ">=8.2" + "php": ">=8.0.2" }, - "require-dev": { - "symfony/filesystem": "^6.4|^7.0" - }, - "time": "2025-07-15T13:41:35+00:00", + "time": "2023-01-20T17:44:14+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2573,7 +2699,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.2" + "source": "https://github.com/symfony/finder/tree/v6.0.19" }, "funding": [ { @@ -2584,10 +2710,6 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2676,8 +2798,8 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", - "version_normalized": "1.33.0.0", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -2687,7 +2809,13 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", - "shasum": "" + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "ext-iconv": "*", @@ -2740,7 +2868,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -2751,10 +2879,6 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2764,8 +2888,8 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.33.0", - "version_normalized": "1.33.0.0", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -2775,7 +2899,13 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "shasum": "" + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "php": ">=7.2" @@ -2823,7 +2953,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" }, "funding": [ { @@ -2834,10 +2964,6 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2847,8 +2973,8 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.33.0", - "version_normalized": "1.33.0.0", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -2858,7 +2984,13 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "shasum": "" + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "php": ">=7.2" @@ -2910,7 +3042,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -2921,10 +3053,6 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -3026,28 +3154,36 @@ }, { "name": "symfony/service-contracts", - "version": "v3.6.0", - "version_normalized": "3.6.0.0", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "shasum": "" + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d78d39c1599bd1188b8e26bb341da52c3c6d8a66", + "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.0.2", + "psr/container": "^2.0" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "time": "2025-04-25T09:37:31+00:00", + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2022-05-30T19:17:58+00:00", "type": "library", "extra": { "thanks": { @@ -3055,17 +3191,14 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.0-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3092,7 +3225,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.0.2" }, "funding": [ { @@ -3112,29 +3245,32 @@ }, { "name": "symfony/var-exporter", - "version": "v6.4.26", - "version_normalized": "6.4.26.0", + "version": "v6.0.19", + "version_normalized": "6.0.19.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc" + "reference": "df56f53818c2d5d9f683f4ad2e365ba73a3b69d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/466fcac5fa2e871f83d31173f80e9c2684743bfc", - "reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc", - "shasum": "" + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/df56f53818c2d5d9f683f4ad2e365ba73a3b69d2", + "reference": "df56f53818c2d5d9f683f4ad2e365ba73a3b69d2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.0.2" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^5.4|^6.0" }, - "time": "2025-09-11T09:57:09+00:00", + "time": "2023-01-13T08:34:10+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3167,12 +3303,10 @@ "export", "hydrate", "instantiate", - "lazy-loading", - "proxy", "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.26" + "source": "https://github.com/symfony/var-exporter/tree/v6.0.19" }, "funding": [ { @@ -3183,10 +3317,6 @@ "url": "https://github.com/fabpot", "type": "github" }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -3201,9 +3331,12 @@ "source": { "type": "git", "url": "https://gitee.com/fastadminnet/framework.git", - "reference": "e95df87aa1a37e83d7c5fb29722cc70967feb54b" + "reference": "9a2e7c2a1b6302afb61035c99c85bf0cfe0c52ec" }, "require": { + "ext-fileinfo": "*", + "ext-json": "*", + "ext-mbstring": "*", "php": ">=7.1.0", "topthink/think-installer": "~1.0" }, @@ -3215,7 +3348,7 @@ "phpunit/phpunit": "4.8.*", "sebastian/phpcpd": "2.*" }, - "time": "2025-03-12T07:32:08+00:00", + "time": "2025-10-21T02:31:08+00:00", "default-branch": true, "type": "think-framework", "installation-source": "source", @@ -3423,6 +3556,190 @@ "source": "https://github.com/top-think/think-queue/tree/master" }, "install-path": "../topthink/think-queue" + }, + { + "name": "workerman/channel", + "version": "v1.2.3", + "version_normalized": "1.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/channel.git", + "reference": "5edb0008eae35bf2da7218d911042abd23aa4370" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/channel/zipball/5edb0008eae35bf2da7218d911042abd23aa4370", + "reference": "5edb0008eae35bf2da7218d911042abd23aa4370", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "workerman/workerman": ">=4.0.12" + }, + "time": "2025-07-08T01:33:22+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Channel\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/channel/issues", + "source": "https://github.com/walkor/channel/tree/v1.2.3" + }, + "install-path": "../workerman/channel" + }, + { + "name": "workerman/phpsocket.io", + "version": "v2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/phpsocket.io.git", + "reference": "0ba306b380e016f447f9860db95fcc1c7553fb91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/phpsocket.io/zipball/0ba306b380e016f447f9860db95fcc1c7553fb91", + "reference": "0ba306b380e016f447f9860db95fcc1c7553fb91", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "workerman/channel": ">=1.0.0", + "workerman/workerman": "^4.0.0" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.7" + }, + "time": "2025-03-24T17:14:03+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPSocketIO\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A server side alternative implementation of socket.io in PHP based on Workerman", + "homepage": "https://www.workerman.net", + "keywords": [ + "Socket.io", + "async", + "non-blocking", + "phpsocket.io", + "server", + "sockets", + "stream", + "workerman" + ], + "support": { + "issues": "https://github.com/walkor/phpsocket.io/issues", + "source": "https://github.com/walkor/phpsocket.io/tree/v2.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/walkor", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "install-path": "../workerman/phpsocket.io" + }, + { + "name": "workerman/workerman", + "version": "v4.2.1", + "version_normalized": "4.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "cafb5a43d93d7d30a16b32a57948581cca993562" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/cafb5a43d93d7d30a16b32a57948581cca993562", + "reference": "cafb5a43d93d7d30a16b32a57948581cca993562", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=8.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "time": "2024-11-24T11:45:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/workerman", + "wiki": "http://doc.workerman.net/" + }, + "funding": [ + { + "url": "https://opencollective.com/workerman", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "install-path": "../workerman/workerman" } ], "dev": true, diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 90ccc5f..669647c 100755 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,511 +1,556 @@ - array( - 'name' => 'fastadminnet/fastadmin', - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'reference' => '48dbc5f4e4de8bfcd104300fbc5b2d1575469204', - 'type' => 'project', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev' => true, + + array ( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'aliases' => + array ( ), - 'versions' => array( - 'composer/pcre' => array( - 'pretty_version' => '3.3.2', - 'version' => '3.3.2.0', - 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', - 'type' => 'library', - 'install_path' => __DIR__ . '/./pcre', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'easywechat-composer/easywechat-composer' => array( - 'pretty_version' => '1.4.1', - 'version' => '1.4.1.0', - 'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd', - 'type' => 'composer-plugin', - 'install_path' => __DIR__ . '/../easywechat-composer/easywechat-composer', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'ezyang/htmlpurifier' => array( - 'pretty_version' => 'v4.19.0', - 'version' => '4.19.0.0', - 'reference' => 'b287d2a16aceffbf6e0295559b39662612b77fcf', - 'type' => 'library', - 'install_path' => __DIR__ . '/../ezyang/htmlpurifier', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'fastadminnet/fastadmin' => array( - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'reference' => '48dbc5f4e4de8bfcd104300fbc5b2d1575469204', - 'type' => 'project', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'fastadminnet/fastadmin-addons' => array( - 'pretty_version' => '1.4.3', - 'version' => '1.4.3.0', - 'reference' => 'b7e371254f97fae7e9232984a746ffa58b64504e', - 'type' => 'library', - 'install_path' => __DIR__ . '/../fastadminnet/fastadmin-addons', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'fastadminnet/fastadmin-mailer' => array( - 'pretty_version' => 'v2.1.1', - 'version' => '2.1.1.0', - 'reference' => 'bca635ac5f564ed6688d818d215021ffb0813746', - 'type' => 'library', - 'install_path' => __DIR__ . '/../fastadminnet/fastadmin-mailer', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'guzzlehttp/guzzle' => array( - 'pretty_version' => '7.10.0', - 'version' => '7.10.0.0', - 'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4', - 'type' => 'library', - 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'guzzlehttp/promises' => array( - 'pretty_version' => '2.3.0', - 'version' => '2.3.0.0', - 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', - 'type' => 'library', - 'install_path' => __DIR__ . '/../guzzlehttp/promises', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'guzzlehttp/psr7' => array( - 'pretty_version' => '2.8.0', - 'version' => '2.8.0.0', - 'reference' => '21dc724a0583619cd1652f673303492272778051', - 'type' => 'library', - 'install_path' => __DIR__ . '/../guzzlehttp/psr7', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'maennchen/zipstream-php' => array( - 'pretty_version' => '3.1.2', - 'version' => '3.1.2.0', - 'reference' => 'aeadcf5c412332eb426c0f9b4485f6accba2a99f', - 'type' => 'library', - 'install_path' => __DIR__ . '/../maennchen/zipstream-php', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'markbaker/complex' => array( - 'pretty_version' => '3.0.2', - 'version' => '3.0.2.0', - 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', - 'type' => 'library', - 'install_path' => __DIR__ . '/../markbaker/complex', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'markbaker/matrix' => array( - 'pretty_version' => '3.0.1', - 'version' => '3.0.1.0', - 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', - 'type' => 'library', - 'install_path' => __DIR__ . '/../markbaker/matrix', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'monolog/monolog' => array( - 'pretty_version' => '2.10.0', - 'version' => '2.10.0.0', - 'reference' => '5cf826f2991858b54d5c3809bee745560a1042a7', - 'type' => 'library', - 'install_path' => __DIR__ . '/../monolog/monolog', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'nelexa/zip' => array( - 'pretty_version' => '4.0.2', - 'version' => '4.0.2.0', - 'reference' => '88a1b6549be813278ff2dd3b6b2ac188827634a7', - 'type' => 'library', - 'install_path' => __DIR__ . '/../nelexa/zip', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'overtrue/pinyin' => array( - 'pretty_version' => '3.0.6', - 'version' => '3.0.6.0', - 'reference' => '3b781d267197b74752daa32814d3a2cf5d140779', - 'type' => 'library', - 'install_path' => __DIR__ . '/../overtrue/pinyin', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'overtrue/socialite' => array( - 'pretty_version' => '2.0.24', - 'version' => '2.0.24.0', - 'reference' => 'ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec', - 'type' => 'library', - 'install_path' => __DIR__ . '/../overtrue/socialite', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'overtrue/wechat' => array( - 'pretty_version' => '4.9.0', - 'version' => '4.9.0.0', - 'reference' => '92791f5d957269c633b9aa175f842f6006f945b1', - 'type' => 'library', - 'install_path' => __DIR__ . '/../overtrue/wechat', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'paragonie/constant_time_encoding' => array( - 'pretty_version' => 'v3.1.3', - 'version' => '3.1.3.0', - 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', - 'type' => 'library', - 'install_path' => __DIR__ . '/../paragonie/constant_time_encoding', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'paragonie/random_compat' => array( - 'pretty_version' => 'v9.99.100', - 'version' => '9.99.100.0', - 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', - 'type' => 'library', - 'install_path' => __DIR__ . '/../paragonie/random_compat', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'phpoffice/phpspreadsheet' => array( - 'pretty_version' => '1.30.0', - 'version' => '1.30.0.0', - 'reference' => '2f39286e0136673778b7a142b3f0d141e43d1714', - 'type' => 'library', - 'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'phpseclib/phpseclib' => array( - 'pretty_version' => '3.0.47', - 'version' => '3.0.47.0', - 'reference' => '9d6ca36a6c2dd434765b1071b2644a1c683b385d', - 'type' => 'library', - 'install_path' => __DIR__ . '/../phpseclib/phpseclib', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'pimple/pimple' => array( - 'pretty_version' => 'v3.5.0', - 'version' => '3.5.0.0', - 'reference' => 'a94b3a4db7fb774b3d78dad2315ddc07629e1bed', - 'type' => 'library', - 'install_path' => __DIR__ . '/../pimple/pimple', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/cache' => array( - 'pretty_version' => '2.0.0', - 'version' => '2.0.0.0', - 'reference' => '213f9dbc5b9bfbc4f8db86d2838dc968752ce13b', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/cache', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/cache-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '1.0|2.0', - ), - ), - 'psr/container' => array( - 'pretty_version' => '2.0.2', - 'version' => '2.0.2.0', - 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/container', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/event-dispatcher' => array( - 'pretty_version' => '1.0.0', - 'version' => '1.0.0.0', - 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/event-dispatcher', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/event-dispatcher-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '1.0', - ), - ), - 'psr/http-client' => array( - 'pretty_version' => '1.0.3', - 'version' => '1.0.3.0', - 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/http-client', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/http-client-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '1.0', - ), - ), - 'psr/http-factory' => array( - 'pretty_version' => '1.1.0', - 'version' => '1.1.0.0', - 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/http-factory', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/http-factory-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '1.0', - ), - ), - 'psr/http-message' => array( - 'pretty_version' => '2.0', - 'version' => '2.0.0.0', - 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/http-message', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/http-message-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '1.0', - ), - ), - 'psr/log' => array( - 'pretty_version' => '1.1.4', - 'version' => '1.1.4.0', - 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/log', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/log-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '1.0.0 || 2.0.0 || 3.0.0', - ), - ), - 'psr/simple-cache' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/simple-cache', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/simple-cache-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '1.0|2.0', - ), - ), - 'ralouphie/getallheaders' => array( - 'pretty_version' => '3.0.3', - 'version' => '3.0.3.0', - 'reference' => '120b605dfeb996808c31b6477290a714d356e822', - 'type' => 'library', - 'install_path' => __DIR__ . '/../ralouphie/getallheaders', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/cache' => array( - 'pretty_version' => 'v5.4.46', - 'version' => '5.4.46.0', - 'reference' => '0fe08ee32cec2748fbfea10c52d3ee02049e0f6b', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/cache', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/cache-contracts' => array( - 'pretty_version' => 'v2.5.4', - 'version' => '2.5.4.0', - 'reference' => '517c3a3619dadfa6952c4651767fcadffb4df65e', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/cache-contracts', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/cache-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '1.0|2.0', - ), - ), - 'symfony/deprecation-contracts' => array( - 'pretty_version' => 'v3.6.0', - 'version' => '3.6.0.0', - 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/event-dispatcher' => array( - 'pretty_version' => 'v5.4.45', - 'version' => '5.4.45.0', - 'reference' => '72982eb416f61003e9bb6e91f8b3213600dcf9e9', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/event-dispatcher', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/event-dispatcher-contracts' => array( - 'pretty_version' => 'v3.6.0', - 'version' => '3.6.0.0', - 'reference' => '59eb412e93815df44f05f342958efa9f46b1e586', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/event-dispatcher-implementation' => array( - 'dev_requirement' => false, - 'provided' => array( - 0 => '2.0', - ), - ), - 'symfony/finder' => array( - 'pretty_version' => 'v7.3.2', - 'version' => '7.3.2.0', - 'reference' => '2a6614966ba1074fa93dae0bc804227422df4dfe', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/finder', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/http-foundation' => array( - 'pretty_version' => 'v5.4.48', - 'version' => '5.4.48.0', - 'reference' => '3f38b8af283b830e1363acd79e5bc3412d055341', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/http-foundation', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/polyfill-mbstring' => array( - 'pretty_version' => 'v1.33.0', - 'version' => '1.33.0.0', - 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/polyfill-php73' => array( - 'pretty_version' => 'v1.33.0', - 'version' => '1.33.0.0', - 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-php73', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/polyfill-php80' => array( - 'pretty_version' => 'v1.33.0', - 'version' => '1.33.0.0', - 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-php80', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/psr-http-message-bridge' => array( - 'pretty_version' => 'v2.3.1', - 'version' => '2.3.1.0', - 'reference' => '581ca6067eb62640de5ff08ee1ba6850a0ee472e', - 'type' => 'symfony-bridge', - 'install_path' => __DIR__ . '/../symfony/psr-http-message-bridge', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/service-contracts' => array( - 'pretty_version' => 'v3.6.0', - 'version' => '3.6.0.0', - 'reference' => 'f021b05a130d35510bd6b25fe9053c2a8a15d5d4', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/service-contracts', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/var-exporter' => array( - 'pretty_version' => 'v6.4.26', - 'version' => '6.4.26.0', - 'reference' => '466fcac5fa2e871f83d31173f80e9c2684743bfc', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/var-exporter', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'topthink/framework' => array( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'reference' => 'e95df87aa1a37e83d7c5fb29722cc70967feb54b', - 'type' => 'think-framework', - 'install_path' => __DIR__ . '/../../thinkphp', - 'aliases' => array( - 0 => '9999999-dev', - ), - 'dev_requirement' => false, - ), - 'topthink/think-captcha' => array( - 'pretty_version' => 'v1.0.9', - 'version' => '1.0.9.0', - 'reference' => '9be9dd7e61c7fa3c478c4b92910d7230b94d0d23', - 'type' => 'library', - 'install_path' => __DIR__ . '/../topthink/think-captcha', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'topthink/think-helper' => array( - 'pretty_version' => 'v1.0.7', - 'version' => '1.0.7.0', - 'reference' => '5f92178606c8ce131d36b37a57c58eb71e55f019', - 'type' => 'library', - 'install_path' => __DIR__ . '/../topthink/think-helper', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'topthink/think-installer' => array( - 'pretty_version' => 'v1.0.14', - 'version' => '1.0.14.0', - 'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1', - 'type' => 'composer-plugin', - 'install_path' => __DIR__ . '/../topthink/think-installer', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'topthink/think-queue' => array( - 'pretty_version' => 'v1.1.6', - 'version' => '1.1.6.0', - 'reference' => '250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245', - 'type' => 'think-extend', - 'install_path' => __DIR__ . '/../topthink/think-queue', - 'aliases' => array(), - 'dev_requirement' => false, - ), + 'reference' => '5769442de60965346d4098544e88c3d20a2755fe', + 'name' => 'fastadminnet/fastadmin', + ), + 'versions' => + array ( + 'composer/pcre' => + array ( + 'pretty_version' => '3.3.2', + 'version' => '3.3.2.0', + 'aliases' => + array ( + ), + 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', ), + 'easywechat-composer/easywechat-composer' => + array ( + 'pretty_version' => '1.4.1', + 'version' => '1.4.1.0', + 'aliases' => + array ( + ), + 'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd', + ), + 'ezyang/htmlpurifier' => + array ( + 'pretty_version' => 'v4.19.0', + 'version' => '4.19.0.0', + 'aliases' => + array ( + ), + 'reference' => 'b287d2a16aceffbf6e0295559b39662612b77fcf', + ), + 'fastadminnet/fastadmin' => + array ( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'aliases' => + array ( + ), + 'reference' => '5769442de60965346d4098544e88c3d20a2755fe', + ), + 'fastadminnet/fastadmin-addons' => + array ( + 'pretty_version' => '1.4.2', + 'version' => '1.4.2.0', + 'aliases' => + array ( + ), + 'reference' => '14af178a62fb4cc897f954fa9d7d53798ad2cf37', + ), + 'fastadminnet/fastadmin-mailer' => + array ( + 'pretty_version' => 'v2.1.1', + 'version' => '2.1.1.0', + 'aliases' => + array ( + ), + 'reference' => 'bca635ac5f564ed6688d818d215021ffb0813746', + ), + 'guzzlehttp/guzzle' => + array ( + 'pretty_version' => '7.9.2', + 'version' => '7.9.2.0', + 'aliases' => + array ( + ), + 'reference' => 'd281ed313b989f213357e3be1a179f02196ac99b', + ), + 'guzzlehttp/promises' => + array ( + 'pretty_version' => '2.3.0', + 'version' => '2.3.0.0', + 'aliases' => + array ( + ), + 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', + ), + 'guzzlehttp/psr7' => + array ( + 'pretty_version' => '2.8.0', + 'version' => '2.8.0.0', + 'aliases' => + array ( + ), + 'reference' => '21dc724a0583619cd1652f673303492272778051', + ), + 'maennchen/zipstream-php' => + array ( + 'pretty_version' => '2.4.0', + 'version' => '2.4.0.0', + 'aliases' => + array ( + ), + 'reference' => '3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3', + ), + 'markbaker/complex' => + array ( + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'aliases' => + array ( + ), + 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', + ), + 'markbaker/matrix' => + array ( + 'pretty_version' => '3.0.1', + 'version' => '3.0.1.0', + 'aliases' => + array ( + ), + 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', + ), + 'monolog/monolog' => + array ( + 'pretty_version' => '2.10.0', + 'version' => '2.10.0.0', + 'aliases' => + array ( + ), + 'reference' => '5cf826f2991858b54d5c3809bee745560a1042a7', + ), + 'myclabs/php-enum' => + array ( + 'pretty_version' => '1.8.4', + 'version' => '1.8.4.0', + 'aliases' => + array ( + ), + 'reference' => 'a867478eae49c9f59ece437ae7f9506bfaa27483', + ), + 'nelexa/zip' => + array ( + 'pretty_version' => '4.0.2', + 'version' => '4.0.2.0', + 'aliases' => + array ( + ), + 'reference' => '88a1b6549be813278ff2dd3b6b2ac188827634a7', + ), + 'overtrue/pinyin' => + array ( + 'pretty_version' => '3.0.6', + 'version' => '3.0.6.0', + 'aliases' => + array ( + ), + 'reference' => '3b781d267197b74752daa32814d3a2cf5d140779', + ), + 'overtrue/socialite' => + array ( + 'pretty_version' => '2.0.24', + 'version' => '2.0.24.0', + 'aliases' => + array ( + ), + 'reference' => 'ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec', + ), + 'overtrue/wechat' => + array ( + 'pretty_version' => '4.9.0', + 'version' => '4.9.0.0', + 'aliases' => + array ( + ), + 'reference' => '92791f5d957269c633b9aa175f842f6006f945b1', + ), + 'paragonie/constant_time_encoding' => + array ( + 'pretty_version' => 'v3.1.3', + 'version' => '3.1.3.0', + 'aliases' => + array ( + ), + 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', + ), + 'paragonie/random_compat' => + array ( + 'pretty_version' => 'v9.99.100', + 'version' => '9.99.100.0', + 'aliases' => + array ( + ), + 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', + ), + 'phpoffice/phpspreadsheet' => + array ( + 'pretty_version' => '1.30.1', + 'version' => '1.30.1.0', + 'aliases' => + array ( + ), + 'reference' => 'fa8257a579ec623473eabfe49731de5967306c4c', + ), + 'phpseclib/phpseclib' => + array ( + 'pretty_version' => '3.0.47', + 'version' => '3.0.47.0', + 'aliases' => + array ( + ), + 'reference' => '9d6ca36a6c2dd434765b1071b2644a1c683b385d', + ), + 'pimple/pimple' => + array ( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'aliases' => + array ( + ), + 'reference' => 'a94b3a4db7fb774b3d78dad2315ddc07629e1bed', + ), + 'psr/cache' => + array ( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'aliases' => + array ( + ), + 'reference' => '213f9dbc5b9bfbc4f8db86d2838dc968752ce13b', + ), + 'psr/cache-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0|2.0', + ), + ), + 'psr/container' => + array ( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'aliases' => + array ( + ), + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + ), + 'psr/event-dispatcher' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + ), + 'psr/event-dispatcher-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/http-client' => + array ( + 'pretty_version' => '1.0.3', + 'version' => '1.0.3.0', + 'aliases' => + array ( + ), + 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', + ), + 'psr/http-client-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/http-factory' => + array ( + 'pretty_version' => '1.0.2', + 'version' => '1.0.2.0', + 'aliases' => + array ( + ), + 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', + ), + 'psr/http-factory-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/http-message' => + array ( + 'pretty_version' => '1.1', + 'version' => '1.1.0.0', + 'aliases' => + array ( + ), + 'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba', + ), + 'psr/http-message-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0', + ), + ), + 'psr/log' => + array ( + 'pretty_version' => '1.1.4', + 'version' => '1.1.4.0', + 'aliases' => + array ( + ), + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', + ), + 'psr/log-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0.0 || 2.0.0 || 3.0.0', + ), + ), + 'psr/simple-cache' => + array ( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'aliases' => + array ( + ), + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + ), + 'psr/simple-cache-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0|2.0', + ), + ), + 'ralouphie/getallheaders' => + array ( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'aliases' => + array ( + ), + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + ), + 'symfony/cache' => + array ( + 'pretty_version' => 'v5.4.46', + 'version' => '5.4.46.0', + 'aliases' => + array ( + ), + 'reference' => '0fe08ee32cec2748fbfea10c52d3ee02049e0f6b', + ), + 'symfony/cache-contracts' => + array ( + 'pretty_version' => 'v2.5.4', + 'version' => '2.5.4.0', + 'aliases' => + array ( + ), + 'reference' => '517c3a3619dadfa6952c4651767fcadffb4df65e', + ), + 'symfony/cache-implementation' => + array ( + 'provided' => + array ( + 0 => '1.0|2.0', + ), + ), + 'symfony/deprecation-contracts' => + array ( + 'pretty_version' => 'v3.0.2', + 'version' => '3.0.2.0', + 'aliases' => + array ( + ), + 'reference' => '26954b3d62a6c5fd0ea8a2a00c0353a14978d05c', + ), + 'symfony/event-dispatcher' => + array ( + 'pretty_version' => 'v5.4.45', + 'version' => '5.4.45.0', + 'aliases' => + array ( + ), + 'reference' => '72982eb416f61003e9bb6e91f8b3213600dcf9e9', + ), + 'symfony/event-dispatcher-contracts' => + array ( + 'pretty_version' => 'v3.0.2', + 'version' => '3.0.2.0', + 'aliases' => + array ( + ), + 'reference' => '7bc61cc2db649b4637d331240c5346dcc7708051', + ), + 'symfony/event-dispatcher-implementation' => + array ( + 'provided' => + array ( + 0 => '2.0', + ), + ), + 'symfony/finder' => + array ( + 'pretty_version' => 'v6.0.19', + 'version' => '6.0.19.0', + 'aliases' => + array ( + ), + 'reference' => '5cc9cac6586fc0c28cd173780ca696e419fefa11', + ), + 'symfony/http-foundation' => + array ( + 'pretty_version' => 'v5.4.48', + 'version' => '5.4.48.0', + 'aliases' => + array ( + ), + 'reference' => '3f38b8af283b830e1363acd79e5bc3412d055341', + ), + 'symfony/polyfill-mbstring' => + array ( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'aliases' => + array ( + ), + 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', + ), + 'symfony/polyfill-php73' => + array ( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'aliases' => + array ( + ), + 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', + ), + 'symfony/polyfill-php80' => + array ( + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'aliases' => + array ( + ), + 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', + ), + 'symfony/psr-http-message-bridge' => + array ( + 'pretty_version' => 'v2.3.1', + 'version' => '2.3.1.0', + 'aliases' => + array ( + ), + 'reference' => '581ca6067eb62640de5ff08ee1ba6850a0ee472e', + ), + 'symfony/service-contracts' => + array ( + 'pretty_version' => 'v3.0.2', + 'version' => '3.0.2.0', + 'aliases' => + array ( + ), + 'reference' => 'd78d39c1599bd1188b8e26bb341da52c3c6d8a66', + ), + 'symfony/var-exporter' => + array ( + 'pretty_version' => 'v6.0.19', + 'version' => '6.0.19.0', + 'aliases' => + array ( + ), + 'reference' => 'df56f53818c2d5d9f683f4ad2e365ba73a3b69d2', + ), + 'topthink/framework' => + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + 0 => '9999999-dev', + ), + 'reference' => '9a2e7c2a1b6302afb61035c99c85bf0cfe0c52ec', + ), + 'topthink/think-captcha' => + array ( + 'pretty_version' => 'v1.0.9', + 'version' => '1.0.9.0', + 'aliases' => + array ( + ), + 'reference' => '9be9dd7e61c7fa3c478c4b92910d7230b94d0d23', + ), + 'topthink/think-helper' => + array ( + 'pretty_version' => 'v1.0.7', + 'version' => '1.0.7.0', + 'aliases' => + array ( + ), + 'reference' => '5f92178606c8ce131d36b37a57c58eb71e55f019', + ), + 'topthink/think-installer' => + array ( + 'pretty_version' => 'v1.0.14', + 'version' => '1.0.14.0', + 'aliases' => + array ( + ), + 'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1', + ), + 'topthink/think-queue' => + array ( + 'pretty_version' => 'v1.1.6', + 'version' => '1.1.6.0', + 'aliases' => + array ( + ), + 'reference' => '250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245', + ), + 'workerman/channel' => + array ( + 'pretty_version' => 'v1.2.3', + 'version' => '1.2.3.0', + 'aliases' => + array ( + ), + 'reference' => '5edb0008eae35bf2da7218d911042abd23aa4370', + ), + 'workerman/phpsocket.io' => + array ( + 'pretty_version' => 'v2.2.0', + 'version' => '2.2.0.0', + 'aliases' => + array ( + ), + 'reference' => '0ba306b380e016f447f9860db95fcc1c7553fb91', + ), + 'workerman/workerman' => + array ( + 'pretty_version' => 'v4.2.1', + 'version' => '4.2.1.0', + 'aliases' => + array ( + ), + 'reference' => 'cafb5a43d93d7d30a16b32a57948581cca993562', + ), + ), ); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php index 580fa96..b168ddd 100755 --- a/vendor/composer/platform_check.php +++ b/vendor/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 70400)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 80002)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.2". You are running ' . PHP_VERSION . '.'; } if ($issues) { diff --git a/vendor/fastadminnet/fastadmin-addons/composer.json b/vendor/fastadminnet/fastadmin-addons/composer.json index 050b7c9..ad98272 100644 --- a/vendor/fastadminnet/fastadmin-addons/composer.json +++ b/vendor/fastadminnet/fastadmin-addons/composer.json @@ -3,7 +3,7 @@ "description": "addons package for fastadmin", "homepage": "https://github.com/fastadminnet/fastadmin-addons", "license": "Apache-2.0", - "version": "1.4.3", + "version": "1.4.2", "authors": [ { "name": "Karson", @@ -18,7 +18,7 @@ "issues": "https://github.com/fastadminnet/fastadmin-addons/issues" }, "require": { - "php": ">=7.1.0", + "php": ">=7.0.0", "nelexa/zip": "^3.3 || ^4.0" }, "autoload": { diff --git a/vendor/fastadminnet/fastadmin-addons/src/addons/Controller.php b/vendor/fastadminnet/fastadmin-addons/src/addons/Controller.php index 457ff5c..fea2c4f 100644 --- a/vendor/fastadminnet/fastadmin-addons/src/addons/Controller.php +++ b/vendor/fastadminnet/fastadmin-addons/src/addons/Controller.php @@ -63,7 +63,7 @@ class Controller extends \think\Controller * @param Request $request Request对象 * @access public */ - public function __construct(?Request $request = null) + public function __construct(Request $request = null) { if (is_null($request)) { $request = Request::instance(); diff --git a/vendor/guzzlehttp/guzzle/CHANGELOG.md b/vendor/guzzlehttp/guzzle/CHANGELOG.md index 5fe721e..e0b6216 100644 --- a/vendor/guzzlehttp/guzzle/CHANGELOG.md +++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -2,25 +2,6 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. -## 7.10.0 - 2025-08-23 - -### Added - -- Support for PHP 8.5 - -### Changed - -- Adjusted `guzzlehttp/promises` version constraint to `^2.3` -- Adjusted `guzzlehttp/psr7` version constraint to `^2.8` - - -## 7.9.3 - 2025-03-27 - -### Changed - -- Remove explicit content-length header for GET requests -- Improve compatibility with bad servers for boolean cookie values - ## 7.9.2 - 2024-07-24 diff --git a/vendor/guzzlehttp/guzzle/composer.json b/vendor/guzzlehttp/guzzle/composer.json index 0db75a9..cbede14 100644 --- a/vendor/guzzlehttp/guzzle/composer.json +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -81,8 +81,8 @@ "require": { "php": "^7.2.5 || ^8.0", "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" }, diff --git a/vendor/guzzlehttp/guzzle/package-lock.json b/vendor/guzzlehttp/guzzle/package-lock.json deleted file mode 100644 index 0e14dc1..0000000 --- a/vendor/guzzlehttp/guzzle/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "guzzle", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php index 47c4d10..c9806da 100644 --- a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -62,10 +62,6 @@ class SetCookie if (is_numeric($value)) { $data[$search] = (int) $value; } - } elseif ($search === 'Secure' || $search === 'Discard' || $search === 'HttpOnly') { - if ($value) { - $data[$search] = true; - } } else { $data[$search] = $value; } diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php index 3c1fa9c..fe36137 100644 --- a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php @@ -125,9 +125,7 @@ class CurlFactory implements CurlFactoryInterface unset($easy->handle); if (\count($this->handles) >= $this->maxHandles) { - if (PHP_VERSION_ID < 80000) { - \curl_close($resource); - } + \curl_close($resource); } else { // Remove all callback functions as they can hold onto references // and are not cleaned up by curl_reset. Using curl_setopt_array @@ -731,10 +729,7 @@ class CurlFactory implements CurlFactoryInterface public function __destruct() { foreach ($this->handles as $id => $handle) { - if (PHP_VERSION_ID < 80000) { - \curl_close($handle); - } - + \curl_close($handle); unset($this->handles[$id]); } } diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php index 21abbed..73a6abe 100644 --- a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -240,10 +240,7 @@ class CurlMultiHandler $handle = $this->handles[$id]['easy']->handle; unset($this->delays[$id], $this->handles[$id]); \curl_multi_remove_handle($this->_mh, $handle); - - if (PHP_VERSION_ID < 80000) { - \curl_close($handle); - } + \curl_close($handle); return true; } diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php index 9df70cf..f045b52 100644 --- a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php +++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -17,10 +17,10 @@ class Proxy * Sends synchronous requests to a specific handler while sending all other * requests to another handler. * - * @param callable(RequestInterface, array): PromiseInterface $default Handler used for normal responses - * @param callable(RequestInterface, array): PromiseInterface $sync Handler used for synchronous responses. + * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for normal responses + * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $sync Handler used for synchronous responses. * - * @return callable(RequestInterface, array): PromiseInterface Returns the composed handler. + * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler. */ public static function wrapSync(callable $default, callable $sync): callable { @@ -37,10 +37,10 @@ class Proxy * performance benefits of curl while still supporting true streaming * through the StreamHandler. * - * @param callable(RequestInterface, array): PromiseInterface $default Handler used for non-streaming responses - * @param callable(RequestInterface, array): PromiseInterface $streaming Handler used for streaming responses + * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for non-streaming responses + * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $streaming Handler used for streaming responses * - * @return callable(RequestInterface, array): PromiseInterface Returns the composed handler. + * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler. */ public static function wrapStreaming(callable $default, callable $streaming): callable { diff --git a/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php index f24921f..1d89a8f 100644 --- a/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php +++ b/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php @@ -53,14 +53,8 @@ class StreamHandler $request = $request->withoutHeader('Expect'); // Append a content-length header if body size is zero to match - // the behavior of `CurlHandler` - if ( - ( - 0 === \strcasecmp('PUT', $request->getMethod()) - || 0 === \strcasecmp('POST', $request->getMethod()) - ) - && 0 === $request->getBody()->getSize() - ) { + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { $request = $request->withHeader('Content-Length', '0'); } @@ -333,15 +327,8 @@ class StreamHandler ); return $this->createResource( - function () use ($uri, $contextResource, $context, $options, $request) { + function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { $resource = @\fopen((string) $uri, 'r', false, $contextResource); - - // See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable - if (function_exists('http_get_last_response_headers')) { - /** @var array|null */ - $http_response_header = \http_get_last_response_headers(); - } - $this->lastHeaders = $http_response_header ?? []; if (false === $resource) { diff --git a/vendor/guzzlehttp/guzzle/src/Middleware.php b/vendor/guzzlehttp/guzzle/src/Middleware.php index 9901da4..6edbb3f 100644 --- a/vendor/guzzlehttp/guzzle/src/Middleware.php +++ b/vendor/guzzlehttp/guzzle/src/Middleware.php @@ -187,12 +187,12 @@ final class Middleware * Middleware that logs requests, responses, and errors using a message * formatter. * + * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * * @param LoggerInterface $logger Logs messages. * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. * @param string $logLevel Level at which to log requests. * - * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. - * * @return callable Returns a function that accepts the next handler. */ public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php index ddc304b..6277c61 100644 --- a/vendor/guzzlehttp/guzzle/src/Pool.php +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -86,7 +86,7 @@ class Pool implements PromisorInterface * @param ClientInterface $client Client used to send the requests * @param array|\Iterator $requests Requests to send concurrently. * @param array $options Passes through the options available in - * {@see Pool::__construct} + * {@see \GuzzleHttp\Pool::__construct} * * @return array Returns an array containing the response or an exception * in the same order that the requests were sent. diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php index c6a5893..df52927 100644 --- a/vendor/guzzlehttp/guzzle/src/Utils.php +++ b/vendor/guzzlehttp/guzzle/src/Utils.php @@ -79,7 +79,7 @@ final class Utils * * The returned handler is not wrapped by any default middlewares. * - * @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system. + * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system. * * @throws \RuntimeException if no viable Handler is available. */ diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php index 9ab4b96..5edc66a 100644 --- a/vendor/guzzlehttp/guzzle/src/functions.php +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -50,7 +50,7 @@ function debug_resource($value = null) * * The returned handler is not wrapped by any default middlewares. * - * @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system. + * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system. * * @throws \RuntimeException if no viable Handler is available. * diff --git a/vendor/maennchen/zipstream-php/.gitattributes b/vendor/maennchen/zipstream-php/.gitattributes deleted file mode 100644 index e058ebd..0000000 --- a/vendor/maennchen/zipstream-php/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -.gitignore text eol=lf -.gitattributes text eol=lf -*.md text eol=lf -*.php text eol=lf -*.yml text eol=lf -*.xml text eol=lf diff --git a/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md b/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 9d75b87..0000000 --- a/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or advances of - any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, - without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -jonatan@maennchen.ch. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][mozilla coc]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][faq]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[mozilla coc]: https://github.com/mozilla/diversity -[faq]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md b/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md deleted file mode 100644 index d8caee0..0000000 --- a/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md +++ /dev/null @@ -1,139 +0,0 @@ -# Contributing to ZipStream-PHP - -## Welcome! - -We look forward to your contributions! Here are some examples how you can -contribute: - -- [Report a bug](https://github.com/maennchen/ZipStream-PHP/issues/new?labels=bug&template=BUG.md) -- [Propose a new feature](https://github.com/maennchen/ZipStream-PHP/issues/new?labels=enhancement&template=FEATURE.md) -- [Send a pull request](https://github.com/maennchen/ZipStream-PHP/pulls) - -## We have a Code of Conduct - -Please note that this project is released with a -[Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this -project you agree to abide by its terms. - -## Any contributions you make will be under the MIT License - -When you submit code changes, your submissions are understood to be under the -same [MIT License](https://github.com/maennchen/ZipStream-PHP/blob/main/LICENSE) -that covers the project. By contributing to this project, you agree that your -contributions will be licensed under its MIT License. - -## Write bug reports with detail, background, and sample code - -In your bug report, please provide the following: - -- A quick summary and/or background -- Steps to reproduce - - Be specific! - - Give sample code if you can. -- What you expected would happen -- What actually happens -- Notes (possibly including why you think this might be happening, or stuff you -- tried that didn't work) - -Please do not report a bug for a version of ZIPStream-PHP that is no longer -supported (`< 3.0.0`). Please do not report a bug if you are using a version of -PHP that is not supported by the version of ZipStream-PHP you are using. - -Please post code and output as text -([using proper markup](https://guides.github.com/features/mastering-markdown/)). -Do not post screenshots of code or output. - -Please include the output of `composer info | sort`. - -## Workflow for Pull Requests - -1. Fork the repository. -2. Create your branch from `main` if you plan to implement new functionality or - change existing code significantly; create your branch from the oldest branch - that is affected by the bug if you plan to fix a bug. -3. Implement your change and add tests for it. -4. Ensure the test suite passes. -5. Ensure the code complies with our coding guidelines (see below). -6. Send that pull request! - -Please make sure you have -[set up your user name and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) -for use with Git. Strings such as `silly nick name ` look really -stupid in the commit history of a project. - -We encourage you to -[sign your Git commits with your GPG key](https://docs.github.com/en/github/authenticating-to-github/signing-commits). - -Pull requests for new features must be based on the `main` branch. - -We are trying to keep backwards compatibility breaks in ZipStream-PHP to a -minimum. Please take this into account when proposing changes. - -Due to time constraints, we are not always able to respond as quickly as we -would like. Please do not take delays personal and feel free to remind us if you -feel that we forgot to respond. - -## Coding Guidelines - -This project comes with a configuration file (located at `/psalm.yml` in the -repository) that you can use to perform static analysis (with a focus on type -checking): - -```bash -$ .composer run test:lint -``` - -This project comes with a configuration file (located at -`/.php-cs-fixer.dist.php` in the repository) that you can use to (re)format your -source code for compliance with this project's coding guidelines: - -```bash -$ composer run format -``` - -Please understand that we will not accept a pull request when its changes -violate this project's coding guidelines. - -## Using ZipStream-PHP from a Git checkout - -The following commands can be used to perform the initial checkout of -ZipStream-PHP: - -```bash -$ git clone git@github.com:maennchen/ZipStream-PHP.git - -$ cd ZipStream-PHP -``` - -Install ZipStream-PHP's dependencies using [Composer](https://getcomposer.org/): - -```bash -$ composer install -$ composer run install:tools # Install phpDocumentor using phive -``` - -## Running ZipStream-PHP's test suite - -After following the steps shown above, ZipStream-PHP's test suite is run like -this: - -```bash -$ composer run test:unit -``` - -There's some slow tests in the test suite that test the handling of big files in -the archives. To skip them use the following command instead: - -```bash -$ composer run test:unit:fast -``` - -## Generating ZipStream-PHP Documentation - -To generate the documentation for the library, run: - -```bash -$ composer run docs:generate -``` - -The guide documentation pages can be found in the `/guides/` directory. diff --git a/vendor/maennchen/zipstream-php/.github/FUNDING.yml b/vendor/maennchen/zipstream-php/.github/FUNDING.yml deleted file mode 100644 index 5a46127..0000000 --- a/vendor/maennchen/zipstream-php/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: maennchen diff --git a/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml deleted file mode 100644 index 0eb8cc7..0000000 --- a/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: 🐞 Bug Report -description: Something is broken? -labels: ["bug"] -body: - - type: markdown - attributes: - value: | - - Create a discussion instead if you are looking for support: - https://github.com/maennchen/ZipStream-PHP/discussions - - type: input - id: version - attributes: - label: ZipStream-PHP version - placeholder: x.y.z - validations: - required: true - - type: input - id: php-version - attributes: - label: PHP version - placeholder: x.y.z - validations: - required: true - - type: checkboxes - id: constraints - attributes: - label: Constraints for Bug Report - options: - - label: | - I'm using a version of ZipStream that is currently supported: - https://github.com/maennchen/ZipStream-PHP#version-support - required: true - - label: | - I'm using a version of PHP that has active support: - https://www.php.net/supported-versions.php - required: true - - label: | - I'm using a version of PHP that is compatible with your used - ZipStream version. - required: true - - label: | - I'm using the latest release of the used ZipStream major version. - required: true - - type: textarea - id: summary - attributes: - label: Summary - description: Provide a summary describing the problem you are experiencing. - validations: - required: true - - type: textarea - id: current-behaviour - attributes: - label: Current behavior - description: What is the current (buggy) behavior? - validations: - required: true - - type: textarea - id: reproduction - attributes: - label: How to reproduce - description: Provide steps to reproduce the bug. - validations: - required: true - - type: textarea - id: expected-behaviour - attributes: - label: Expected behavior - description: What was the expected (correct) behavior? - validations: - required: true diff --git a/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml deleted file mode 100644 index e5dec63..0000000 --- a/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: 🎉 Feature Request -description: You have a neat idea that should be implemented? -labels: ["enhancement"] -body: - - type: textarea - id: description - attributes: - label: Description - description: Provide a summary of the feature you would like to see implemented. - validations: - required: true diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 6892c57..0000000 --- a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,6 +0,0 @@ -Please go the the `Preview` tab and select the appropriate sub-template: - -* [🐞 Failing Test](?expand=1&template=FAILING_TEST.md) -* [🐞 Bug Fix](?expand=1&template=FIX.md) -* [⚙ Improvement](?expand=1&template=IMPROVEMENT.md) -* [🎉 New Feature](?expand=1&template=NEW_FEATURE.md) diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md deleted file mode 100644 index 24603cb..0000000 --- a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md deleted file mode 100644 index 77f65a0..0000000 --- a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md deleted file mode 100644 index 3ac8e31..0000000 --- a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md deleted file mode 100644 index ca53939..0000000 --- a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/vendor/maennchen/zipstream-php/.github/SECURITY.md b/vendor/maennchen/zipstream-php/.github/SECURITY.md deleted file mode 100644 index 3046c31..0000000 --- a/vendor/maennchen/zipstream-php/.github/SECURITY.md +++ /dev/null @@ -1,22 +0,0 @@ -# Security Policy - -[![OpenSSF Vulnerability Disclosure](https://img.shields.io/badge/OpenSSF-Vulnerability_Disclosure-green)](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md) -[![GitHub Report](https://img.shields.io/badge/GitHub-Security_Advisories-blue)](https://github.com/maennchen/ZipStream-PHP/security/advisories/new) -[![Email Report](https://img.shields.io/badge/Email-jonatan%40maennchen.ch-blue)](mailto:jonatan@maennchen.ch) - -This repository follows the -[OpenSSF Vulnerability Disclosure guide](https://github.com/ossf/oss-vulnerability-guide/tree/main). -You can learn more about it in the -[Finders Guide](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md). - -Please report vulnerabilities via the -[GitHub Security Vulnerability Reporting](https://github.com/maennchen/ZipStream-PHP/security/advisories/new) -or via email to [`jonatan@maennchen.ch`](mailto:jonatan@maennchen.ch) if this does -not work for you. - -Our vulnerability management team will respond within 3 working days of your -report. If the issue is confirmed as a vulnerability, we will open a Security -Advisory. This project follows a 90 day disclosure timeline. - -If you have questions about reporting security issues, email the vulnerability -management team: [`jonatan@maennchen.ch`](mailto:jonatan@maennchen.ch) diff --git a/vendor/maennchen/zipstream-php/.github/dependabot.yml b/vendor/maennchen/zipstream-php/.github/dependabot.yml deleted file mode 100644 index 9d20742..0000000 --- a/vendor/maennchen/zipstream-php/.github/dependabot.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "composer" - directory: "/" - schedule: - interval: "daily" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - groups: - github-actions: - applies-to: version-updates - patterns: - - "*" diff --git a/vendor/maennchen/zipstream-php/.github/scorecard.yml b/vendor/maennchen/zipstream-php/.github/scorecard.yml deleted file mode 100644 index 219fc0b..0000000 --- a/vendor/maennchen/zipstream-php/.github/scorecard.yml +++ /dev/null @@ -1,14 +0,0 @@ -annotations: - - checks: - - fuzzing - reasons: - - reason: not-applicable # PHP is memory safe - - checks: - - packaging - reasons: - - reason: not-supported # Using Composer - - checks: - - signed-releases - reasons: - - reason: not-applicable # Releases are distributed via Composer - diff --git a/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml b/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml deleted file mode 100644 index 15ff278..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml +++ /dev/null @@ -1,24 +0,0 @@ -on: - push: - branches: - - "main" - -name: "Main Branch" - -permissions: - contents: read - -jobs: - test: - name: "Test" - - permissions: - contents: read - security-events: write - - uses: ./.github/workflows/part_test.yml - - docs: - name: "Docs" - - uses: ./.github/workflows/part_docs.yml diff --git a/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml b/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml deleted file mode 100644 index 20a13a2..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml +++ /dev/null @@ -1,30 +0,0 @@ -on: - workflow_call: {} - -name: "Dependabot" - -permissions: - contents: read - -jobs: - automerge_dependabot: - name: "Automerge PRs" - - runs-on: ubuntu-latest - - permissions: - pull-requests: write - contents: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - uses: fastify/github-action-merge-dependabot@c3bde0759d4f24db16f7b250b2122bc2df57e817 # v3.11.0 - with: - github-token: ${{ github.token }} - use-github-auto-merge: true - # Major Updates need to be merged manually - target: minor diff --git a/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml b/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml deleted file mode 100644 index 9b779eb..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml +++ /dev/null @@ -1,51 +0,0 @@ -on: - workflow_call: {} - -name: "Documentation" - -permissions: - contents: read - -jobs: - generate: - name: "Generate" - - runs-on: ubuntu-latest - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: Checkout Code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: SetUp PHP - id: setup-php - uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2 - with: - php-version: "8.3" - tools: phive - - name: Cache Tools - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - id: cache - with: - path: ~/.phive - key: tools-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-${{ hashFiles('**/phars.xml') }} - restore-keys: | - tools-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}- - tools-${{ steps.setup-php.outputs.php-version }}- - tools- - - name: Install Tools - run: composer run install:tools - - name: Generate Docs - run: composer run docs:generate - - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 - with: - name: docs - path: docs - - name: Package for GitHub Pages - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 - with: - path: docs - diff --git a/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml b/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml deleted file mode 100644 index 112d72a..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml +++ /dev/null @@ -1,94 +0,0 @@ -on: - workflow_call: - inputs: - releaseName: - required: true - type: string - stable: - required: false - type: boolean - default: false - -name: "Release" - -permissions: - contents: read - -jobs: - create: - name: Create Release - - runs-on: ubuntu-latest - - permissions: - contents: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: Create prerelease - if: ${{ !inputs.stable }} - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - gh release create \ - --repo ${{ github.repository }} \ - --title ${{ inputs.releaseName }} \ - --prerelease \ - --generate-notes \ - ${{ inputs.releaseName }} - - - name: Create release - if: ${{ inputs.stable }} - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - gh release create \ - --repo ${{ github.repository }} \ - --title ${{ inputs.releaseName }} \ - --generate-notes \ - ${{ inputs.releaseName }} - - upload_release: - name: "Upload" - - needs: ["create"] - - runs-on: ubuntu-latest - - permissions: - id-token: write - contents: write - attestations: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: docs - path: docs - - run: | - tar -czvf docs.tar.gz docs - - name: "Attest Documentation" - id: attestation - uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 - with: - subject-path: "docs.tar.gz" - - name: Copy Attestation - run: cp "$ATTESTATION" docs.tar.gz.sigstore - env: - ATTESTATION: "${{ steps.attestation.outputs.bundle-path }}" - - name: Upload - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - gh release upload --clobber "${{ github.ref_name }}" \ - docs.tar.gz docs.tar.gz.sigstore diff --git a/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml b/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml deleted file mode 100644 index d4f8180..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml +++ /dev/null @@ -1,181 +0,0 @@ -on: - workflow_call: - -name: "Test" - -permissions: - contents: read - -jobs: - phpunit: - name: PHPUnit (PHP ${{ matrix.php }} on ${{ matrix.os }}) - - runs-on: ${{ matrix.os }} - - continue-on-error: ${{ matrix.experimental }} - - strategy: - fail-fast: false - matrix: - php: ["8.2", "8.3", "8.4"] - os: [ubuntu-latest] - experimental: [false] - include: - - php: nightly - os: ubuntu-latest - experimental: true - - php: "8.4" - os: windows-latest - experimental: false - - php: "8.4" - os: macos-latest - experimental: false - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: Checkout Code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: SetUp PHP - id: setup-php - uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2 - with: - php-version: "${{ matrix.php }}" - tools: phpunit - coverage: xdebug - extensions: xdebug,zip - - name: Get composer cache directory - id: composer-cache-common - if: "${{ runner.os != 'Windows' }}" - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - name: Get composer cache directory - id: composer-cache-windows - if: "${{ runner.os == 'Windows' }}" - run: echo "dir=$(composer config cache-files-dir)" >> $env:GITHUB_OUTPUT - - name: Cache Deps - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - id: cache - with: - path: ${{ steps.composer-cache-common.outputs.dir }}${{ steps.composer-cache-windows.outputs.dir }} - key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer- - deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}- - deps-${{ steps.setup-php.outputs.php-version }}- - deps- - - name: Install Deps - if: matrix.php != 'nightly' - run: composer install --prefer-dist - - name: Install Deps (ignore PHP requirement) - if: matrix.php == 'nightly' - run: composer install --prefer-dist --ignore-platform-req=php+ - - name: Run PHPUnit - run: composer run test:unit:cov - - name: Upload coverage results to Coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: ${{ runner.os }}-${{ steps.setup-php.outputs.php-version }} - run: composer run coverage:report - continue-on-error: ${{ matrix.experimental }} - - mark_coverage_done: - needs: ["phpunit"] - - runs-on: ubuntu-latest - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: Coveralls Finished - uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6 - with: - github-token: ${{ secrets.github_token }} - parallel-finished: true - - psalm: - name: Run Psalm - - runs-on: "ubuntu-latest" - - permissions: - security-events: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: Checkout Code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: SetUp PHP - id: setup-php - uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2 - with: - php-version: "8.3" - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - name: Cache Deps - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - id: cache - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer- - deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}- - deps-${{ steps.setup-php.outputs.php-version }}- - deps- - - name: Install Deps - run: composer install --prefer-dist - - name: Run Psalm - run: composer run test:lint -- --report=results.sarif - - name: "Upload SARIF" - uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3 - with: - sarif_file: results.sarif - - php-cs: - name: Run PHP-CS - - runs-on: "ubuntu-latest" - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: Checkout Code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: SetUp PHP - id: setup-php - uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2 - with: - php-version: "8.3" - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - name: Cache Deps - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - id: cache - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer- - deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}- - deps-${{ steps.setup-php.outputs.php-version }}- - deps- - - name: Install Deps - run: composer install --prefer-dist - - name: Run PHP-CS - run: composer run test:formatted diff --git a/vendor/maennchen/zipstream-php/.github/workflows/pr.yml b/vendor/maennchen/zipstream-php/.github/workflows/pr.yml deleted file mode 100644 index d21f398..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/pr.yml +++ /dev/null @@ -1,50 +0,0 @@ -on: - pull_request: - branches: - - "*" - workflow_dispatch: {} - -name: "Pull Request" - -permissions: - contents: read - -jobs: - test: - name: "Test" - - permissions: - contents: read - security-events: write - - uses: ./.github/workflows/part_test.yml - - docs: - name: "Docs" - - uses: ./.github/workflows/part_docs.yml - - dependabot: - name: "Dependabot" - - if: ${{ github.actor == 'dependabot[bot]'}} - - permissions: - pull-requests: write - contents: write - - uses: ./.github/workflows/part_dependabot.yml - - dependency-review: - name: Dependency Review - runs-on: ubuntu-latest - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: 'Checkout Repository' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: 'Dependency Review' - uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 diff --git a/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml b/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml deleted file mode 100644 index c1d08a2..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml +++ /dev/null @@ -1,78 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. They are provided -# by a third-party and are governed by separate terms of service, privacy -# policy, and support documentation. - -name: Scorecard supply-chain security -on: - # For Branch-Protection check. Only the default branch is supported. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection - branch_protection_rule: - # To guarantee Maintained check is occasionally updated. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained - schedule: - - cron: '28 11 * * 3' - push: - branches: [ "main" ] - -# Declare default permissions as read only. -permissions: read-all - -jobs: - analysis: - name: Scorecard analysis - runs-on: ubuntu-latest - permissions: - # Needed to upload the results to code-scanning dashboard. - security-events: write - # Needed to publish results and get a badge (see publish_results below). - id-token: write - # Uncomment the permissions below if installing in a private repository. - # contents: read - # actions: read - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - persist-credentials: false - - - name: "Run analysis" - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 - with: - results_file: results.sarif - results_format: sarif - # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: - # - you want to enable the Branch-Protection check on a *public* repository, or - # - you are installing Scorecard on a *private* repository - # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. - # repo_token: ${{ secrets.SCORECARD_TOKEN }} - - # Public repositories: - # - Publish results to OpenSSF REST API for easy access by consumers - # - Allows the repository to include the Scorecard badge. - # - See https://github.com/ossf/scorecard-action#publishing-results. - # For private repositories: - # - `publish_results` will always be set to `false`, regardless - # of the value entered here. - publish_results: true - - # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF - # format to the repository Actions tab. - - name: "Upload artifact" - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard (optional). - # Commenting out will disable upload of results to your repo's Code Scanning dashboard - - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 - with: - sarif_file: results.sarif diff --git a/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml b/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml deleted file mode 100644 index b339945..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml +++ /dev/null @@ -1,29 +0,0 @@ -on: - push: - tags: - - "[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+" - -name: "Beta Tag" - -permissions: - contents: read - -jobs: - docs: - name: "Docs" - - uses: ./.github/workflows/part_docs.yml - - release: - name: "Release" - - needs: ["docs"] - - permissions: - id-token: write - contents: write - attestations: write - - uses: ./.github/workflows/part_release.yml - with: - releaseName: "${{ github.ref_name }}" diff --git a/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml b/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml deleted file mode 100644 index dfc1438..0000000 --- a/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml +++ /dev/null @@ -1,55 +0,0 @@ -on: - push: - tags: - - "[0-9]+.[0-9]+.[0-9]+" - -name: "Stable Tag" - -permissions: - contents: read - -jobs: - docs: - name: "Docs" - - uses: ./.github/workflows/part_docs.yml - - release: - name: "Release" - - needs: ["docs"] - - permissions: - id-token: write - contents: write - attestations: write - - uses: ./.github/workflows/part_release.yml - with: - releaseName: "${{ github.ref_name }}" - stable: true - - deploy_pages: - name: "Deploy to GitHub Pages" - - needs: ["release", "docs"] - - runs-on: ubuntu-latest - - permissions: - pages: write - id-token: write - - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - steps: - - name: Harden Runner - uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 - with: - egress-policy: audit - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/vendor/maennchen/zipstream-php/.gitignore b/vendor/maennchen/zipstream-php/.gitignore deleted file mode 100644 index e52a498..0000000 --- a/vendor/maennchen/zipstream-php/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -/composer.lock -/cov -/coverage.clover.xml -/docs -.idea -/.php-cs-fixer.cache -/.phpdoc/cache -/.phpunit.result.cache -/phpunit.xml -/.phpunit.cache -/tools -/vendor diff --git a/vendor/maennchen/zipstream-php/.phive/phars.xml b/vendor/maennchen/zipstream-php/.phive/phars.xml index c958402..569106a 100644 --- a/vendor/maennchen/zipstream-php/.phive/phars.xml +++ b/vendor/maennchen/zipstream-php/.phive/phars.xml @@ -1,4 +1,4 @@ - + diff --git a/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php b/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php index 9d47c38..3ba86a4 100644 --- a/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php +++ b/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php @@ -13,7 +13,6 @@ declare(strict_types=1); use PhpCsFixer\Config; use PhpCsFixer\Finder; -use PhpCsFixer\Runner; $finder = Finder::create() ->exclude('.github') @@ -27,8 +26,7 @@ $config = new Config(); return $config->setRules([ '@PER' => true, '@PER:risky' => true, - '@PHP83Migration' => true, - '@PHP84Migration' => true, + '@PHP81Migration' => true, '@PHPUnit84Migration:risky' => true, 'array_syntax' => ['syntax' => 'short'], 'class_attributes_separation' => true, @@ -52,6 +50,7 @@ return $config->setRules([ 'semicolon_after_instruction' => true, 'short_scalar_cast' => true, 'simplified_null_return' => true, + 'single_blank_line_before_namespace' => true, 'single_class_element_per_statement' => true, 'single_line_comment_style' => true, 'single_quote' => true, @@ -69,5 +68,4 @@ return $config->setRules([ ], ]) ->setFinder($finder) - ->setRiskyAllowed(true) - ->setParallelConfig(Runner\Parallel\ParallelConfigFactory::detect()); + ->setRiskyAllowed(true); \ No newline at end of file diff --git a/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig b/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig index 2a70c0a..b7507fb 100644 --- a/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig +++ b/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig @@ -9,7 +9,7 @@ "social": [ { "iconClass": "fab fa-github", "url": "https://github.com/maennchen/ZipStream-PHP"}, { "iconClass": "fas fa-envelope-open-text", "url": "https://github.com/maennchen/ZipStream-PHP/discussions"}, - { "iconClass": "fas fa-money-bill", "url": "https://github.com/sponsors/maennchen"}, + { "iconClass": "fas fa-money-bill", "url": "https://opencollective.com/zipstream"}, ] } %} \ No newline at end of file diff --git a/vendor/maennchen/zipstream-php/.tool-versions b/vendor/maennchen/zipstream-php/.tool-versions index 150c1ee..54f6ff0 100644 --- a/vendor/maennchen/zipstream-php/.tool-versions +++ b/vendor/maennchen/zipstream-php/.tool-versions @@ -1 +1 @@ -php 8.4.3 +php 8.2.0 diff --git a/vendor/maennchen/zipstream-php/README.md b/vendor/maennchen/zipstream-php/README.md index 1e6d679..155a265 100644 --- a/vendor/maennchen/zipstream-php/README.md +++ b/vendor/maennchen/zipstream-php/README.md @@ -4,8 +4,7 @@ [![Coverage Status](https://coveralls.io/repos/github/maennchen/ZipStream-PHP/badge.svg?branch=main)](https://coveralls.io/github/maennchen/ZipStream-PHP?branch=main) [![Latest Stable Version](https://poser.pugx.org/maennchen/zipstream-php/v/stable)](https://packagist.org/packages/maennchen/zipstream-php) [![Total Downloads](https://poser.pugx.org/maennchen/zipstream-php/downloads)](https://packagist.org/packages/maennchen/zipstream-php) -[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9524/badge)](https://www.bestpractices.dev/projects/9524) -[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/maennchen/ZipStream-PHP/badge)](https://scorecard.dev/viewer/?uri=github.com/maennchen/ZipStream-PHP) +[![Financial Contributors on Open Collective](https://opencollective.com/zipstream/all/badge.svg?label=financial+contributors)](https://opencollective.com/zipstream) [![License](https://img.shields.io/github/license/maennchen/zipstream-php.svg)](LICENSE) ## Unstable Branch @@ -15,17 +14,13 @@ version. ## Overview -A fast and simple streaming zip file downloader for PHP. Using this library will -save you from having to write the Zip to disk. You can directly send it to the -user, which is much faster. It can work with S3 buckets or any PSR7 Stream. +A fast and simple streaming zip file downloader for PHP. Using this library will save you from having to write the Zip to disk. You can directly send it to the user, which is much faster. It can work with S3 buckets or any PSR7 Stream. Please see the [LICENSE](LICENSE) file for licensing and warranty information. ## Installation -Simply add a dependency on maennchen/zipstream-php to your project's -`composer.json` file if you use Composer to manage the dependencies of your -project. Use following command to add the package to your project's dependencies: +Simply add a dependency on maennchen/zipstream-php to your project's composer.json file if you use Composer to manage the dependencies of your project. Use following command to add the package to your project's dependencies: ```bash composer require maennchen/zipstream-php @@ -34,120 +29,51 @@ composer require maennchen/zipstream-php ## Usage For detailed instructions, please check the -[Documentation](https://maennchen.github.io/ZipStream-PHP/). +[Documentation](https://maennchen.dev/ZipStream-PHP/). + +Here's a simple example: ```php // Autoload the dependencies require 'vendor/autoload.php'; -// create a new zipstream object -$zip = new ZipStream\ZipStream( - outputName: 'example.zip', +// enable output of HTTP headers +$options = new ZipStream\Option\Archive(); +$options->setSendHttpHeaders(true); - // enable output of HTTP headers - sendHttpHeaders: true, -); +// create a new zipstream object +$zip = new ZipStream\ZipStream('example.zip', $options); // create a file named 'hello.txt' -$zip->addFile( - fileName: 'hello.txt', - data: 'This is the contents of hello.txt', -); +$zip->addFile('hello.txt', 'This is the contents of hello.txt'); // add a file named 'some_image.jpg' from a local file 'path/to/image.jpg' -$zip->addFileFromPath( - fileName: 'some_image.jpg', - path: 'path/to/image.jpg', -); +$zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg'); // finish the zip stream $zip->finish(); ``` -## Upgrade to version 3.1.2 - -- Minimum PHP Version: `8.2` - -## Upgrade to version 3.0.0 - -### General - -- Minimum PHP Version: `8.1` -- Only 64bit Architecture is supported. -- The class `ZipStream\Option\Method` has been replaced with the enum - `ZipStream\CompressionMethod`. -- Most clases have been flagged as `@internal` and should not be used from the - outside. - If you're using internal resources to extend this library, please open an - issue so that a clean interface can be added & published. - The externally available classes & enums are: - - `ZipStream\CompressionMethod` - - `ZipStream\Exception*` - - `ZipStream\ZipStream` - -### Archive Options - -- The class `ZipStream\Option\Archive` has been replaced in favor of named - arguments in the `ZipStream\ZipStream` constuctor. -- The archive options `largeFileSize` & `largeFileMethod` has been removed. If - you want different `compressionMethods` based on the file size, you'll have to - implement this yourself. -- The archive option `httpHeaderCallback` changed the type from `callable` to - `Closure`. -- The archive option `zeroHeader` has been replaced with the option - `defaultEnableZeroHeader` and can be overridden for every file. Its default - value changed from `false` to `true`. -- The archive option `statFiles` was removed since the library no longer checks - filesizes this way. -- The archive option `deflateLevel` has been replaced with the option - `defaultDeflateLevel` and can be overridden for every file. -- The first argument (`name`) of the `ZipStream\ZipStream` constuctor has been - replaced with the named argument `outputName`. -- Headers are now also sent if the `outputName` is empty. If you do not want to - automatically send http headers, set `sendHttpHeaders` to `false`. - -### File Options - -- The class `ZipStream\Option\File` has been replaced in favor of named - arguments in the `ZipStream\ZipStream->addFile*` functions. -- The file option `method` has been renamed to `compressionMethod`. -- The file option `time` has been renamed to `lastModificationDateTime`. -- The file option `size` has been renamed to `maxSize`. - ## Upgrade to version 2.0.0 -https://github.com/maennchen/ZipStream-PHP/tree/2.0.0#upgrade-to-version-200 +- Only the self opened streams will be closed (#139) + If you were relying on ZipStream to close streams that the library didn't open, + you'll need to close them yourself now. ## Upgrade to version 1.0.0 -https://github.com/maennchen/ZipStream-PHP/tree/2.0.0#upgrade-to-version-100 +- All options parameters to all function have been moved from an `array` to structured option objects. See [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Available-options) for examples. +- The whole library has been refactored. The minimal PHP requirement has been raised to PHP 7.1. + +## Usage with Symfony and S3 + +You can find example code on [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Symfony-example). ## Contributing ZipStream-PHP is a collaborative project. Please take a look at the [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) file. -## Version Support - -Versions are supported according to the table below. - -Please do not open any pull requests contradicting the current version support -status. - -Careful: Always check the `README` on `main` for up-to-date information. - -| Version | New Features | Bugfixes | Security | -|---------|--------------|----------|----------| -| *3* | ✓ | ✓ | ✓ | -| *2* | ✗ | ✗ | ✓ | -| *1* | ✗ | ✗ | ✗ | -| *0* | ✗ | ✗ | ✗ | - -This library aligns itself with the PHP core support. New features and bugfixes -will only target PHP versions according to their current status. - -See: https://www.php.net/supported-versions.php - ## About the Authors - Paul Duncan - https://pablotron.org/ @@ -155,3 +81,34 @@ See: https://www.php.net/supported-versions.php - Jesse G. Donat - https://donatstudios.com - Nicolas CARPi - https://www.deltablot.com - Nik Barham - https://www.brokencube.co.uk + +## Contributors + +### Code Contributors + +This project exists thanks to all the people who contribute. +[[Contribute](.github/CONTRIBUTING.md)]. + + +### Financial Contributors + +Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)] + +#### Individuals + + + +#### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/zipstream/contribute)] + + + + + + + + + + + diff --git a/vendor/maennchen/zipstream-php/composer.json b/vendor/maennchen/zipstream-php/composer.json index 6ecd503..2746da1 100644 --- a/vendor/maennchen/zipstream-php/composer.json +++ b/vendor/maennchen/zipstream-php/composer.json @@ -22,42 +22,28 @@ } ], "require": { - "php-64bit": "^8.2", + "php": "^8.0", "ext-mbstring": "*", - "ext-zlib": "*" + "psr/http-message": "^1.0", + "myclabs/php-enum": "^1.5" }, "require-dev": { - "phpunit/phpunit": "^11.0", - "guzzlehttp/guzzle": "^7.5", + "phpunit/phpunit": "^8.5.8 || ^9.4.2", + "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", "ext-zip": "*", "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.5", - "friendsofphp/php-cs-fixer": "^3.16", - "vimeo/psalm": "^6.0", - "brianium/paratest": "^7.7" - }, - "suggest": { - "psr/http-message": "^2.0", - "guzzlehttp/psr7": "^2.4" + "vimeo/psalm": "^5.0", + "php-coveralls/php-coveralls": "^2.4", + "friendsofphp/php-cs-fixer": "^3.9" }, "scripts": { - "format": "php-cs-fixer fix", - "test": [ - "@test:unit", - "@test:formatted", - "@test:lint" - ], - "test:unit:setup-cov": "@putenv XDEBUG_MODE=coverage", - "test:unit": "paratest --functional", - "test:unit:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov"], - "test:unit:slow": "@test:unit --group slow", - "test:unit:slow:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov --group slow"], - "test:unit:fast": "@test:unit --exclude-group slow", - "test:unit:fast:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov --exclude-group slow"], - "test:formatted": "@format --dry-run --stop-on-violation --using-cache=no", - "test:lint": "psalm --stats --show-info=true --find-unused-psalm-suppress", + "format": "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix", + "test": "composer run test:unit && composer run test:formatted && composer run test:lint", + "test:unit": "phpunit --coverage-clover=coverage.clover.xml --coverage-html cov", + "test:formatted": "composer run format -- --dry-run --stop-on-violation --using-cache=no", + "test:lint": "psalm --stats --show-info --find-unused-psalm-suppress", "coverage:report": "php-coveralls --coverage_clover=coverage.clover.xml --json_path=coveralls-upload.json --insecure", - "install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656 --trust-gpg-keys 0x8AC0BAA79732DD42", + "install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656", "docs:generate": "tools/phpdocumentor --sourcecode" }, "autoload": { @@ -65,9 +51,6 @@ "ZipStream\\": "src/" } }, - "autoload-dev": { - "psr-4": { "ZipStream\\Test\\": "test/" } - }, "archive": { "exclude": [ "/composer.lock", diff --git a/vendor/maennchen/zipstream-php/guides/ContentLength.rst b/vendor/maennchen/zipstream-php/guides/ContentLength.rst index 21fea34..e51e692 100644 --- a/vendor/maennchen/zipstream-php/guides/ContentLength.rst +++ b/vendor/maennchen/zipstream-php/guides/ContentLength.rst @@ -1,47 +1,79 @@ Adding Content-Length header ============= -Adding a ``Content-Length`` header for ``ZipStream`` can be achieved by -using the options ``SIMULATION_STRICT`` or ``SIMULATION_LAX`` in the -``operationMode`` parameter. +Adding a ``Content-Length`` header for ``ZipStream`` is not trivial since the +size is not known beforehand. -In the ``SIMULATION_STRICT`` mode, ``ZipStream`` will not allow to calculate the -size based on reading the whole file. ``SIMULATION_LAX`` will read the whole -file if neccessary. - -``SIMULATION_STRICT`` is therefore useful to make sure that the size can be -calculated efficiently. +The following workaround adds an approximated header: .. code-block:: php - use ZipStream\OperationMode; - use ZipStream\ZipStream; - $zip = new ZipStream( - operationMode: OperationMode::SIMULATE_STRICT, // or SIMULATE_LAX - defaultEnableZeroHeader: false, - sendHttpHeaders: true, - outputStream: $stream, - ); + class Zip + { + /** @var string */ + private $name; - // Normally add files - $zip->addFile('sample.txt', 'Sample String Data'); + private $files = []; - // Use addFileFromCallback and exactSize if you want to defer opening of - // the file resource - $zip->addFileFromCallback( - 'sample.txt', - exactSize: 18, - callback: function () { - return fopen('...'); + public function __construct($name) + { + $this->name = $name; } - ); - // Read resulting file size - $size = $zip->finish(); - - // Tell it to the browser - header('Content-Length: '. $size); - - // Execute the Simulation and stream the actual zip to the client - $zip->executeSimulation(); + public function addFile($name, $data) + { + $this->files[] = ['type' => 'addFile', 'name' => $name, 'data' => $data]; + } + public function addFileFromPath($name, $path) + { + $this->files[] = ['type' => 'addFileFromPath', 'name' => $name, 'path' => $path]; + } + + public function getEstimate() + { + $estimate = 22; + foreach ($this->files as $file) { + $estimate += 76 + 2 * strlen($file['name']); + if ($file['type'] === 'addFile') { + $estimate += strlen($file['data']); + } + if ($file['type'] === 'addFileFromPath') { + $estimate += filesize($file['path']); + } + } + return $estimate; + } + + public function finish() + { + header('Content-Length: ' . $this->getEstimate()); + $options = new \ZipStream\Option\Archive(); + $options->setSendHttpHeaders(true); + $options->setEnableZip64(false); + $options->setDeflateLevel(-1); + $zip = new \ZipStream\ZipStream($this->name, $options); + + $fileOptions = new \ZipStream\Option\File(); + $fileOptions->setMethod(\ZipStream\Option\Method::STORE()); + foreach ($this->files as $file) { + if ($file['type'] === 'addFile') { + $zip->addFile($file['name'], $file['data'], $fileOptions); + } + if ($file['type'] === 'addFileFromPath') { + $zip->addFileFromPath($file['name'], $file['path'], $fileOptions); + } + } + $zip->finish(); + exit; + } + } + +It only works with the following constraints: + +- All file content is known beforehand. +- Content Deflation is disabled + +Thanks to +`partiellkorrekt `_ +for this workaround. \ No newline at end of file diff --git a/vendor/maennchen/zipstream-php/guides/FlySystem.rst b/vendor/maennchen/zipstream-php/guides/FlySystem.rst index 4e6c6fb..0243f24 100644 --- a/vendor/maennchen/zipstream-php/guides/FlySystem.rst +++ b/vendor/maennchen/zipstream-php/guides/FlySystem.rst @@ -14,21 +14,20 @@ default one, and pass it to Flysystem ``putStream`` method. // the content is lost when closing the stream / opening another one $tempStream = fopen('php://memory', 'w+'); + // Init Options + $zipStreamOptions = new Archive(); + $zipStreamOptions->setOutputStream($tempStream); + // Create Zip Archive - $zipStream = new ZipStream( - outputStream: $tempStream, - outputName: 'test.zip', - ); + $zipStream = new ZipStream('test.zip', $zipStreamOptions); $zipStream->addFile('test.txt', 'text'); $zipStream->finish(); - // Store File - // (see Flysystem documentation, and all its framework integration) - // Can be any adapter (AWS, Google, Ftp, etc.) - $adapter = new Local(__DIR__.'/path/to/folder'); + // Store File (see Flysystem documentation, and all its framework integration) + $adapter = new Local(__DIR__.'/path/to/folder'); // Can be any adapter (AWS, Google, Ftp, etc.) $filesystem = new Filesystem($adapter); - $filesystem->writeStream('test.zip', $tempStream) + $filesystem->putStream('test.zip', $tempStream) // Close Stream - fclose($tempStream); + fclose($tempStream); \ No newline at end of file diff --git a/vendor/maennchen/zipstream-php/guides/Options.rst b/vendor/maennchen/zipstream-php/guides/Options.rst index 5e92e94..eabaa6f 100644 --- a/vendor/maennchen/zipstream-php/guides/Options.rst +++ b/vendor/maennchen/zipstream-php/guides/Options.rst @@ -2,65 +2,60 @@ Available options =============== Here is the full list of options available to you. You can also have a look at -``src/ZipStream.php`` file. +``src/Option/Archive.php`` file. + +First, an instance of ``ZipStream\Option\Archive`` needs to be created, and +after that you use setters methods to modify the values. .. code-block:: php - use ZipStream\ZipStream; + use ZipStream\Option\Archive as ArchiveOptions; require_once 'vendor/autoload.php'; - $zip = new ZipStream( - // Define output stream - // (argument is eiter a resource or implementing - // `Psr\Http\Message\StreamInterface`) - // - // Setup with `psr/http-message` & `guzzlehttp/psr7` dependencies - // required when using `Psr\Http\Message\StreamInterface`. - outputStream: $filePointer, + $opt = new ArchiveOptions(); - // Set the deflate level (default is 6; use -1 to disable it) - defaultDeflateLevel: 6, + // Define output stream (argument is of type resource) + $opt->setOutputStream($fd); - // Add a comment to the zip file - comment: 'This is a comment.', + // Set the deflate level (default is 6; use -1 to disable it) + $opt->setDeflateLevel(6); - // Send http headers (default is true) - sendHttpHeaders: false, + // Add a comment to the zip file + $opt->setComment('This is a comment.'); - // HTTP Content-Disposition. - // Defaults to 'attachment', where FILENAME is the specified filename. - // Note that this does nothing if you are not sending HTTP headers. - contentDisposition: 'attachment', + // Size, in bytes, of the largest file to try and load into memory (used by addFileFromPath()). Large files may also be compressed differently; see the 'largeFileMethod' option. + $opt->setLargeFileSize(30000000); - // Output Name for HTTP Content-Disposition - // Defaults to no name - outputName: "example.zip", + // How to handle large files. Legal values are STORE (the default), or DEFLATE. Store sends the file raw and is significantly faster, while DEFLATE compresses the file and is much, much slower. Note that deflate must compress the file twice and is extremely slow. + $opt->setLargeFileMethod(ZipStream\Option\Method::STORE()); + $opt->setLargeFileMethod(ZipStream\Option\Method::DEFLATE()); - // HTTP Content-Type. - // Defaults to 'application/x-zip'. - // Note that this does nothing if you are not sending HTTP headers. - contentType: 'application/x-zip', + // Send http headers (default is false) + $opt->setSendHttpHeaders(false); - // Set the function called for setting headers. - // Default is the `header()` of PHP - httpHeaderCallback: header(...), + // HTTP Content-Disposition. Defaults to 'attachment', where FILENAME is the specified filename. Note that this does nothing if you are not sending HTTP headers. + $opt->setContentDisposition('attachment'); - // Enable streaming files with single read where general purpose bit 3 - // indicates local file header contain zero values in crc and size - // fields, these appear only after file contents in data descriptor - // block. - // Set to true if your input stream is remote - // (used with addFileFromStream()). - // Default is false. - defaultEnableZeroHeader: false, + // Set the content type (does nothing if you are not sending HTTP headers) + $opt->setContentType('application/x-zip'); - // Enable zip64 extension, allowing very large archives - // (> 4Gb or file count > 64k) - // Default is true - enableZip64: true, + // Set the function called for setting headers. Default is the `header()` of PHP + $opt->setHttpHeaderCallback('header'); - // Flush output buffer after every write - // Default is false - flushOutput: true, - ); + // Enable streaming files with single read where general purpose bit 3 indicates local file header contain zero values in crc and size fields, these appear only after file contents in data descriptor block. Default is false. Set to true if your input stream is remote (used with addFileFromStream()). + $opt->setZeroHeader(false); + + // Enable reading file stat for determining file size. When a 32-bit system reads file size that is over 2 GB, invalid value appears in file size due to integer overflow. Should be disabled on 32-bit systems with method addFileFromPath if any file may exceed 2 GB. In this case file will be read in blocks and correct size will be determined from content. Default is true. + $opt->setStatFiles(true); + + // Enable zip64 extension, allowing very large archives (> 4Gb or file count > 64k) + // default is true + $opt->setEnableZip64(true); + + // Flush output buffer after every write + // default is false + $opt->setFlushOutput(true); + + // Now that everything is set you can pass the options to the ZipStream instance + $zip = new ZipStream('example.zip', $opt); diff --git a/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst b/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst index 22af71d..4b4ca4b 100644 --- a/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst +++ b/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst @@ -12,10 +12,7 @@ Example --------------- .. code-block:: php - + $stream = $response->getBody(); // add a file named 'streamfile.txt' from the content of the stream - $zip->addFileFromPsr7Stream( - fileName: 'streamfile.txt', - stream: $stream, - ); + $zip->addFileFromPsr7Stream('streamfile.txt', $stream); diff --git a/vendor/maennchen/zipstream-php/guides/StreamOutput.rst b/vendor/maennchen/zipstream-php/guides/StreamOutput.rst index 9f3165b..1a0495f 100644 --- a/vendor/maennchen/zipstream-php/guides/StreamOutput.rst +++ b/vendor/maennchen/zipstream-php/guides/StreamOutput.rst @@ -5,9 +5,9 @@ Stream to S3 Bucket --------------- .. code-block:: php - use Aws\S3\S3Client; use Aws\Credentials\CredentialProvider; + use ZipStream\Option\Archive; use ZipStream\ZipStream; $bucket = 'your bucket name'; @@ -21,19 +21,13 @@ Stream to S3 Bucket $zipFile = fopen("s3://$bucket/example.zip", 'w'); - $zip = new ZipStream( - enableZip64: false, - outputStream: $zipFile, - ); + $options = new Archive(); + $options->setEnableZip64(false); + $options->setOutputStream($zipFile); - $zip->addFile( - fileName: 'file1.txt', - data: 'File1 data', - ); - $zip->addFile( - fileName: 'file2.txt', - data: 'File2 data', - ); + $zip = new ZipStream(null, $options); + $zip->addFile('file1.txt', 'File1 data'); + $zip->addFile('file2.txt', 'File2 data'); $zip->finish(); - fclose($zipFile); + fclose($zipFile); \ No newline at end of file diff --git a/vendor/maennchen/zipstream-php/guides/Symfony.rst b/vendor/maennchen/zipstream-php/guides/Symfony.rst index 902552c..18f9059 100644 --- a/vendor/maennchen/zipstream-php/guides/Symfony.rst +++ b/vendor/maennchen/zipstream-php/guides/Symfony.rst @@ -31,7 +31,7 @@ stored in an AWS S3 bucket by key: */ public function zipStreamAction() { - // sample test file on s3 + //sample test file on s3 $s3keys = array( "ziptestfolder/file1.txt" ); @@ -39,18 +39,18 @@ stored in an AWS S3 bucket by key: $s3Client = $this->get('app.amazon.s3'); //s3client service $s3Client->registerStreamWrapper(); //required - // using StreamedResponse to wrap ZipStream functionality - // for files on AWS s3. + //using StreamedResponse to wrap ZipStream functionality for files on AWS s3. $response = new StreamedResponse(function() use($s3keys, $s3Client) { // Define suitable options for ZipStream Archive. + $options = new \ZipStream\Option\Archive(); + $options->setContentType('application/octet-stream'); // this is needed to prevent issues with truncated zip files + $options->setZeroHeader(true); + $options->setComment('test zip file.'); + //initialise zipstream with output zip filename and options. - $zip = new ZipStream\ZipStream( - outputName: 'test.zip', - defaultEnableZeroHeader: true, - contentType: 'application/octet-stream', - ); + $zip = new ZipStream\ZipStream('test.zip', $options); //loop keys - useful for multiple files foreach ($s3keys as $key) { @@ -58,19 +58,15 @@ stored in an AWS S3 bucket by key: //file using the same name. $fileName = basename($key); - // concatenate s3path. - // replace with your bucket name or get from parameters file. - $bucket = 'bucketname'; + //concatenate s3path. + $bucket = 'bucketname'; //replace with your bucket name or get from parameters file. $s3path = "s3://" . $bucket . "/" . $key; //addFileFromStream if ($streamRead = fopen($s3path, 'r')) { - $zip->addFileFromStream( - fileName: $fileName, - stream: $streamRead, - ); + $zip->addFileFromStream($fileName, $streamRead); } else { - die('Could not open stream for reading'); + die('Could not open stream for reading'); } } @@ -127,4 +123,4 @@ You need to add correct permissions 's3' => ['ACL' => 'public-read'], ]); - fopen($path, 'w', null, $outputContext); + fopen($path, 'w', null, $outputContext); \ No newline at end of file diff --git a/vendor/maennchen/zipstream-php/guides/index.rst b/vendor/maennchen/zipstream-php/guides/index.rst index 48f465a..67f504b 100644 --- a/vendor/maennchen/zipstream-php/guides/index.rst +++ b/vendor/maennchen/zipstream-php/guides/index.rst @@ -22,20 +22,11 @@ Installation Simply add a dependency on ``maennchen/zipstream-php`` to your project's ``composer.json`` file if you use Composer to manage the dependencies of your -project. Use following command to add the package to your project's -dependencies: +project. Use following command to add the package to your project's dependencies: .. code-block:: sh composer require maennchen/zipstream-php -If you want to use``addFileFromPsr7Stream``` -(``Psr\Http\Message\StreamInterface``) or use a stream instead of a -``resource`` as ``outputStream``, the following dependencies must be installed -as well: - -.. code-block:: sh - composer require psr/http-message guzzlehttp/psr7 - If ``composer install`` yields the following error, your installation is missing the `mbstring extension `_, either `install it `_ @@ -61,42 +52,25 @@ Here's a simple example: // Autoload the dependencies require 'vendor/autoload.php'; - // create a new zipstream object - $zip = new ZipStream\ZipStream( - outputName: 'example.zip', + // enable output of HTTP headers + $options = new ZipStream\Option\Archive(); + $options->setSendHttpHeaders(true); - // enable output of HTTP headers - sendHttpHeaders: true, - ); + // create a new zipstream object + $zip = new ZipStream\ZipStream('example.zip', $options); // create a file named 'hello.txt' - $zip->addFile( - fileName: 'hello.txt', - data: 'This is the contents of hello.txt', - ); + $zip->addFile('hello.txt', 'This is the contents of hello.txt'); // add a file named 'some_image.jpg' from a local file 'path/to/image.jpg' - $zip->addFileFromPath( - fileName: 'some_image.jpg', - path: 'path/to/image.jpg', - ); + $zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg'); // add a file named 'goodbye.txt' from an open stream resource - $filePointer = tmpfile(); - fwrite($filePointer, 'The quick brown fox jumped over the lazy dog.'); - rewind($filePointer); - $zip->addFileFromStream( - fileName: 'goodbye.txt', - stream: $filePointer, - ); - fclose($filePointer); - - // add a file named 'streamfile.txt' from the body of a `guzzle` response - // Setup with `psr/http-message` & `guzzlehttp/psr7` dependencies required. - $zip->addFileFromPsr7Stream( - fileName: 'streamfile.txt', - stream: $response->getBody(), - ); + $fp = tmpfile(); + fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); + rewind($fp); + $zip->addFileFromStream('goodbye.txt', $fp); + fclose($fp); // finish the zip stream $zip->finish(); @@ -113,7 +87,7 @@ The native Mac OS archive extraction tool prior to macOS 10.15 might not open archives in some conditions. A workaround is to disable the Zip64 feature with the option ``enableZip64: false``. This limits the archive to 4 Gb and 64k files but will allow users on macOS 10.14 and below to open them without issue. -See `#116 `_. +See `#116 `_. The linux ``unzip`` utility might not handle properly unicode characters. It is recommended to extract with another tool like diff --git a/vendor/maennchen/zipstream-php/phpunit.xml.dist b/vendor/maennchen/zipstream-php/phpunit.xml.dist index 1b02a3a..8a2f318 100644 --- a/vendor/maennchen/zipstream-php/phpunit.xml.dist +++ b/vendor/maennchen/zipstream-php/phpunit.xml.dist @@ -1,15 +1,14 @@ - - + + + + src + + test - - - src - - diff --git a/vendor/maennchen/zipstream-php/psalm.xml b/vendor/maennchen/zipstream-php/psalm.xml index 56af0e6..4e4c4f6 100644 --- a/vendor/maennchen/zipstream-php/psalm.xml +++ b/vendor/maennchen/zipstream-php/psalm.xml @@ -1,25 +1,53 @@ - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/maennchen/zipstream-php/src/Bigint.php b/vendor/maennchen/zipstream-php/src/Bigint.php new file mode 100644 index 0000000..f2565e9 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Bigint.php @@ -0,0 +1,174 @@ +fillBytes($value, 0, 8); + } + + /** + * Get an instance + * + * @param int $value + * @return Bigint + */ + public static function init(int $value = 0): self + { + return new self($value); + } + + /** + * Fill bytes from low to high + * + * @param int $low + * @param int $high + * @return Bigint + */ + public static function fromLowHigh(int $low, int $high): self + { + $bigint = new self(); + $bigint->fillBytes($low, 0, 4); + $bigint->fillBytes($high, 4, 4); + return $bigint; + } + + /** + * Get high 32 + * + * @return int + */ + public function getHigh32(): int + { + return $this->getValue(4, 4); + } + + /** + * Get value from bytes array + * + * @param int $end + * @param int $length + * @return int + */ + public function getValue(int $end = 0, int $length = 8): int + { + $result = 0; + for ($i = $end + $length - 1; $i >= $end; $i--) { + $result <<= 8; + $result |= $this->bytes[$i]; + } + return $result; + } + + /** + * Get low FF + * + * @param bool $force + * @return float + */ + public function getLowFF(bool $force = false): float + { + if ($force || $this->isOver32()) { + return (float)0xFFFFFFFF; + } + return (float)$this->getLow32(); + } + + /** + * Check if is over 32 + * + * @psalm-suppress ArgumentTypeCoercion + * @param bool $force + * @return bool + */ + public function isOver32(bool $force = false): bool + { + // value 0xFFFFFFFF already needs a Zip64 header + return $force || + max(array_slice($this->bytes, 4, 4)) > 0 || + min(array_slice($this->bytes, 0, 4)) === 0xFF; + } + + /** + * Get low 32 + * + * @return int + */ + public function getLow32(): int + { + return $this->getValue(0, 4); + } + + /** + * Get hexadecimal + * + * @return string + */ + public function getHex64(): string + { + $result = '0x'; + for ($i = 7; $i >= 0; $i--) { + $result .= sprintf('%02X', $this->bytes[$i]); + } + return $result; + } + + /** + * Add + * + * @param Bigint $other + * @return Bigint + */ + public function add(self $other): self + { + $result = clone $this; + $overflow = false; + for ($i = 0; $i < 8; $i++) { + $result->bytes[$i] += $other->bytes[$i]; + if ($overflow) { + $result->bytes[$i]++; + $overflow = false; + } + if ($result->bytes[$i] & 0x100) { + $overflow = true; + $result->bytes[$i] &= 0xFF; + } + } + if ($overflow) { + throw new OverflowException(); + } + return $result; + } + + /** + * Fill the bytes field with int + * + * @param int $value + * @param int $start + * @param int $count + * @return void + */ + protected function fillBytes(int $value, int $start, int $count): void + { + for ($i = 0; $i < $count; $i++) { + $this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF; + $value >>= 8; + } + } +} diff --git a/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php b/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php deleted file mode 100644 index ffcfc6e..0000000 --- a/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php +++ /dev/null @@ -1,52 +0,0 @@ -value), - new PackField(format: 'V', value: Time::dateTimeToDosTime($lastModificationDateTime)), - new PackField(format: 'V', value: $crc32), - new PackField(format: 'V', value: $compressedSize), - new PackField(format: 'V', value: $uncompressedSize), - new PackField(format: 'v', value: strlen($fileName)), - new PackField(format: 'v', value: strlen($extraField)), - new PackField(format: 'v', value: strlen($fileComment)), - new PackField(format: 'v', value: $diskNumberStart), - new PackField(format: 'v', value: $internalFileAttributes), - new PackField(format: 'V', value: $externalFileAttributes), - new PackField(format: 'V', value: $relativeOffsetOfLocalHeader), - ) . $fileName . $extraField . $fileComment; - } -} diff --git a/vendor/maennchen/zipstream-php/src/CompressionMethod.php b/vendor/maennchen/zipstream-php/src/CompressionMethod.php deleted file mode 100644 index 51e4363..0000000 --- a/vendor/maennchen/zipstream-php/src/CompressionMethod.php +++ /dev/null @@ -1,106 +0,0 @@ -format(DateTimeInterface::ATOM) . " can't be represented as DOS time / date."); - } -} diff --git a/vendor/maennchen/zipstream-php/src/Exception/EncodingException.php b/vendor/maennchen/zipstream-php/src/Exception/EncodingException.php new file mode 100644 index 0000000..5b0267d --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Exception/EncodingException.php @@ -0,0 +1,14 @@ +resource = $resource; - parent::__construct('Function ' . $function . 'failed on resource.'); - } -} diff --git a/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php b/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php deleted file mode 100644 index 717c1aa..0000000 --- a/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php +++ /dev/null @@ -1,19 +0,0 @@ -fileName = self::filterFilename($fileName); - $this->checkEncoding(); + public $opt; - if ($this->enableZeroHeader) { - $this->generalPurposeBitFlag |= GeneralPurposeBitFlag::ZERO_HEADER; - } + /** + * @var Bigint + */ + public $len; - $this->version = $this->compressionMethod === CompressionMethod::DEFLATE ? Version::DEFLATE : Version::STORE; + /** + * @var Bigint + */ + public $zlen; + + /** @var int */ + public $crc; + + /** + * @var Bigint + */ + public $hlen; + + /** + * @var Bigint + */ + public $ofs; + + /** + * @var int + */ + public $bits; + + /** + * @var Version + */ + public $version; + + /** + * @var ZipStream + */ + public $zip; + + /** + * @var resource + */ + private $deflate; + + /** + * @var HashContext + */ + private $hash; + + /** + * @var Method + */ + private $method; + + /** + * @var Bigint + */ + private $totalLength; + + public function __construct(ZipStream $zip, string $name, ?FileOptions $opt = null) + { + $this->zip = $zip; + + $this->name = $name; + $this->opt = $opt ?: new FileOptions(); + $this->method = $this->opt->getMethod(); + $this->version = Version::STORE(); + $this->ofs = new Bigint(); } - public function cloneSimulationExecution(): self + public function processPath(string $path): void { - return new self( - $this->fileName, - $this->dataCallback, - OperationMode::NORMAL, - $this->startOffset, - $this->compressionMethod, - $this->comment, - $this->lastModificationDateTime, - $this->deflateLevel, - $this->maxSize, - $this->exactSize, - $this->enableZip64, - $this->enableZeroHeader, - $this->send, - $this->recordSentBytes, - ); - } - - public function process(): string - { - $forecastSize = $this->forecastSize(); - - if ($this->enableZeroHeader) { - // No calculation required - } elseif ($this->isSimulation() && $forecastSize !== null) { - $this->uncompressedSize = $forecastSize; - $this->compressedSize = $forecastSize; - } else { - $this->readStream(send: false); - if (rewind($this->unpackStream()) === false) { - throw new ResourceActionException('rewind', $this->unpackStream()); + if (!is_readable($path)) { + if (!file_exists($path)) { + throw new FileNotFoundException($path); } + throw new FileNotReadableException($path); } - - $this->addFileHeader(); - - $detectedSize = $forecastSize ?? ($this->compressedSize > 0 ? $this->compressedSize : null); - - if ( - $this->isSimulation() && - $detectedSize !== null - ) { - $this->uncompressedSize = $detectedSize; - $this->compressedSize = $detectedSize; - ($this->recordSentBytes)($detectedSize); + if ($this->zip->isLargeFile($path) === false) { + $data = file_get_contents($path); + $this->processData($data); } else { - $this->readStream(send: true); + $this->method = $this->zip->opt->getLargeFileMethod(); + + $stream = new Stream(fopen($path, 'rb')); + $this->processStream($stream); + $stream->close(); + } + } + + public function processData(string $data): void + { + $this->len = new Bigint(strlen($data)); + $this->crc = crc32($data); + + // compress data if needed + if ($this->method->equals(Method::DEFLATE())) { + $data = gzdeflate($data); } + $this->zlen = new Bigint(strlen($data)); + $this->addFileHeader(); + $this->zip->send($data); $this->addFileFooter(); - return $this->getCdrFile(); - } - - /** - * @return resource - */ - private function unpackStream() - { - if ($this->stream) { - return $this->stream; - } - - if ($this->operationMode === OperationMode::SIMULATE_STRICT) { - throw new SimulationFileUnknownException(); - } - - $this->stream = ($this->dataCallback)(); - - if (!$this->enableZeroHeader && !stream_get_meta_data($this->stream)['seekable']) { - throw new StreamNotSeekableException(); - } - if (!( - str_contains(stream_get_meta_data($this->stream)['mode'], 'r') - || str_contains(stream_get_meta_data($this->stream)['mode'], 'w+') - || str_contains(stream_get_meta_data($this->stream)['mode'], 'a+') - || str_contains(stream_get_meta_data($this->stream)['mode'], 'x+') - || str_contains(stream_get_meta_data($this->stream)['mode'], 'c+') - )) { - throw new StreamNotReadableException(); - } - - return $this->stream; - } - - private function forecastSize(): ?int - { - if ($this->compressionMethod !== CompressionMethod::STORE) { - return null; - } - if ($this->exactSize !== null) { - return $this->exactSize; - } - $fstat = fstat($this->unpackStream()); - if (!$fstat || !array_key_exists('size', $fstat) || $fstat['size'] < 1) { - return null; - } - - if ($this->maxSize !== null && $this->maxSize < $fstat['size']) { - return $this->maxSize; - } - - return $fstat['size']; } /** * Create and send zip header for this file. + * + * @return void + * @throws \ZipStream\Exception\EncodingException */ - private function addFileHeader(): void + public function addFileHeader(): void { - $forceEnableZip64 = $this->enableZeroHeader && $this->enableZip64; + $name = static::filterFilename($this->name); - $footer = $this->buildZip64ExtraBlock($forceEnableZip64); + // calculate name length + $nameLength = strlen($name); - $zip64Enabled = $footer !== ''; + // create dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); - if ($zip64Enabled) { - $this->version = Version::ZIP64; + $comment = $this->opt->getComment(); + + if (!mb_check_encoding($name, 'ASCII') || + !mb_check_encoding($comment, 'ASCII')) { + // Sets Bit 11: Language encoding flag (EFS). If this bit is set, + // the filename and comment fields for this file + // MUST be encoded using UTF-8. (see APPENDIX D) + if (mb_check_encoding($name, 'UTF-8') && + mb_check_encoding($comment, 'UTF-8')) { + $this->bits |= self::BIT_EFS_UTF8; + } } - if ($this->generalPurposeBitFlag & GeneralPurposeBitFlag::EFS) { - // Put the tricky entry to - // force Linux unzip to lookup EFS flag. - $footer .= Zs\ExtendedInformationExtraField::generate(); + if ($this->method->equals(Method::DEFLATE())) { + $this->version = Version::DEFLATE(); } - $data = LocalFileHeader::generate( - versionNeededToExtract: $this->version->value, - generalPurposeBitFlag: $this->generalPurposeBitFlag, - compressionMethod: $this->compressionMethod, - lastModificationDateTime: $this->lastModificationDateTime, - crc32UncompressedData: $this->crc, - compressedSize: $zip64Enabled - ? 0xFFFFFFFF - : $this->compressedSize, - uncompressedSize: $zip64Enabled - ? 0xFFFFFFFF - : $this->uncompressedSize, - fileName: $this->fileName, - extraField: $footer, - ); + $force = (bool)($this->bits & self::BIT_ZERO_HEADER) && + $this->zip->opt->isEnableZip64(); + $footer = $this->buildZip64ExtraBlock($force); - ($this->send)($data); + // If this file will start over 4GB limit in ZIP file, + // CDR record will have to use Zip64 extension to describe offset + // to keep consistency we use the same value here + if ($this->zip->ofs->isOver32()) { + $this->version = Version::ZIP64(); + } + + $fields = [ + ['V', ZipStream::FILE_HEADER_SIGNATURE], + ['v', $this->version->getValue()], // Version needed to Extract + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 of data (0 -> moved to data descriptor footer) + ['V', $this->zlen->getLowFF($force)], // Length of compressed data (forced to 0xFFFFFFFF for zero header) + ['V', $this->len->getLowFF($force)], // Length of original data (forced to 0xFFFFFFFF for zero header) + ['v', $nameLength], // Length of filename + ['v', strlen($footer)], // Extra data (see above) + ]; + + // pack fields and calculate "total" length + $header = ZipStream::packFields($fields); + + // print header and filename + $data = $header . $name . $footer; + $this->zip->send($data); + + // save header length + $this->hlen = Bigint::init(strlen($data)); } /** * Strip characters that are not legal in Windows filenames * to prevent compatibility issues + * + * @param string $filename Unprocessed filename + * @return string */ - private static function filterFilename( - /** - * Unprocessed filename - */ - string $fileName - ): string { + public static function filterFilename(string $filename): string + { // strip leading slashes from file name // (fixes bug in windows archive viewer) - $fileName = ltrim($fileName, '/'); + $filename = preg_replace('/^\\/+/', '', $filename); - return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $fileName); + return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename); } - private function checkEncoding(): void + /** + * Create and send data descriptor footer for this file. + * + * @return void + */ + public function addFileFooter(): void { - // Sets Bit 11: Language encoding flag (EFS). If this bit is set, - // the filename and comment fields for this file - // MUST be encoded using UTF-8. (see APPENDIX D) - if (mb_check_encoding($this->fileName, 'UTF-8') && - mb_check_encoding($this->comment, 'UTF-8')) { - $this->generalPurposeBitFlag |= GeneralPurposeBitFlag::EFS; - } - } + if ($this->bits & self::BIT_ZERO_HEADER) { + // compressed and uncompressed size + $sizeFormat = 'V'; + if ($this->zip->opt->isEnableZip64()) { + $sizeFormat = 'P'; + } + $fields = [ + ['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE], + ['V', $this->crc], // CRC32 + [$sizeFormat, $this->zlen], // Length of compressed data + [$sizeFormat, $this->len], // Length of original data + ]; - private function buildZip64ExtraBlock(bool $force = false): string - { - $outputZip64ExtraBlock = false; - - $originalSize = null; - if ($force || $this->uncompressedSize > 0xFFFFFFFF) { - $outputZip64ExtraBlock = true; - $originalSize = $this->uncompressedSize; - } - - $compressedSize = null; - if ($force || $this->compressedSize > 0xFFFFFFFF) { - $outputZip64ExtraBlock = true; - $compressedSize = $this->compressedSize; - } - - // If this file will start over 4GB limit in ZIP file, - // CDR record will have to use Zip64 extension to describe offset - // to keep consistency we use the same value here - $relativeHeaderOffset = null; - if ($this->startOffset > 0xFFFFFFFF) { - $outputZip64ExtraBlock = true; - $relativeHeaderOffset = $this->startOffset; - } - - if (!$outputZip64ExtraBlock) { - return ''; - } - - if (!$this->enableZip64) { - throw new OverflowException(); - } - - return Zip64\ExtendedInformationExtraField::generate( - originalSize: $originalSize, - compressedSize: $compressedSize, - relativeHeaderOffset: $relativeHeaderOffset, - diskStartNumber: null, - ); - } - - private function addFileFooter(): void - { - if (($this->compressedSize > 0xFFFFFFFF || $this->uncompressedSize > 0xFFFFFFFF) && $this->version !== Version::ZIP64) { - throw new OverflowException(); - } - - if (!$this->enableZeroHeader) { - return; - } - - if ($this->version === Version::ZIP64) { - $footer = Zip64\DataDescriptor::generate( - crc32UncompressedData: $this->crc, - compressedSize: $this->compressedSize, - uncompressedSize: $this->uncompressedSize, - ); + $footer = ZipStream::packFields($fields); + $this->zip->send($footer); } else { - $footer = DataDescriptor::generate( - crc32UncompressedData: $this->crc, - compressedSize: $this->compressedSize, - uncompressedSize: $this->uncompressedSize, - ); + $footer = ''; } - - ($this->send)($footer); + $this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer))); + $this->zip->addToCdr($this); } - private function readStream(bool $send): void + public function processStream(StreamInterface $stream): void { - $this->compressedSize = 0; - $this->uncompressedSize = 0; - $hash = hash_init('crc32b'); + $this->zlen = new Bigint(); + $this->len = new Bigint(); - $deflate = $this->compressionInit(); - - while ( - !feof($this->unpackStream()) && - ($this->maxSize === null || $this->uncompressedSize < $this->maxSize) && - ($this->exactSize === null || $this->uncompressedSize < $this->exactSize) - ) { - $readLength = min( - ($this->maxSize ?? PHP_INT_MAX) - $this->uncompressedSize, - ($this->exactSize ?? PHP_INT_MAX) - $this->uncompressedSize, - self::CHUNKED_READ_BLOCK_SIZE - ); - - $data = fread($this->unpackStream(), $readLength); - - if ($data === false) { - throw new ResourceActionException('fread', $this->unpackStream()); - } - - hash_update($hash, $data); - - $this->uncompressedSize += strlen($data); - - if ($deflate) { - $data = deflate_add( - $deflate, - $data, - feof($this->unpackStream()) ? ZLIB_FINISH : ZLIB_NO_FLUSH - ); - - if ($data === false) { - throw new RuntimeException('deflate_add failed'); - } - } - - $this->compressedSize += strlen($data); - - if ($send) { - ($this->send)($data); - } - } - - if ($this->exactSize !== null && $this->uncompressedSize !== $this->exactSize) { - throw new FileSizeIncorrectException(expectedSize: $this->exactSize, actualSize: $this->uncompressedSize); - } - - $this->crc = hexdec(hash_final($hash)); - } - - private function compressionInit(): ?DeflateContext - { - switch ($this->compressionMethod) { - case CompressionMethod::STORE: - // Noting to do - return null; - case CompressionMethod::DEFLATE: - $deflateContext = deflate_init( - ZLIB_ENCODING_RAW, - ['level' => $this->deflateLevel] - ); - - if (!$deflateContext) { - // @codeCoverageIgnoreStart - throw new RuntimeException("Can't initialize deflate context."); - // @codeCoverageIgnoreEnd - } - - // False positive, resource is no longer returned from this function - return $deflateContext; - default: - // @codeCoverageIgnoreStart - throw new RuntimeException('Unsupported Compression Method ' . print_r($this->compressionMethod, true)); - // @codeCoverageIgnoreEnd + if ($this->zip->opt->isZeroHeader()) { + $this->processStreamWithZeroHeader($stream); + } else { + $this->processStreamWithComputedHeader($stream); } } - private function getCdrFile(): string + /** + * Send CDR record for specified file. + * + * @return string + */ + public function getCdrFile(): string { + $name = static::filterFilename($this->name); + + // get attributes + $comment = $this->opt->getComment(); + + // get dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); + $footer = $this->buildZip64ExtraBlock(); - return CentralDirectoryFileHeader::generate( - versionMadeBy: ZipStream::ZIP_VERSION_MADE_BY, - versionNeededToExtract: $this->version->value, - generalPurposeBitFlag: $this->generalPurposeBitFlag, - compressionMethod: $this->compressionMethod, - lastModificationDateTime: $this->lastModificationDateTime, - crc32: $this->crc, - compressedSize: $this->compressedSize > 0xFFFFFFFF - ? 0xFFFFFFFF - : $this->compressedSize, - uncompressedSize: $this->uncompressedSize > 0xFFFFFFFF - ? 0xFFFFFFFF - : $this->uncompressedSize, - fileName: $this->fileName, - extraField: $footer, - fileComment: $this->comment, - diskNumberStart: 0, - internalFileAttributes: 0, - externalFileAttributes: 32, - relativeOffsetOfLocalHeader: $this->startOffset > 0xFFFFFFFF - ? 0xFFFFFFFF - : $this->startOffset, - ); + $fields = [ + ['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature + ['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version + ['v', $this->version->getValue()], // Extract by version + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 + ['V', $this->zlen->getLowFF()], // Compressed Data Length + ['V', $this->len->getLowFF()], // Original Data Length + ['v', strlen($name)], // Length of filename + ['v', strlen($footer)], // Extra data len (see above) + ['v', strlen($comment)], // Length of comment + ['v', 0], // Disk number + ['v', 0], // Internal File Attributes + ['V', 32], // External File Attributes + ['V', $this->ofs->getLowFF()], // Relative offset of local header + ]; + + // pack fields, then append name and comment + $header = ZipStream::packFields($fields); + + return $header . $name . $footer . $comment; } - private function isSimulation(): bool + /** + * @return Bigint + */ + public function getTotalLength(): Bigint { - return $this->operationMode === OperationMode::SIMULATE_LAX || $this->operationMode === OperationMode::SIMULATE_STRICT; + return $this->totalLength; + } + + /** + * Convert a UNIX timestamp to a DOS timestamp. + * + * @param int $when + * @return int DOS Timestamp + */ + final protected static function dosTime(int $when): int + { + // get date array for timestamp + $d = getdate($when); + + // set lower-bound on dates + if ($d['year'] < 1980) { + $d = [ + 'year' => 1980, + 'mon' => 1, + 'mday' => 1, + 'hours' => 0, + 'minutes' => 0, + 'seconds' => 0, + ]; + } + + // remove extra years from 1980 + $d['year'] -= 1980; + + // return date string + return + ($d['year'] << 25) | + ($d['mon'] << 21) | + ($d['mday'] << 16) | + ($d['hours'] << 11) | + ($d['minutes'] << 5) | + ($d['seconds'] >> 1); + } + + protected function buildZip64ExtraBlock(bool $force = false): string + { + $fields = []; + if ($this->len->isOver32($force)) { + $fields[] = ['P', $this->len]; // Length of original data + } + + if ($this->len->isOver32($force)) { + $fields[] = ['P', $this->zlen]; // Length of compressed data + } + + if ($this->ofs->isOver32()) { + $fields[] = ['P', $this->ofs]; // Offset of local header record + } + + if (!empty($fields)) { + if (!$this->zip->opt->isEnableZip64()) { + throw new OverflowException(); + } + + array_unshift( + $fields, + ['v', 0x0001], // 64 bit extension + ['v', count($fields) * 8] // Length of data block + ); + $this->version = Version::ZIP64(); + } + + if ($this->bits & self::BIT_EFS_UTF8) { + // Put the tricky entry to + // force Linux unzip to lookup EFS flag. + $fields[] = ['v', 0x5653]; // Choose 'ZS' for proprietary usage + $fields[] = ['v', 0x0000]; // zero length + } + + return ZipStream::packFields($fields); + } + + protected function processStreamWithZeroHeader(StreamInterface $stream): void + { + $this->bits |= self::BIT_ZERO_HEADER; + $this->addFileHeader(); + $this->readStream($stream, self::COMPUTE | self::SEND); + $this->addFileFooter(); + } + + protected function readStream(StreamInterface $stream, ?int $options = null): void + { + $this->deflateInit(); + $total = 0; + $size = $this->opt->getSize(); + while (!$stream->eof() && ($size === 0 || $total < $size)) { + $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); + $total += strlen($data); + if ($size > 0 && $total > $size) { + $data = substr($data, 0, strlen($data)-($total - $size)); + } + $this->deflateData($stream, $data, $options); + if ($options & self::SEND) { + $this->zip->send($data); + } + } + $this->deflateFinish($options); + } + + protected function deflateInit(): void + { + $hash = hash_init(self::HASH_ALGORITHM); + $this->hash = $hash; + if ($this->method->equals(Method::DEFLATE())) { + $this->deflate = deflate_init( + ZLIB_ENCODING_RAW, + ['level' => $this->opt->getDeflateLevel()] + ); + } + } + + protected function deflateData(StreamInterface $stream, string &$data, ?int $options = null): void + { + if ($options & self::COMPUTE) { + $this->len = $this->len->add(Bigint::init(strlen($data))); + hash_update($this->hash, $data); + } + if ($this->deflate) { + $data = deflate_add( + $this->deflate, + $data, + $stream->eof() + ? ZLIB_FINISH + : ZLIB_NO_FLUSH + ); + } + if ($options & self::COMPUTE) { + $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); + } + } + + protected function deflateFinish(?int $options = null): void + { + if ($options & self::COMPUTE) { + $this->crc = hexdec(hash_final($this->hash)); + } + } + + protected function processStreamWithComputedHeader(StreamInterface $stream): void + { + $this->readStream($stream, self::COMPUTE); + $stream->rewind(); + + $this->addFileHeader(); + $this->readStream($stream, self::SEND); + $this->addFileFooter(); } } diff --git a/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php b/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php deleted file mode 100644 index 23a66d8..0000000 --- a/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php +++ /dev/null @@ -1,89 +0,0 @@ -value), - new PackField(format: 'V', value: Time::dateTimeToDosTime($lastModificationDateTime)), - new PackField(format: 'V', value: $crc32UncompressedData), - new PackField(format: 'V', value: $compressedSize), - new PackField(format: 'V', value: $uncompressedSize), - new PackField(format: 'v', value: strlen($fileName)), - new PackField(format: 'v', value: strlen($extraField)), - ) . $fileName . $extraField; - } -} diff --git a/vendor/maennchen/zipstream-php/src/OperationMode.php b/vendor/maennchen/zipstream-php/src/OperationMode.php deleted file mode 100644 index dd650f0..0000000 --- a/vendor/maennchen/zipstream-php/src/OperationMode.php +++ /dev/null @@ -1,35 +0,0 @@ - 4 GB or file count > 64k) + * + * @var bool + */ + private $enableZip64 = true; + + /** + * Enable streaming files with single read where + * general purpose bit 3 indicates local file header + * contain zero values in crc and size fields, + * these appear only after file contents + * in data descriptor block. + * + * @var bool + */ + private $zeroHeader = false; + + /** + * Enable reading file stat for determining file size. + * When a 32-bit system reads file size that is + * over 2 GB, invalid value appears in file size + * due to integer overflow. Should be disabled on + * 32-bit systems with method addFileFromPath + * if any file may exceed 2 GB. In this case file + * will be read in blocks and correct size will be + * determined from content. + * + * @var bool + */ + private $statFiles = true; + + /** + * Enable flush after every write to output stream. + * @var bool + */ + private $flushOutput = false; + + /** + * HTTP Content-Disposition. Defaults to + * 'attachment', where + * FILENAME is the specified filename. + * + * Note that this does nothing if you are + * not sending HTTP headers. + * + * @var string + */ + private $contentDisposition = 'attachment'; + + /** + * Note that this does nothing if you are + * not sending HTTP headers. + * + * @var string + */ + private $contentType = 'application/x-zip'; + + /** + * @var int + */ + private $deflateLevel = 6; + + /** + * @var StreamInterface|resource + */ + private $outputStream; + + /** + * Options constructor. + */ + public function __construct() + { + $this->largeFileMethod = Method::STORE(); + $this->outputStream = fopen('php://output', 'wb'); + } + + public function getComment(): string + { + return $this->comment; + } + + public function setComment(string $comment): void + { + $this->comment = $comment; + } + + public function getLargeFileSize(): int + { + return $this->largeFileSize; + } + + public function setLargeFileSize(int $largeFileSize): void + { + $this->largeFileSize = $largeFileSize; + } + + public function getLargeFileMethod(): Method + { + return $this->largeFileMethod; + } + + public function setLargeFileMethod(Method $largeFileMethod): void + { + $this->largeFileMethod = $largeFileMethod; + } + + public function isSendHttpHeaders(): bool + { + return $this->sendHttpHeaders; + } + + public function setSendHttpHeaders(bool $sendHttpHeaders): void + { + $this->sendHttpHeaders = $sendHttpHeaders; + } + + public function getHttpHeaderCallback(): callable + { + return $this->httpHeaderCallback; + } + + public function setHttpHeaderCallback(callable $httpHeaderCallback): void + { + $this->httpHeaderCallback = $httpHeaderCallback; + } + + public function isEnableZip64(): bool + { + return $this->enableZip64; + } + + public function setEnableZip64(bool $enableZip64): void + { + $this->enableZip64 = $enableZip64; + } + + public function isZeroHeader(): bool + { + return $this->zeroHeader; + } + + public function setZeroHeader(bool $zeroHeader): void + { + $this->zeroHeader = $zeroHeader; + } + + public function isFlushOutput(): bool + { + return $this->flushOutput; + } + + public function setFlushOutput(bool $flushOutput): void + { + $this->flushOutput = $flushOutput; + } + + public function isStatFiles(): bool + { + return $this->statFiles; + } + + public function setStatFiles(bool $statFiles): void + { + $this->statFiles = $statFiles; + } + + public function getContentDisposition(): string + { + return $this->contentDisposition; + } + + public function setContentDisposition(string $contentDisposition): void + { + $this->contentDisposition = $contentDisposition; + } + + public function getContentType(): string + { + return $this->contentType; + } + + public function setContentType(string $contentType): void + { + $this->contentType = $contentType; + } + + /** + * @return StreamInterface|resource + */ + public function getOutputStream() + { + return $this->outputStream; + } + + /** + * @param StreamInterface|resource $outputStream + */ + public function setOutputStream($outputStream): void + { + $this->outputStream = $outputStream; + } + + /** + * @return int + */ + public function getDeflateLevel(): int + { + return $this->deflateLevel; + } + + /** + * @param int $deflateLevel + */ + public function setDeflateLevel(int $deflateLevel): void + { + $this->deflateLevel = $deflateLevel; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Option/File.php b/vendor/maennchen/zipstream-php/src/Option/File.php new file mode 100644 index 0000000..37e37ce --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/File.php @@ -0,0 +1,122 @@ +deflateLevel = $this->deflateLevel ?: $archiveOptions->getDeflateLevel(); + $this->time = $this->time ?: new DateTime(); + } + + /** + * @return string + */ + public function getComment(): string + { + return $this->comment; + } + + /** + * @param string $comment + */ + public function setComment(string $comment): void + { + $this->comment = $comment; + } + + /** + * @return Method + */ + public function getMethod(): Method + { + return $this->method ?: Method::DEFLATE(); + } + + /** + * @param Method $method + */ + public function setMethod(Method $method): void + { + $this->method = $method; + } + + /** + * @return int + */ + public function getDeflateLevel(): int + { + return $this->deflateLevel ?: Archive::DEFAULT_DEFLATE_LEVEL; + } + + /** + * @param int $deflateLevel + */ + public function setDeflateLevel(int $deflateLevel): void + { + $this->deflateLevel = $deflateLevel; + } + + /** + * @return DateTimeInterface + */ + public function getTime(): DateTimeInterface + { + return $this->time; + } + + /** + * @param DateTimeInterface $time + */ + public function setTime(DateTimeInterface $time): void + { + $this->time = $time; + } + + /** + * @return int + */ + public function getSize(): int + { + return $this->size; + } + + /** + * @param int $size + */ + public function setSize(int $size): void + { + $this->size = $size; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Option/Method.php b/vendor/maennchen/zipstream-php/src/Option/Method.php new file mode 100644 index 0000000..0dfce1b --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/Method.php @@ -0,0 +1,23 @@ + + */ +class Method extends Enum +{ + public const STORE = 0x00; + + public const DEFLATE = 0x08; +} diff --git a/vendor/maennchen/zipstream-php/src/Option/Version.php b/vendor/maennchen/zipstream-php/src/Option/Version.php new file mode 100644 index 0000000..f3daa85 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/Version.php @@ -0,0 +1,27 @@ + + */ +class Version extends Enum +{ + public const STORE = 0x000A; // 1.00 + + public const DEFLATE = 0x0014; // 2.00 + + public const ZIP64 = 0x002D; // 4.50 +} diff --git a/vendor/maennchen/zipstream-php/src/PackField.php b/vendor/maennchen/zipstream-php/src/PackField.php deleted file mode 100644 index 892b400..0000000 --- a/vendor/maennchen/zipstream-php/src/PackField.php +++ /dev/null @@ -1,56 +0,0 @@ -format; - }, ''); - - $args = array_map(function (self $field) { - switch ($field->format) { - case 'V': - if ($field->value > self::MAX_V) { - throw new RuntimeException(print_r($field->value, true) . ' is larger than 32 bits'); - } - break; - case 'v': - if ($field->value > self::MAX_v) { - throw new RuntimeException(print_r($field->value, true) . ' is larger than 16 bits'); - } - break; - case 'P': break; - default: - break; - } - - return $field->value; - }, $fields); - - return pack($fmt, ...$args); - } -} diff --git a/vendor/maennchen/zipstream-php/src/Stream.php b/vendor/maennchen/zipstream-php/src/Stream.php new file mode 100644 index 0000000..d80e70f --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Stream.php @@ -0,0 +1,265 @@ +stream = $stream; + } + + /** + * Reads all data from the stream into a string, from the beginning to end. + * + * This method MUST attempt to seek to the beginning of the stream before + * reading data and read the stream until the end is reached. + * + * Warning: This could attempt to load a large amount of data into memory. + * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring + * @return string + */ + public function __toString(): string + { + try { + $this->seek(0); + } catch (RuntimeException $e) { + } + return (string) stream_get_contents($this->stream); + } + + /** + * Closes the stream and any underlying resources. + * + * @return void + */ + public function close(): void + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + + /** + * Separates any underlying resources from the stream. + * + * After the stream has been detached, the stream is in an unusable state. + * + * @return resource|null Underlying PHP stream, if any + */ + public function detach() + { + $result = $this->stream; + $this->stream = null; + return $result; + } + + /** + * Seek to a position in the stream. + * + * @link http://www.php.net/manual/en/function.fseek.php + * @param int $offset Stream offset + * @param int $whence Specifies how the cursor position will be calculated + * based on the seek offset. Valid values are identical to the built-in + * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to + * offset bytes SEEK_CUR: Set position to current location plus offset + * SEEK_END: Set position to end-of-stream plus offset. + * @throws RuntimeException on failure. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->isSeekable()) { + throw new RuntimeException(); + } + if (fseek($this->stream, $offset, $whence) !== 0) { + throw new RuntimeException(); + } + } + + /** + * Returns whether or not the stream is seekable. + * + * @return bool + */ + public function isSeekable(): bool + { + return (bool)$this->getMetadata('seekable'); + } + + /** + * Get stream metadata as an associative array or retrieve a specific key. + * + * The keys returned are identical to the keys returned from PHP's + * stream_get_meta_data() function. + * + * @link http://php.net/manual/en/function.stream-get-meta-data.php + * @param string $key Specific metadata to retrieve. + * @return array|mixed|null Returns an associative array if no key is + * provided. Returns a specific key value if a key is provided and the + * value is found, or null if the key is not found. + */ + public function getMetadata($key = null) + { + $metadata = stream_get_meta_data($this->stream); + return $key !== null ? @$metadata[$key] : $metadata; + } + + /** + * Get the size of the stream if known. + * + * @return int|null Returns the size in bytes if known, or null if unknown. + */ + public function getSize(): ?int + { + $stats = fstat($this->stream); + return $stats['size']; + } + + /** + * Returns the current position of the file read/write pointer + * + * @return int Position of the file pointer + * @throws RuntimeException on error. + */ + public function tell(): int + { + $position = ftell($this->stream); + if ($position === false) { + throw new RuntimeException(); + } + return $position; + } + + /** + * Returns true if the stream is at the end of the stream. + * + * @return bool + */ + public function eof(): bool + { + return feof($this->stream); + } + + /** + * Seek to the beginning of the stream. + * + * If the stream is not seekable, this method will raise an exception; + * otherwise, it will perform a seek(0). + * + * @see seek() + * @link http://www.php.net/manual/en/function.fseek.php + * @throws RuntimeException on failure. + */ + public function rewind(): void + { + $this->seek(0); + } + + /** + * Write data to the stream. + * + * @param string $string The string that is to be written. + * @return int Returns the number of bytes written to the stream. + * @throws RuntimeException on failure. + */ + public function write($string): int + { + if (!$this->isWritable()) { + throw new RuntimeException(); + } + if (fwrite($this->stream, $string) === false) { + throw new RuntimeException(); + } + return mb_strlen($string); + } + + /** + * Returns whether or not the stream is writable. + * + * @return bool + */ + public function isWritable(): bool + { + $mode = $this->getMetadata('mode'); + if (!is_string($mode)) { + throw new RuntimeException('Could not get stream mode from metadata!'); + } + return preg_match('/[waxc+]/', $mode) === 1; + } + + /** + * Read data from the stream. + * + * @param int $length Read up to $length bytes from the object and return + * them. Fewer than $length bytes may be returned if underlying stream + * call returns fewer bytes. + * @return string Returns the data read from the stream, or an empty string + * if no bytes are available. + * @throws RuntimeException if an error occurs. + */ + public function read($length): string + { + if (!$this->isReadable()) { + throw new RuntimeException(); + } + $result = fread($this->stream, $length); + if ($result === false) { + throw new RuntimeException(); + } + return $result; + } + + /** + * Returns whether or not the stream is readable. + * + * @return bool + */ + public function isReadable(): bool + { + $mode = $this->getMetadata('mode'); + if (!is_string($mode)) { + throw new RuntimeException('Could not get stream mode from metadata!'); + } + return preg_match('/[r+]/', $mode) === 1; + } + + /** + * Returns the remaining contents in a string + * + * @return string + * @throws RuntimeException if unable to read or an error occurs while + * reading. + */ + public function getContents(): string + { + if (!$this->isReadable()) { + throw new RuntimeException(); + } + $result = stream_get_contents($this->stream); + if ($result === false) { + throw new RuntimeException(); + } + return $result; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Time.php b/vendor/maennchen/zipstream-php/src/Time.php deleted file mode 100644 index 1b4121c..0000000 --- a/vendor/maennchen/zipstream-php/src/Time.php +++ /dev/null @@ -1,39 +0,0 @@ -getTimestamp() < $dosMinimumDate->getTimestamp()) { - throw new DosTimeOverflowException(dateTime: $dateTime); - } - - $dateTime = DateTimeImmutable::createFromInterface($dateTime)->sub(new DateInterval('P1980Y')); - - [$year, $month, $day, $hour, $minute, $second] = explode(' ', $dateTime->format('Y n j G i s')); - - return - ((int) $year << 25) | - ((int) $month << 21) | - ((int) $day << 16) | - ((int) $hour << 11) | - ((int) $minute << 5) | - ((int) $second >> 1); - } -} diff --git a/vendor/maennchen/zipstream-php/src/Version.php b/vendor/maennchen/zipstream-php/src/Version.php deleted file mode 100644 index c014f8a..0000000 --- a/vendor/maennchen/zipstream-php/src/Version.php +++ /dev/null @@ -1,12 +0,0 @@ -addFile(fileName: 'world.txt', data: 'Hello World'); + * * add first file + * $data = file_get_contents('some_file.gif'); + * $zip->addFile('some_file.gif', $data); * - * // add second file - * $zip->addFile(fileName: 'moon.txt', data: 'Hello Moon'); - * ``` + * * add second file + * $data = file_get_contents('some_file.gif'); + * $zip->addFile('another_file.png', $data); * * 3. Finish the zip stream: * - * ```php - * $zip->finish(); - * ``` + * $zip->finish(); * * You can also add an archive comment, add comments to individual files, * and adjust the timestamp of files. See the API documentation for each * method below for additional information. * - * ## Example + * Example: * - * ```php - * // create a new zip stream object - * $zip = new ZipStream(outputName: 'some_files.zip'); + * // create a new zip stream object + * $zip = new ZipStream('some_files.zip'); * - * // list of local files - * $files = array('foo.txt', 'bar.jpg'); + * // list of local files + * $files = array('foo.txt', 'bar.jpg'); * - * // read and add each file to the archive - * foreach ($files as $path) - * $zip->addFileFromPath(fileName: $path, $path); + * // read and add each file to the archive + * foreach ($files as $path) + * $zip->addFile($path, file_get_contents($path)); * - * // write archive footer to stream - * $zip->finish(); - * ``` + * // write archive footer to stream + * $zip->finish(); */ class ZipStream { @@ -89,783 +80,529 @@ class ZipStream * Here we are using 6 for the OS, indicating OS/2 H.P.F.S. * to prevent file permissions issues upon extract (see #84) * 0x603 is 00000110 00000011 in binary, so 6 and 3 - * - * @internal */ public const ZIP_VERSION_MADE_BY = 0x603; - private bool $ready = true; + /** + * The following signatures end with 0x4b50, which in ASCII is PK, + * the initials of the inventor Phil Katz. + * See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers + */ + public const FILE_HEADER_SIGNATURE = 0x04034b50; - private int $offset = 0; + public const CDR_FILE_SIGNATURE = 0x02014b50; + + public const CDR_EOF_SIGNATURE = 0x06054b50; + + public const DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; + + public const ZIP64_CDR_EOF_SIGNATURE = 0x06064b50; + + public const ZIP64_CDR_LOCATOR_SIGNATURE = 0x07064b50; /** - * @var string[] + * Global Options + * + * @var ArchiveOptions */ - private array $centralDirectoryRecords = []; + public $opt; /** - * @var resource + * @var array */ - private $outputStream; - - private readonly Closure $httpHeaderCallback; + public $files = []; /** - * @var File[] + * @var Bigint */ - private array $recordedSimulation = []; + public $cdr_ofs; + + /** + * @var Bigint + */ + public $ofs; + + /** + * @var bool + */ + protected $need_headers; + + /** + * @var null|String + */ + protected $output_name; /** * Create a new ZipStream object. * - * ##### Examples + * Parameters: * - * ```php - * // create a new zip file named 'foo.zip' - * $zip = new ZipStream(outputName: 'foo.zip'); + * @param String $name - Name of output file (optional). + * @param ArchiveOptions $opt - Archive Options * - * // create a new zip file named 'bar.zip' with a comment - * $zip = new ZipStream( - * outputName: 'bar.zip', - * comment: 'this is a comment for the zip file.', - * ); - * ``` + * Large File Support: * - * @param OperationMode $operationMode - * The mode can be used to switch between `NORMAL` and `SIMULATION_*` modes. - * For details see the `OperationMode` documentation. + * By default, the method addFileFromPath() will send send files + * larger than 20 megabytes along raw rather than attempting to + * compress them. You can change both the maximum size and the + * compression behavior using the largeFile* options above, with the + * following caveats: * - * Default to `NORMAL`. + * * For "small" files (e.g. files smaller than largeFileSize), the + * memory use can be up to twice that of the actual file. In other + * words, adding a 10 megabyte file to the archive could potentially + * occupy 20 megabytes of memory. * - * @param string $comment - * Archive Level Comment + * * Enabling compression on large files (e.g. files larger than + * large_file_size) is extremely slow, because ZipStream has to pass + * over the large file once to calculate header information, and then + * again to compress and send the actual data. * - * @param StreamInterface|resource|null $outputStream - * Override the output of the archive to a different target. + * Examples: * - * By default the archive is sent to `STDOUT`. + * // create a new zip file named 'foo.zip' + * $zip = new ZipStream('foo.zip'); * - * @param CompressionMethod $defaultCompressionMethod - * How to handle file compression. Legal values are - * `CompressionMethod::DEFLATE` (the default), or - * `CompressionMethod::STORE`. `STORE` sends the file raw and is - * significantly faster, while `DEFLATE` compresses the file and - * is much, much slower. + * // create a new zip file named 'bar.zip' with a comment + * $opt->setComment = 'this is a comment for the zip file.'; + * $zip = new ZipStream('bar.zip', $opt); * - * @param int $defaultDeflateLevel - * Default deflation level. Only relevant if `compressionMethod` - * is `DEFLATE`. + * Notes: * - * See details of [`deflate_init`](https://www.php.net/manual/en/function.deflate-init.php#refsect1-function.deflate-init-parameters) - * - * @param bool $enableZip64 - * Enable Zip64 extension, supporting very large - * archives (any size > 4 GB or file count > 64k) - * - * @param bool $defaultEnableZeroHeader - * Enable streaming files with single read. - * - * When the zero header is set, the file is streamed into the output - * and the size & checksum are added at the end of the file. This is the - * fastest method and uses the least memory. Unfortunately not all - * ZIP clients fully support this and can lead to clients reporting - * the generated ZIP files as corrupted in combination with other - * circumstances. (Zip64 enabled, using UTF8 in comments / names etc.) - * - * When the zero header is not set, the length & checksum need to be - * defined before the file is actually added. To prevent loading all - * the data into memory, the data has to be read twice. If the data - * which is added is not seekable, this call will fail. - * - * @param bool $sendHttpHeaders - * Boolean indicating whether or not to send - * the HTTP headers for this file. - * - * @param ?Closure $httpHeaderCallback - * The method called to send HTTP headers - * - * @param string|null $outputName - * The name of the created archive. - * - * Only relevant if `$sendHttpHeaders = true`. - * - * @param string $contentDisposition - * HTTP Content-Disposition - * - * Only relevant if `sendHttpHeaders = true`. - * - * @param string $contentType - * HTTP Content Type - * - * Only relevant if `sendHttpHeaders = true`. - * - * @param bool $flushOutput - * Enable flush after every write to output stream. - * - * @return self + * In order to let this library send HTTP headers, a filename must be given + * _and_ the option `sendHttpHeaders` must be `true`. This behavior is to + * allow software to send its own headers (including the filename), and + * still use this library. */ - public function __construct( - private OperationMode $operationMode = OperationMode::NORMAL, - private readonly string $comment = '', - $outputStream = null, - private readonly CompressionMethod $defaultCompressionMethod = CompressionMethod::DEFLATE, - private readonly int $defaultDeflateLevel = 6, - private readonly bool $enableZip64 = true, - private readonly bool $defaultEnableZeroHeader = true, - private bool $sendHttpHeaders = true, - ?Closure $httpHeaderCallback = null, - private readonly ?string $outputName = null, - private readonly string $contentDisposition = 'attachment', - private readonly string $contentType = 'application/x-zip', - private bool $flushOutput = false, - ) { - $this->outputStream = self::normalizeStream($outputStream); - $this->httpHeaderCallback = $httpHeaderCallback ?? header(...); + public function __construct(?string $name = null, ?ArchiveOptions $opt = null) + { + $this->opt = $opt ?: new ArchiveOptions(); + + $this->output_name = $name; + $this->need_headers = $name && $this->opt->isSendHttpHeaders(); + + $this->cdr_ofs = new Bigint(); + $this->ofs = new Bigint(); } /** + * addFile + * * Add a file to the archive. * - * ##### File Options + * @param String $name - path of file in archive (including directory). + * @param String $data - contents of file + * @param FileOptions $options * - * See {@see addFileFromPsr7Stream()} + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * method - Storage method for file ("store" or "deflate") * - * ##### Examples + * Examples: * - * ```php - * // add a file named 'world.txt' - * $zip->addFile(fileName: 'world.txt', data: 'Hello World!'); + * // add a file named 'foo.txt' + * $data = file_get_contents('foo.txt'); + * $zip->addFile('foo.txt', $data); * - * // add a file named 'bar.jpg' with a comment and a last-modified - * // time of two hours ago - * $zip->addFile( - * fileName: 'bar.jpg', - * data: $data, - * comment: 'this is a comment about bar.jpg', - * lastModificationDateTime: new DateTime('2 hours ago'), - * ); - * ``` - * - * @param string $data - * - * contents of file + * // add a file named 'bar.jpg' with a comment and a last-modified + * // time of two hours ago + * $data = file_get_contents('bar.jpg'); + * $opt->setTime = time() - 2 * 3600; + * $opt->setComment = 'this is a comment about bar.jpg'; + * $zip->addFile('bar.jpg', $data, $opt); */ - public function addFile( - string $fileName, - string $data, - string $comment = '', - ?CompressionMethod $compressionMethod = null, - ?int $deflateLevel = null, - ?DateTimeInterface $lastModificationDateTime = null, - ?int $maxSize = null, - ?int $exactSize = null, - ?bool $enableZeroHeader = null, - ): void { - $this->addFileFromCallback( - fileName: $fileName, - callback: fn() => $data, - comment: $comment, - compressionMethod: $compressionMethod, - deflateLevel: $deflateLevel, - lastModificationDateTime: $lastModificationDateTime, - maxSize: $maxSize, - exactSize: $exactSize, - enableZeroHeader: $enableZeroHeader, - ); + public function addFile(string $name, string $data, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processData($data); } /** + * addFileFromPath + * * Add a file at path to the archive. * - * ##### File Options + * Note that large files may be compressed differently than smaller + * files; see the "Large File Support" section above for more + * information. * - * See {@see addFileFromPsr7Stream()} + * @param String $name - name of file in archive (including directory path). + * @param String $path - path to file on disk (note: paths should be encoded using + * UNIX-style forward slashes -- e.g '/path/to/some/file'). + * @param FileOptions $options * - * ###### Examples + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * method - Storage method for file ("store" or "deflate") * - * ```php - * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' - * $zip->addFileFromPath( - * fileName: 'foo.txt', - * path: '/tmp/foo.txt', - * ); + * Examples: * - * // add a file named 'bigfile.rar' from the local file - * // '/usr/share/bigfile.rar' with a comment and a last-modified - * // time of two hours ago - * $zip->addFileFromPath( - * fileName: 'bigfile.rar', - * path: '/usr/share/bigfile.rar', - * comment: 'this is a comment about bigfile.rar', - * lastModificationDateTime: new DateTime('2 hours ago'), - * ); - * ``` + * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' + * $zip->addFileFromPath('foo.txt', '/tmp/foo.txt'); * + * // add a file named 'bigfile.rar' from the local file + * // '/usr/share/bigfile.rar' with a comment and a last-modified + * // time of two hours ago + * $path = '/usr/share/bigfile.rar'; + * $opt->setTime = time() - 2 * 3600; + * $opt->setComment = 'this is a comment about bar.jpg'; + * $zip->addFileFromPath('bigfile.rar', $path, $opt); + * + * @return void * @throws \ZipStream\Exception\FileNotFoundException * @throws \ZipStream\Exception\FileNotReadableException */ - public function addFileFromPath( - /** - * name of file in archive (including directory path). - */ - string $fileName, + public function addFileFromPath(string $name, string $path, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); - /** - * path to file on disk (note: paths should be encoded using - * UNIX-style forward slashes -- e.g '/path/to/some/file'). - */ - string $path, - string $comment = '', - ?CompressionMethod $compressionMethod = null, - ?int $deflateLevel = null, - ?DateTimeInterface $lastModificationDateTime = null, - ?int $maxSize = null, - ?int $exactSize = null, - ?bool $enableZeroHeader = null, - ): void { - if (!is_readable($path)) { - if (!file_exists($path)) { - throw new FileNotFoundException($path); - } - throw new FileNotReadableException($path); - } - - $fileTime = filemtime($path); - if ($fileTime !== false) { - $lastModificationDateTime ??= (new DateTimeImmutable())->setTimestamp($fileTime); - } - - $this->addFileFromCallback( - fileName: $fileName, - callback: function () use ($path) { - - $stream = fopen($path, 'rb'); - - if (!$stream) { - // @codeCoverageIgnoreStart - throw new ResourceActionException('fopen'); - // @codeCoverageIgnoreEnd - } - - return $stream; - }, - comment: $comment, - compressionMethod: $compressionMethod, - deflateLevel: $deflateLevel, - lastModificationDateTime: $lastModificationDateTime, - maxSize: $maxSize, - exactSize: $exactSize, - enableZeroHeader: $enableZeroHeader, - ); + $file = new File($this, $name, $options); + $file->processPath($path); } /** - * Add an open stream (resource) to the archive. + * addFileFromStream * - * ##### File Options - * - * See {@see addFileFromPsr7Stream()} - * - * ##### Examples - * - * ```php - * // create a temporary file stream and write text to it - * $filePointer = tmpfile(); - * fwrite($filePointer, 'The quick brown fox jumped over the lazy dog.'); - * - * // add a file named 'streamfile.txt' from the content of the stream - * $archive->addFileFromStream( - * fileName: 'streamfile.txt', - * stream: $filePointer, - * ); - * ``` - * - * @param resource $stream contents of file as a stream resource - */ - public function addFileFromStream( - string $fileName, - $stream, - string $comment = '', - ?CompressionMethod $compressionMethod = null, - ?int $deflateLevel = null, - ?DateTimeInterface $lastModificationDateTime = null, - ?int $maxSize = null, - ?int $exactSize = null, - ?bool $enableZeroHeader = null, - ): void { - $this->addFileFromCallback( - fileName: $fileName, - callback: fn() => $stream, - comment: $comment, - compressionMethod: $compressionMethod, - deflateLevel: $deflateLevel, - lastModificationDateTime: $lastModificationDateTime, - maxSize: $maxSize, - exactSize: $exactSize, - enableZeroHeader: $enableZeroHeader, - ); - } - - /** * Add an open stream to the archive. * - * ##### Examples + * @param String $name - path of file in archive (including directory). + * @param resource $stream - contents of file as a stream resource + * @param FileOptions $options * - * ```php - * $stream = $response->getBody(); - * // add a file named 'streamfile.txt' from the content of the stream - * $archive->addFileFromPsr7Stream( - * fileName: 'streamfile.txt', - * stream: $stream, - * ); - * ``` + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. * - * @param string $fileName - * path of file in archive (including directory) + * Examples: * - * @param StreamInterface $stream - * contents of file as a stream resource + * // create a temporary file stream and write text to it + * $fp = tmpfile(); + * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); * - * @param string $comment - * ZIP comment for this file + * // add a file named 'streamfile.txt' from the content of the stream + * $x->addFileFromStream('streamfile.txt', $fp); * - * @param ?CompressionMethod $compressionMethod - * Override `defaultCompressionMethod` + * @return void + */ + public function addFileFromStream(string $name, $stream, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processStream(new Stream($stream)); + } + + /** + * addFileFromPsr7Stream * - * See {@see __construct()} + * Add an open stream to the archive. * - * @param ?int $deflateLevel - * Override `defaultDeflateLevel` + * @param String $name - path of file in archive (including directory). + * @param StreamInterface $stream - contents of file as a stream resource + * @param FileOptions $options * - * See {@see __construct()} + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. * - * @param ?DateTimeInterface $lastModificationDateTime - * Set last modification time of file. + * Examples: * - * Default: `now` + * $stream = $response->getBody(); + * // add a file named 'streamfile.txt' from the content of the stream + * $x->addFileFromPsr7Stream('streamfile.txt', $stream); * - * @param ?int $maxSize - * Only read `maxSize` bytes from file. - * - * The file is considered done when either reaching `EOF` - * or the `maxSize`. - * - * @param ?int $exactSize - * Read exactly `exactSize` bytes from file. - * If `EOF` is reached before reading `exactSize` bytes, an error will be - * thrown. The parameter allows for faster size calculations if the `stream` - * does not support `fstat` size or is slow and otherwise known beforehand. - * - * @param ?bool $enableZeroHeader - * Override `defaultEnableZeroHeader` - * - * See {@see __construct()} + * @return void */ public function addFileFromPsr7Stream( - string $fileName, + string $name, StreamInterface $stream, - string $comment = '', - ?CompressionMethod $compressionMethod = null, - ?int $deflateLevel = null, - ?DateTimeInterface $lastModificationDateTime = null, - ?int $maxSize = null, - ?int $exactSize = null, - ?bool $enableZeroHeader = null, + ?FileOptions $options = null ): void { - $this->addFileFromCallback( - fileName: $fileName, - callback: fn() => $stream, - comment: $comment, - compressionMethod: $compressionMethod, - deflateLevel: $deflateLevel, - lastModificationDateTime: $lastModificationDateTime, - maxSize: $maxSize, - exactSize: $exactSize, - enableZeroHeader: $enableZeroHeader, - ); + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processStream($stream); } /** - * Add a file based on a callback. + * finish * - * This is useful when you want to simulate a lot of files without keeping - * all of the file handles open at the same time. - * - * ##### Examples - * - * ```php - * foreach($files as $name => $size) { - * $archive->addFileFromCallback( - * fileName: 'streamfile.txt', - * exactSize: $size, - * callback: function() use($name): Psr\Http\Message\StreamInterface { - * $response = download($name); - * return $response->getBody(); - * } - * ); - * } - * ``` - * - * @param string $fileName - * path of file in archive (including directory) - * - * @param Closure $callback - * @psalm-param Closure(): (resource|StreamInterface|string) $callback - * A callback to get the file contents in the shape of a PHP stream, - * a Psr StreamInterface implementation, or a string. - * - * @param string $comment - * ZIP comment for this file - * - * @param ?CompressionMethod $compressionMethod - * Override `defaultCompressionMethod` - * - * See {@see __construct()} - * - * @param ?int $deflateLevel - * Override `defaultDeflateLevel` - * - * See {@see __construct()} - * - * @param ?DateTimeInterface $lastModificationDateTime - * Set last modification time of file. - * - * Default: `now` - * - * @param ?int $maxSize - * Only read `maxSize` bytes from file. - * - * The file is considered done when either reaching `EOF` - * or the `maxSize`. - * - * @param ?int $exactSize - * Read exactly `exactSize` bytes from file. - * If `EOF` is reached before reading `exactSize` bytes, an error will be - * thrown. The parameter allows for faster size calculations if the `stream` - * does not support `fstat` size or is slow and otherwise known beforehand. - * - * @param ?bool $enableZeroHeader - * Override `defaultEnableZeroHeader` - * - * See {@see __construct()} - */ - public function addFileFromCallback( - string $fileName, - Closure $callback, - string $comment = '', - ?CompressionMethod $compressionMethod = null, - ?int $deflateLevel = null, - ?DateTimeInterface $lastModificationDateTime = null, - ?int $maxSize = null, - ?int $exactSize = null, - ?bool $enableZeroHeader = null, - ): void { - $file = new File( - dataCallback: function () use ($callback, $maxSize) { - $data = $callback(); - - if (is_resource($data)) { - return $data; - } - - if ($data instanceof StreamInterface) { - return StreamWrapper::getResource($data); - } - - - $stream = fopen('php://memory', 'rw+'); - if ($stream === false) { - // @codeCoverageIgnoreStart - throw new ResourceActionException('fopen'); - // @codeCoverageIgnoreEnd - } - if ($maxSize !== null && fwrite($stream, $data, $maxSize) === false) { - // @codeCoverageIgnoreStart - throw new ResourceActionException('fwrite', $stream); - // @codeCoverageIgnoreEnd - } elseif (fwrite($stream, $data) === false) { - // @codeCoverageIgnoreStart - throw new ResourceActionException('fwrite', $stream); - // @codeCoverageIgnoreEnd - } - if (rewind($stream) === false) { - // @codeCoverageIgnoreStart - throw new ResourceActionException('rewind', $stream); - // @codeCoverageIgnoreEnd - } - - return $stream; - - }, - send: $this->send(...), - recordSentBytes: $this->recordSentBytes(...), - operationMode: $this->operationMode, - fileName: $fileName, - startOffset: $this->offset, - compressionMethod: $compressionMethod ?? $this->defaultCompressionMethod, - comment: $comment, - deflateLevel: $deflateLevel ?? $this->defaultDeflateLevel, - lastModificationDateTime: $lastModificationDateTime ?? new DateTimeImmutable(), - maxSize: $maxSize, - exactSize: $exactSize, - enableZip64: $this->enableZip64, - enableZeroHeader: $enableZeroHeader ?? $this->defaultEnableZeroHeader, - ); - - if ($this->operationMode !== OperationMode::NORMAL) { - $this->recordedSimulation[] = $file; - } - - $this->centralDirectoryRecords[] = $file->process(); - } - - /** - * Add a directory to the archive. - * - * ##### File Options - * - * See {@see addFileFromPsr7Stream()} - * - * ##### Examples - * - * ```php - * // add a directory named 'world/' - * $zip->addDirectory(fileName: 'world/'); - * ``` - */ - public function addDirectory( - string $fileName, - string $comment = '', - ?DateTimeInterface $lastModificationDateTime = null, - ): void { - if (!str_ends_with($fileName, '/')) { - $fileName .= '/'; - } - - $this->addFile( - fileName: $fileName, - data: '', - comment: $comment, - compressionMethod: CompressionMethod::STORE, - deflateLevel: null, - lastModificationDateTime: $lastModificationDateTime, - maxSize: 0, - exactSize: 0, - enableZeroHeader: false, - ); - } - - /** - * Executes a previously calculated simulation. - * - * ##### Example - * - * ```php - * $zip = new ZipStream( - * outputName: 'foo.zip', - * operationMode: OperationMode::SIMULATE_STRICT, - * ); - * - * $zip->addFile('test.txt', 'Hello World'); - * - * $size = $zip->finish(); - * - * header('Content-Length: '. $size); - * - * $zip->executeSimulation(); - * ``` - */ - public function executeSimulation(): void - { - if ($this->operationMode !== OperationMode::NORMAL) { - throw new RuntimeException('Zip simulation is not finished.'); - } - - foreach ($this->recordedSimulation as $file) { - $this->centralDirectoryRecords[] = $file->cloneSimulationExecution()->process(); - } - - $this->finish(); - } - - /** * Write zip footer to stream. * - * The clase is left in an unusable state after `finish`. + * Example: * - * ##### Example + * // add a list of files to the archive + * $files = array('foo.txt', 'bar.jpg'); + * foreach ($files as $path) + * $zip->addFile($path, file_get_contents($path)); * - * ```php - * // write footer to stream - * $zip->finish(); - * ``` + * // write footer to stream + * $zip->finish(); + * @return void + * + * @throws OverflowException */ - public function finish(): int + public function finish(): void { - $centralDirectoryStartOffsetOnDisk = $this->offset; - $sizeOfCentralDirectory = 0; - // add trailing cdr file records - foreach ($this->centralDirectoryRecords as $centralDirectoryRecord) { - $this->send($centralDirectoryRecord); - $sizeOfCentralDirectory += strlen($centralDirectoryRecord); + foreach ($this->files as $cdrFile) { + $this->send($cdrFile); + $this->cdr_ofs = $this->cdr_ofs->add(Bigint::init(strlen($cdrFile))); } // Add 64bit headers (if applicable) - if (count($this->centralDirectoryRecords) >= 0xFFFF || - $centralDirectoryStartOffsetOnDisk > 0xFFFFFFFF || - $sizeOfCentralDirectory > 0xFFFFFFFF) { - if (!$this->enableZip64) { + if (count($this->files) >= 0xFFFF || + $this->cdr_ofs->isOver32() || + $this->ofs->isOver32()) { + if (!$this->opt->isEnableZip64()) { throw new OverflowException(); } - $this->send(Zip64\EndOfCentralDirectory::generate( - versionMadeBy: self::ZIP_VERSION_MADE_BY, - versionNeededToExtract: Version::ZIP64->value, - numberOfThisDisk: 0, - numberOfTheDiskWithCentralDirectoryStart: 0, - numberOfCentralDirectoryEntriesOnThisDisk: count($this->centralDirectoryRecords), - numberOfCentralDirectoryEntries: count($this->centralDirectoryRecords), - sizeOfCentralDirectory: $sizeOfCentralDirectory, - centralDirectoryStartOffsetOnDisk: $centralDirectoryStartOffsetOnDisk, - extensibleDataSector: '', - )); - - $this->send(Zip64\EndOfCentralDirectoryLocator::generate( - numberOfTheDiskWithZip64CentralDirectoryStart: 0x00, - zip64centralDirectoryStartOffsetOnDisk: $centralDirectoryStartOffsetOnDisk + $sizeOfCentralDirectory, - totalNumberOfDisks: 1, - )); + $this->addCdr64Eof(); + $this->addCdr64Locator(); } // add trailing cdr eof record - $numberOfCentralDirectoryEntries = min(count($this->centralDirectoryRecords), 0xFFFF); - $this->send(EndOfCentralDirectory::generate( - numberOfThisDisk: 0x00, - numberOfTheDiskWithCentralDirectoryStart: 0x00, - numberOfCentralDirectoryEntriesOnThisDisk: $numberOfCentralDirectoryEntries, - numberOfCentralDirectoryEntries: $numberOfCentralDirectoryEntries, - sizeOfCentralDirectory: min($sizeOfCentralDirectory, 0xFFFFFFFF), - centralDirectoryStartOffsetOnDisk: min($centralDirectoryStartOffsetOnDisk, 0xFFFFFFFF), - zipFileComment: $this->comment, - )); - - $size = $this->offset; + $this->addCdrEof(); // The End $this->clear(); - - return $size; } /** - * @param StreamInterface|resource|null $outputStream - * @return resource + * Create a format string and argument list for pack(), then call + * pack() and return the result. + * + * @param array $fields + * @return string */ - private static function normalizeStream($outputStream) + public static function packFields(array $fields): string { - if ($outputStream instanceof StreamInterface) { - return StreamWrapper::getResource($outputStream); - } - if (is_resource($outputStream)) { - return $outputStream; - } - $resource = fopen('php://output', 'wb'); + $fmt = ''; + $args = []; - if ($resource === false) { - throw new RuntimeException('fopen of php://output failed'); + // populate format string and argument list + foreach ($fields as [$format, $value]) { + if ($format === 'P') { + $fmt .= 'VV'; + if ($value instanceof Bigint) { + $args[] = $value->getLow32(); + $args[] = $value->getHigh32(); + } else { + $args[] = $value; + $args[] = 0; + } + } else { + if ($value instanceof Bigint) { + $value = $value->getLow32(); + } + $fmt .= $format; + $args[] = $value; + } } - return $resource; - } + // prepend format string to argument list + array_unshift($args, $fmt); - /** - * Record sent bytes - */ - private function recordSentBytes(int $sentBytes): void - { - $this->offset += $sentBytes; + // build output string from header and compressed data + return pack(...$args); } /** * Send string, sending HTTP headers if necessary. * Flush output after write if configure option is set. + * + * @param String $str + * @return void */ - private function send(string $data): void + public function send(string $str): void { - if (!$this->ready) { - throw new RuntimeException('Archive is already finished'); - } - - if ($this->operationMode === OperationMode::NORMAL && $this->sendHttpHeaders) { + if ($this->need_headers) { $this->sendHttpHeaders(); - $this->sendHttpHeaders = false; + } + $this->need_headers = false; + + $outputStream = $this->opt->getOutputStream(); + + if ($outputStream instanceof StreamInterface) { + $outputStream->write($str); + } else { + fwrite($outputStream, $str); } - $this->recordSentBytes(strlen($data)); - - if ($this->operationMode === OperationMode::NORMAL) { - if (fwrite($this->outputStream, $data) === false) { - throw new ResourceActionException('fwrite', $this->outputStream); + if ($this->opt->isFlushOutput()) { + // flush output buffer if it is on and flushable + $status = ob_get_status(); + if (isset($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { + ob_flush(); } - if ($this->flushOutput) { - // flush output buffer if it is on and flushable - $status = ob_get_status(); - if (isset($status['flags']) && is_int($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { - ob_flush(); - } - - // Flush system buffers after flushing userspace output buffer - flush(); - } + // Flush system buffers after flushing userspace output buffer + flush(); } } /** - * Send HTTP headers for this stream. - */ - private function sendHttpHeaders(): void + * Is this file larger than large_file_size? + * + * @param string $path + * @return bool + */ + public function isLargeFile(string $path): bool + { + if (!$this->opt->isStatFiles()) { + return false; + } + $stat = stat($path); + return $stat['size'] > $this->opt->getLargeFileSize(); + } + + /** + * Save file attributes for trailing CDR record. + * + * @param File $file + * @return void + */ + public function addToCdr(File $file): void + { + $file->ofs = $this->ofs; + $this->ofs = $this->ofs->add($file->getTotalLength()); + $this->files[] = $file->getCdrFile(); + } + + /** + * Send ZIP64 CDR EOF (Central Directory Record End-of-File) record. + * + * @return void + */ + protected function addCdr64Eof(): void + { + $num_files = count($this->files); + $cdr_length = $this->cdr_ofs; + $cdr_offset = $this->ofs; + + $fields = [ + ['V', static::ZIP64_CDR_EOF_SIGNATURE], // ZIP64 end of central file header signature + ['P', 44], // Length of data below this header (length of block - 12) = 44 + ['v', static::ZIP_VERSION_MADE_BY], // Made by version + ['v', Version::ZIP64], // Extract by version + ['V', 0x00], // disk number + ['V', 0x00], // no of disks + ['P', $num_files], // no of entries on disk + ['P', $num_files], // no of entries in cdr + ['P', $cdr_length], // CDR size + ['P', $cdr_offset], // CDR offset + ]; + + $ret = static::packFields($fields); + $this->send($ret); + } + + /** + * Send HTTP headers for this stream. + * + * @return void + */ + protected function sendHttpHeaders(): void { // grab content disposition - $disposition = $this->contentDisposition; + $disposition = $this->opt->getContentDisposition(); - if ($this->outputName !== null) { + if ($this->output_name) { // Various different browsers dislike various characters here. Strip them all for safety. - $safeOutput = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->outputName)); + $safe_output = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->output_name)); // Check if we need to UTF-8 encode the filename - $urlencoded = rawurlencode($safeOutput); + $urlencoded = rawurlencode($safe_output); $disposition .= "; filename*=UTF-8''{$urlencoded}"; } $headers = [ - 'Content-Type' => $this->contentType, + 'Content-Type' => $this->opt->getContentType(), 'Content-Disposition' => $disposition, 'Pragma' => 'public', 'Cache-Control' => 'public, must-revalidate', 'Content-Transfer-Encoding' => 'binary', ]; + $call = $this->opt->getHttpHeaderCallback(); foreach ($headers as $key => $val) { - ($this->httpHeaderCallback)("$key: $val"); + $call("$key: $val"); } } + /** + * Send ZIP64 CDR Locator (Central Directory Record Locator) record. + * + * @return void + */ + protected function addCdr64Locator(): void + { + $cdr_offset = $this->ofs->add($this->cdr_ofs); + + $fields = [ + ['V', static::ZIP64_CDR_LOCATOR_SIGNATURE], // ZIP64 end of central file header signature + ['V', 0x00], // Disc number containing CDR64EOF + ['P', $cdr_offset], // CDR offset + ['V', 1], // Total number of disks + ]; + + $ret = static::packFields($fields); + $this->send($ret); + } + + /** + * Send CDR EOF (Central Directory Record End-of-File) record. + * + * @return void + */ + protected function addCdrEof(): void + { + $num_files = count($this->files); + $cdr_length = $this->cdr_ofs; + $cdr_offset = $this->ofs; + + // grab comment (if specified) + $comment = $this->opt->getComment(); + + $fields = [ + ['V', static::CDR_EOF_SIGNATURE], // end of central file header signature + ['v', 0x00], // disk number + ['v', 0x00], // no of disks + ['v', min($num_files, 0xFFFF)], // no of entries on disk + ['v', min($num_files, 0xFFFF)], // no of entries in cdr + ['V', $cdr_length->getLowFF()], // CDR size + ['V', $cdr_offset->getLowFF()], // CDR offset + ['v', strlen($comment)], // Zip Comment size + ]; + + $ret = static::packFields($fields) . $comment; + $this->send($ret); + } + /** * Clear all internal variables. Note that the stream object is not * usable after this. + * + * @return void */ - private function clear(): void + protected function clear(): void { - $this->centralDirectoryRecords = []; - $this->offset = 0; - - if ($this->operationMode === OperationMode::NORMAL) { - $this->ready = false; - $this->recordedSimulation = []; - } else { - $this->operationMode = OperationMode::NORMAL; - } + $this->files = []; + $this->ofs = new Bigint(); + $this->cdr_ofs = new Bigint(); + $this->opt = new ArchiveOptions(); } } diff --git a/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php b/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php deleted file mode 100644 index bf621bc..0000000 --- a/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php +++ /dev/null @@ -1,23 +0,0 @@ -fail("File {$filePath} must contain {$needle}"); - } - - protected function assertFileDoesNotContain(string $filePath, string $needle): void - { - $last = ''; - - $handle = fopen($filePath, 'r'); - while (!feof($handle)) { - $line = fgets($handle, 1024); - - if (str_contains($last . $line, $needle)) { - fclose($handle); - - $this->fail("File {$filePath} must not contain {$needle}"); - } - - $last = $line; - } - - fclose($handle); - } -} diff --git a/vendor/maennchen/zipstream-php/test/BigintTest.php b/vendor/maennchen/zipstream-php/test/BigintTest.php new file mode 100644 index 0000000..4d26fcd --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/BigintTest.php @@ -0,0 +1,66 @@ +assertSame('0x0000000012345678', $bigint->getHex64()); + $this->assertSame(0x12345678, $bigint->getLow32()); + $this->assertSame(0, $bigint->getHigh32()); + } + + public function testConstructLarge(): void + { + $bigint = new Bigint(0x87654321); + $this->assertSame('0x0000000087654321', $bigint->getHex64()); + $this->assertSame('87654321', bin2hex(pack('N', $bigint->getLow32()))); + $this->assertSame(0, $bigint->getHigh32()); + } + + public function testAddSmallValue(): void + { + $bigint = new Bigint(1); + $bigint = $bigint->add(Bigint::init(2)); + $this->assertSame(3, $bigint->getLow32()); + $this->assertFalse($bigint->isOver32()); + $this->assertTrue($bigint->isOver32(true)); + $this->assertSame($bigint->getLowFF(), (float)$bigint->getLow32()); + $this->assertSame($bigint->getLowFF(true), (float)0xFFFFFFFF); + } + + public function testAddWithOverflowAtLowestByte(): void + { + $bigint = new Bigint(0xFF); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertSame(0x100, $bigint->getLow32()); + } + + public function testAddWithOverflowAtInteger32(): void + { + $bigint = new Bigint(0xFFFFFFFE); + $this->assertFalse($bigint->isOver32()); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertTrue($bigint->isOver32()); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertSame('0x0000000100000000', $bigint->getHex64()); + $this->assertTrue($bigint->isOver32()); + $this->assertSame((float)0xFFFFFFFF, $bigint->getLowFF()); + } + + public function testAddWithOverflowAtInteger64(): void + { + $bigint = Bigint::fromLowHigh(0xFFFFFFFF, 0xFFFFFFFF); + $this->assertSame('0xFFFFFFFFFFFFFFFF', $bigint->getHex64()); + $this->expectException(OverflowException::class); + $bigint->add(Bigint::init(1)); + } +} diff --git a/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php b/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php deleted file mode 100644 index 5457b4f..0000000 --- a/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php +++ /dev/null @@ -1,60 +0,0 @@ -assertSame( - bin2hex($header), - '504b0102' . // 4 bytes; central file header signature - '0306' . // 2 bytes; version made by - '2d00' . // 2 bytes; version needed to extract - '2222' . // 2 bytes; general purpose bit flag - '0800' . // 2 bytes; compression method - '2008' . // 2 bytes; last mod file time - '2154' . // 2 bytes; last mod file date - '11111111' . // 4 bytes; crc-32 - '77777777' . // 4 bytes; compressed size - '99999999' . // 4 bytes; uncompressed size - '0800' . // 2 bytes; file name length (n) - '0c00' . // 2 bytes; extra field length (m) - '0c00' . // 2 bytes; file comment length (o) - '0000' . // 2 bytes; disk number start - '0000' . // 2 bytes; internal file attributes - '20000000' . // 4 bytes; external file attributes - '34120000' . // 4 bytes; relative offset of local header - '746573742e706e67' . // n bytes; file name - '736f6d6520636f6e74656e74' . // m bytes; extra field - '736f6d6520636f6d6d656e74' // o bytes; file comment - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php b/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php deleted file mode 100644 index cc886c7..0000000 --- a/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php +++ /dev/null @@ -1,26 +0,0 @@ -assertSame( - bin2hex(DataDescriptor::generate( - crc32UncompressedData: 0x11111111, - compressedSize: 0x77777777, - uncompressedSize: 0x99999999, - )), - '504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50 - '11111111' . // 4 bytes; CRC-32 of uncompressed data - '77777777' . // 4 bytes; Compressed size - '99999999' // 4 bytes; Uncompressed size - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php b/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php deleted file mode 100644 index be0a907..0000000 --- a/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php +++ /dev/null @@ -1,35 +0,0 @@ -assertSame( - bin2hex(EndOfCentralDirectory::generate( - numberOfThisDisk: 0x00, - numberOfTheDiskWithCentralDirectoryStart: 0x00, - numberOfCentralDirectoryEntriesOnThisDisk: 0x10, - numberOfCentralDirectoryEntries: 0x10, - sizeOfCentralDirectory: 0x22, - centralDirectoryStartOffsetOnDisk: 0x33, - zipFileComment: 'foo', - )), - '504b0506' . // 4 bytes; end of central dir signature 0x06054b50 - '0000' . // 2 bytes; number of this disk - '0000' . // 2 bytes; number of the disk with the start of the central directory - '1000' . // 2 bytes; total number of entries in the central directory on this disk - '1000' . // 2 bytes; total number of entries in the central directory - '22000000' . // 4 bytes; size of the central directory - '33000000' . // 4 bytes; offset of start of central directory with respect to the starting disk number - '0300' . // 2 bytes; .ZIP file comment length - bin2hex('foo') - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php b/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php deleted file mode 100644 index d9e7df1..0000000 --- a/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php +++ /dev/null @@ -1,104 +0,0 @@ -detach(); - } - - /** - * @return null - */ - public function detach() - { - return; - } - - public function getSize(): ?int - { - return null; - } - - public function tell(): int - { - return $this->offset; - } - - public function eof(): bool - { - return false; - } - - public function isSeekable(): bool - { - return true; - } - - public function seek(int $offset, int $whence = SEEK_SET): void - { - switch ($whence) { - case SEEK_SET: - $this->offset = $offset; - break; - case SEEK_CUR: - $this->offset += $offset; - break; - case SEEK_END: - throw new RuntimeException('Infinite Stream!'); - break; - } - } - - public function rewind(): void - { - $this->seek(0); - } - - public function isWritable(): bool - { - return false; - } - - public function write(string $string): int - { - throw new RuntimeException('Not writeable'); - } - - public function isReadable(): bool - { - return true; - } - - public function read(int $length): string - { - $this->offset += $length; - return substr(str_repeat($this->toRepeat, (int) ceil($length / strlen($this->toRepeat))), 0, $length); - } - - public function getContents(): string - { - throw new RuntimeException('Infinite Stream!'); - } - - public function getMetadata(?string $key = null): array|null - { - return $key !== null ? null : []; - } -} diff --git a/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php b/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php deleted file mode 100644 index af9305b..0000000 --- a/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php +++ /dev/null @@ -1,141 +0,0 @@ -context); - - if (!isset($options[self::NAME]['injectFaults'])) { - return false; - } - - $this->mode = $mode; - $this->injectFaults = $options[self::NAME]['injectFaults']; - - if ($this->shouldFail(__FUNCTION__)) { - return false; - } - - return true; - } - - public function stream_write(string $data) - { - if ($this->shouldFail(__FUNCTION__)) { - return false; - } - return true; - } - - public function stream_eof() - { - return true; - } - - public function stream_seek(int $offset, int $whence): bool - { - if ($this->shouldFail(__FUNCTION__)) { - return false; - } - - return true; - } - - public function stream_tell(): int - { - if ($this->shouldFail(__FUNCTION__)) { - return false; - } - - return 0; - } - - public static function register(): void - { - if (!in_array(self::NAME, stream_get_wrappers(), true)) { - stream_wrapper_register(self::NAME, __CLASS__); - } - } - - public function stream_stat(): array - { - static $modeMap = [ - 'r' => 33060, - 'rb' => 33060, - 'r+' => 33206, - 'w' => 33188, - 'wb' => 33188, - ]; - - return [ - 'dev' => 0, - 'ino' => 0, - 'mode' => $modeMap[$this->mode], - 'nlink' => 0, - 'uid' => 0, - 'gid' => 0, - 'rdev' => 0, - 'size' => 0, - 'atime' => 0, - 'mtime' => 0, - 'ctime' => 0, - 'blksize' => 0, - 'blocks' => 0, - ]; - } - - public function url_stat(string $path, int $flags): array - { - return [ - 'dev' => 0, - 'ino' => 0, - 'mode' => 0, - 'nlink' => 0, - 'uid' => 0, - 'gid' => 0, - 'rdev' => 0, - 'size' => 0, - 'atime' => 0, - 'mtime' => 0, - 'ctime' => 0, - 'blksize' => 0, - 'blocks' => 0, - ]; - } - - private static function createStreamContext(array $injectFaults) - { - return stream_context_create([ - self::NAME => ['injectFaults' => $injectFaults], - ]); - } - - private function shouldFail(string $function): bool - { - return in_array($function, $this->injectFaults, true); - } -} diff --git a/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php b/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php deleted file mode 100644 index 196dd0f..0000000 --- a/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php +++ /dev/null @@ -1,47 +0,0 @@ -assertSame( - bin2hex((string) $header), - '504b0304' . // 4 bytes; Local file header signature - '2d00' . // 2 bytes; Version needed to extract (minimum) - '2222' . // 2 bytes; General purpose bit flag - '0800' . // 2 bytes; Compression method; e.g. none = 0, DEFLATE = 8 - '2008' . // 2 bytes; File last modification time - '2154' . // 2 bytes; File last modification date - '11111111' . // 4 bytes; CRC-32 of uncompressed data - '77777777' . // 4 bytes; Compressed size (or 0xffffffff for ZIP64) - '99999999' . // 4 bytes; Uncompressed size (or 0xffffffff for ZIP64) - '0800' . // 2 bytes; File name length (n) - '0c00' . // 2 bytes; Extra field length (m) - '746573742e706e67' . // n bytes; File name - '736f6d6520636f6e74656e74' // m bytes; Extra field - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/PackFieldTest.php b/vendor/maennchen/zipstream-php/test/PackFieldTest.php deleted file mode 100644 index ecd66ba..0000000 --- a/vendor/maennchen/zipstream-php/test/PackFieldTest.php +++ /dev/null @@ -1,42 +0,0 @@ -assertSame( - bin2hex(PackField::pack(new PackField(format: 'v', value: 0x1122))), - '2211', - ); - } - - public function testOverflow2(): void - { - $this->expectException(RuntimeException::class); - - PackField::pack(new PackField(format: 'v', value: 0xFFFFF)); - } - - public function testOverflow4(): void - { - $this->expectException(RuntimeException::class); - - PackField::pack(new PackField(format: 'V', value: 0xFFFFFFFFF)); - } - - public function testUnknownOperator(): void - { - $this->assertSame( - bin2hex(PackField::pack(new PackField(format: 'a', value: 0x1122))), - '34', - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/ResourceStream.php b/vendor/maennchen/zipstream-php/test/ResourceStream.php deleted file mode 100644 index 752a1a3..0000000 --- a/vendor/maennchen/zipstream-php/test/ResourceStream.php +++ /dev/null @@ -1,159 +0,0 @@ -isSeekable()) { - $this->seek(0); - } - return (string) stream_get_contents($this->stream); - } - - public function close(): void - { - $stream = $this->detach(); - if ($stream) { - fclose($stream); - } - } - - public function detach() - { - $result = $this->stream; - // According to the interface, the stream is left in an unusable state; - /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ - $this->stream = null; - return $result; - } - - public function seek(int $offset, int $whence = SEEK_SET): void - { - if (!$this->isSeekable()) { - throw new RuntimeException(); - } - if (fseek($this->stream, $offset, $whence) !== 0) { - // @codeCoverageIgnoreStart - throw new RuntimeException(); - // @codeCoverageIgnoreEnd - } - } - - public function isSeekable(): bool - { - return (bool) $this->getMetadata('seekable'); - } - - public function getMetadata(?string $key = null) - { - $metadata = stream_get_meta_data($this->stream); - return $key !== null ? @$metadata[$key] : $metadata; - } - - public function getSize(): ?int - { - $stats = fstat($this->stream); - return $stats['size']; - } - - public function tell(): int - { - $position = ftell($this->stream); - if ($position === false) { - // @codeCoverageIgnoreStart - throw new RuntimeException(); - // @codeCoverageIgnoreEnd - } - return $position; - } - - public function eof(): bool - { - return feof($this->stream); - } - - public function rewind(): void - { - $this->seek(0); - } - - public function write(string $string): int - { - if (!$this->isWritable()) { - throw new RuntimeException(); - } - if (fwrite($this->stream, $string) === false) { - // @codeCoverageIgnoreStart - throw new RuntimeException(); - // @codeCoverageIgnoreEnd - } - return strlen($string); - } - - public function isWritable(): bool - { - $mode = $this->getMetadata('mode'); - if (!is_string($mode)) { - // @codeCoverageIgnoreStart - throw new RuntimeException('Could not get stream mode from metadata!'); - // @codeCoverageIgnoreEnd - } - return preg_match('/[waxc+]/', $mode) === 1; - } - - public function read(int $length): string - { - if (!$this->isReadable()) { - throw new RuntimeException(); - } - $result = fread($this->stream, $length); - if ($result === false) { - // @codeCoverageIgnoreStart - throw new RuntimeException(); - // @codeCoverageIgnoreEnd - } - return $result; - } - - public function isReadable(): bool - { - $mode = $this->getMetadata('mode'); - if (!is_string($mode)) { - // @codeCoverageIgnoreStart - throw new RuntimeException('Could not get stream mode from metadata!'); - // @codeCoverageIgnoreEnd - } - return preg_match('/[r+]/', $mode) === 1; - } - - public function getContents(): string - { - if (!$this->isReadable()) { - throw new RuntimeException(); - } - $result = stream_get_contents($this->stream); - if ($result === false) { - // @codeCoverageIgnoreStart - throw new RuntimeException(); - // @codeCoverageIgnoreEnd - } - return $result; - } -} diff --git a/vendor/maennchen/zipstream-php/test/Tempfile.php b/vendor/maennchen/zipstream-php/test/Tempfile.php deleted file mode 100644 index 7ef9c61..0000000 --- a/vendor/maennchen/zipstream-php/test/Tempfile.php +++ /dev/null @@ -1,42 +0,0 @@ -getTmpFileStream(); - - $this->tempfile = $tempfile; - $this->tempfileStream = $tempfileStream; - } - - protected function tearDown(): void - { - unlink($this->tempfile); - if (is_resource($this->tempfileStream)) { - fclose($this->tempfileStream); - } - - $this->tempfile = null; - $this->tempfileStream = null; - } - - protected function getTmpFileStream(): array - { - $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); - $stream = fopen($tmp, 'wb+'); - - return [$tmp, $stream]; - } -} diff --git a/vendor/maennchen/zipstream-php/test/TimeTest.php b/vendor/maennchen/zipstream-php/test/TimeTest.php deleted file mode 100644 index 61cfe03..0000000 --- a/vendor/maennchen/zipstream-php/test/TimeTest.php +++ /dev/null @@ -1,44 +0,0 @@ -assertSame( - Time::dateTimeToDosTime(new DateTimeImmutable('2014-11-17T17:46:08Z')), - 1165069764 - ); - - // January 1 1980 - DOS Epoch. - $this->assertSame( - Time::dateTimeToDosTime(new DateTimeImmutable('1980-01-01T00:00:00+00:00')), - 2162688 - ); - - // Local timezone different than UTC. - $prevLocalTimezone = date_default_timezone_get(); - date_default_timezone_set('Europe/Berlin'); - $this->assertSame( - Time::dateTimeToDosTime(new DateTimeImmutable('1980-01-01T00:00:00+00:00')), - 2162688 - ); - date_default_timezone_set($prevLocalTimezone); - } - - public function testTooEarlyDateToDosTime(): void - { - $this->expectException(DosTimeOverflowException::class); - - // January 1 1980 is the minimum DOS Epoch. - Time::dateTimeToDosTime(new DateTimeImmutable('1970-01-01T00:00:00+00:00')); - } -} diff --git a/vendor/maennchen/zipstream-php/test/Util.php b/vendor/maennchen/zipstream-php/test/Util.php deleted file mode 100644 index 86592b4..0000000 --- a/vendor/maennchen/zipstream-php/test/Util.php +++ /dev/null @@ -1,127 +0,0 @@ -cmdExists('hexdump')) { - return ''; - } - - $output = []; - - if (!exec("hexdump -C \"$path\" | head -n 50", $output)) { - return ''; - } - - return "\nHexdump:\n" . implode("\n", $output); - } - - protected function validateAndExtractZip(string $zipPath): string - { - $tmpDir = $this->getTmpDir(); - - $zipArchive = new ZipArchive(); - $result = $zipArchive->open($zipPath); - - if ($result !== true) { - $codeName = $this->zipArchiveOpenErrorCodeName($result); - $debugInformation = $this->dumpZipContents($zipPath); - - $this->fail("Failed to open {$zipPath}. Code: $result ($codeName)$debugInformation"); - - return $tmpDir; - } - - $this->assertSame(0, $zipArchive->status); - $this->assertSame(0, $zipArchive->statusSys); - - $zipArchive->extractTo($tmpDir); - $zipArchive->close(); - - return $tmpDir; - } - - protected function zipArchiveOpenErrorCodeName(int $code): string - { - switch ($code) { - case ZipArchive::ER_EXISTS: return 'ER_EXISTS'; - case ZipArchive::ER_INCONS: return 'ER_INCONS'; - case ZipArchive::ER_INVAL: return 'ER_INVAL'; - case ZipArchive::ER_MEMORY: return 'ER_MEMORY'; - case ZipArchive::ER_NOENT: return 'ER_NOENT'; - case ZipArchive::ER_NOZIP: return 'ER_NOZIP'; - case ZipArchive::ER_OPEN: return 'ER_OPEN'; - case ZipArchive::ER_READ: return 'ER_READ'; - case ZipArchive::ER_SEEK: return 'ER_SEEK'; - default: return 'unknown'; - } - } - - protected function getTmpDir(): string - { - $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); - unlink($tmp); - mkdir($tmp) or $this->fail('Failed to make directory'); - - return $tmp; - } - - /** - * @return string[] - */ - protected function getRecursiveFileList(string $path, bool $includeDirectories = false): array - { - $data = []; - $path = (string) realpath($path); - $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); - - $pathLen = strlen($path); - foreach ($files as $file) { - $filePath = $file->getRealPath(); - - if (is_dir($filePath) && !$includeDirectories) { - continue; - } - - $data[] = substr($filePath, $pathLen + 1); - } - - sort($data); - - return $data; - } -} diff --git a/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php b/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php deleted file mode 100644 index 49fb2cc..0000000 --- a/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertSame( - bin2hex($descriptor), - '504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50 - '11111111' . // 4 bytes; CRC-32 of uncompressed data - '6666666677777777' . // 8 bytes; Compressed size - '8888888899999999' // 8 bytes; Uncompressed size - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php deleted file mode 100644 index 271a298..0000000 --- a/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertSame( - bin2hex($descriptor), - '504b0607' . // 4 bytes; zip64 end of central dir locator signature - 0x07064b50 - '11111111' . // 4 bytes; number of the disk with the start of the zip64 end of central directory - '3333333322222222' . // 28 bytes; relative offset of the zip64 end of central directory record - '44444444' // 4 bytes;total number of disks - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php deleted file mode 100644 index b86fb17..0000000 --- a/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php +++ /dev/null @@ -1,41 +0,0 @@ -assertSame( - bin2hex($descriptor), - '504b0606' . // 4 bytes;zip64 end of central dir signature - 0x06064b50 - '2f00000000000000' . // 8 bytes; size of zip64 end of central directory record - '3333' . // 2 bytes; version made by - '4444' . // 2 bytes; version needed to extract - '55555555' . // 4 bytes; number of this disk - '66666666' . // 4 bytes; number of the disk with the start of the central directory - '8888888877777777' . // 8 bytes; total number of entries in the central directory on this disk - 'aaaaaaaa99999999' . // 8 bytes; total number of entries in the central directory - 'ccccccccbbbbbbbb' . // 8 bytes; size of the central directory - 'eeeeeeeedddddddd' . // 8 bytes; offset of start of central directory with respect to the starting disk number - bin2hex('foo') - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php b/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php deleted file mode 100644 index 904783d..0000000 --- a/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php +++ /dev/null @@ -1,42 +0,0 @@ -assertSame( - bin2hex($extraField), - '0100' . // 2 bytes; Tag for this "extra" block type - '1c00' . // 2 bytes; Size of this "extra" block - '6666666677777777' . // 8 bytes; Original uncompressed file size - '8888888899999999' . // 8 bytes; Size of compressed data - '1111111122222222' . // 8 bytes; Offset of local header record - '33333333' // 4 bytes; Number of the disk on which this file starts - ); - } - - public function testSerializesEmptyCorrectly(): void - { - $extraField = ExtendedInformationExtraField::generate(); - - $this->assertSame( - bin2hex($extraField), - '0100' . // 2 bytes; Tag for this "extra" block type - '0000' // 2 bytes; Size of this "extra" block - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/ZipStreamTest.php b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php index f4ead1d..0aa6535 100644 --- a/vendor/maennchen/zipstream-php/test/ZipStreamTest.php +++ b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php @@ -2,49 +2,80 @@ declare(strict_types=1); -namespace ZipStream\Test; +namespace ZipStreamTest; -use DateTimeImmutable; use GuzzleHttp\Psr7\Response; -use GuzzleHttp\Psr7\StreamWrapper; use org\bovigo\vfs\vfsStream; -use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\StreamInterface; -use RuntimeException; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use ReflectionClass; use ZipArchive; -use ZipStream\CompressionMethod; -use ZipStream\Exception\FileNotFoundException; -use ZipStream\Exception\FileNotReadableException; -use ZipStream\Exception\FileSizeIncorrectException; -use ZipStream\Exception\OverflowException; -use ZipStream\Exception\ResourceActionException; -use ZipStream\Exception\SimulationFileUnknownException; -use ZipStream\Exception\StreamNotReadableException; -use ZipStream\Exception\StreamNotSeekableException; -use ZipStream\OperationMode; -use ZipStream\PackField; +use ZipStream\File; +use ZipStream\Option\Archive as ArchiveOptions; +use ZipStream\Option\File as FileOptions; +use ZipStream\Option\Method; +use ZipStream\Stream; use ZipStream\ZipStream; +/** + * Test Class for the Main ZipStream CLass + */ class ZipStreamTest extends TestCase { - use Util; - use Assertions; - use Tempfile; + public function testFileNotFoundException(): void + { + $this->expectException(\ZipStream\Exception\FileNotFoundException::class); + // Get ZipStream Object + $zip = new ZipStream(); + + // Trigger error by adding a file which doesn't exist + $zip->addFileFromPath('foobar.php', '/foo/bar/foobar.php'); + } + + public function testFileNotReadableException(): void + { + // create new virtual filesystem + $root = vfsStream::setup('vfs'); + // create a virtual file with no permissions + $file = vfsStream::newFile('foo.txt', 0)->at($root)->setContent('bar'); + $zip = new ZipStream(); + $this->expectException(\ZipStream\Exception\FileNotReadableException::class); + $zip->addFileFromPath('foo.txt', $file->url()); + } + + public function testDostime(): void + { + // Allows testing of protected method + $class = new ReflectionClass(File::class); + $method = $class->getMethod('dostime'); + $method->setAccessible(true); + + $this->assertSame($method->invoke(null, 1416246368), 1165069764); + + // January 1 1980 - DOS Epoch. + $this->assertSame($method->invoke(null, 315532800), 2162688); + + // January 1 1970 -> January 1 1980 due to minimum DOS Epoch. @todo Throw Exception? + $this->assertSame($method->invoke(null, 0), 2162688); + } public function testAddFile(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); $zip->addFile('sample.txt', 'Sample String Data'); $zip->addFile('test/sample.txt', 'More Simple Sample Data'); $zip->finish(); + fclose($stream); - $tmpDir = $this->validateAndExtractZip($this->tempfile); + $tmpDir = $this->validateAndExtractZip($tmp); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); @@ -55,10 +86,12 @@ class ZipStreamTest extends TestCase public function testAddFileUtf8NameComment(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); $name = 'árvíztűrő tükörfúrógép.txt'; $content = 'Sample String Data'; @@ -67,26 +100,32 @@ class ZipStreamTest extends TestCase 'from Hungarian language in lowercase. ' . 'In uppercase: ÁÍŰŐÜÖÚÓÉ'; - $zip->addFile(fileName: $name, data: $content, comment: $comment); - $zip->finish(); + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); - $tmpDir = $this->validateAndExtractZip($this->tempfile); + $zip->addFile($name, $content, $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame([$name], $files); $this->assertStringEqualsFile($tmpDir . '/' . $name, $content); - $zipArchive = new ZipArchive(); - $zipArchive->open($this->tempfile); - $this->assertSame($comment, $zipArchive->getCommentName($name)); + $zipArch = new ZipArchive(); + $zipArch->open($tmp); + $this->assertSame($comment, $zipArch->getCommentName($name)); } public function testAddFileUtf8NameNonUtfComment(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); $name = 'á.txt'; $content = 'any'; @@ -98,111 +137,101 @@ class ZipStreamTest extends TestCase // nearly CP850 (DOS-Latin-1) $guessComment = mb_convert_encoding($comment, 'UTF-8', 'CP850'); - $zip->addFile(fileName: $name, data: $content, comment: $comment); + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); + $zip->addFile($name, $content, $fileOptions); $zip->finish(); + fclose($stream); $zipArch = new ZipArchive(); - $zipArch->open($this->tempfile); + $zipArch->open($tmp); $this->assertSame($guessComment, $zipArch->getCommentName($name)); $this->assertSame($comment, $zipArch->getCommentName($name, ZipArchive::FL_ENC_RAW)); } + public function testAddFileNonUtf8NameUtfComment(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = mb_convert_encoding('á.txt', 'ISO-8859-2', 'UTF-8'); + $content = 'any'; + $comment = 'á'; + + // @see https://libzip.org/documentation/zip_get_name.html + // + // mb_convert_encoding hasn't CP437. + // nearly CP850 (DOS-Latin-1) + $guessName = mb_convert_encoding($name, 'UTF-8', 'CP850'); + + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); + + $zip->addFile($name, $content, $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + + $this->assertNotSame([$name], $files); + $this->assertSame([$guessName], $files); + $this->assertStringEqualsFile($tmpDir . '/' . $guessName, $content); + + $zipArch = new ZipArchive(); + $zipArch->open($tmp); + $this->assertSame($guessName, $zipArch->getNameIndex(0)); + $this->assertSame($name, $zipArch->getNameIndex(0, ZipArchive::FL_ENC_RAW)); + $this->assertSame($comment, $zipArch->getCommentName($guessName)); + } + public function testAddFileWithStorageMethod(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); - $zip->addFile(fileName: 'sample.txt', data: 'Sample String Data', compressionMethod: CompressionMethod::STORE); - $zip->addFile(fileName: 'test/sample.txt', data: 'More Simple Sample Data'); + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $zip->addFile('sample.txt', 'Sample String Data', $fileOptions); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); $zip->finish(); + fclose($stream); - $zipArchive = new ZipArchive(); - $zipArchive->open($this->tempfile); + $zipArch = new ZipArchive(); + $zipArch->open($tmp); - $sample1 = $zipArchive->statName('sample.txt'); - $sample12 = $zipArchive->statName('test/sample.txt'); - $this->assertSame($sample1['comp_method'], CompressionMethod::STORE->value); - $this->assertSame($sample12['comp_method'], CompressionMethod::DEFLATE->value); + $sample1 = $zipArch->statName('sample.txt'); + $sample12 = $zipArch->statName('test/sample.txt'); + $this->assertSame($sample1['comp_method'], Method::STORE); + $this->assertSame($sample12['comp_method'], Method::DEFLATE); - $zipArchive->close(); + $zipArch->close(); } public function testAddFileFromPath(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); [$tmpExample, $streamExample] = $this->getTmpFileStream(); fwrite($streamExample, 'Sample String Data'); fclose($streamExample); - $zip->addFileFromPath(fileName: 'sample.txt', path: $tmpExample); - - [$tmpExample, $streamExample] = $this->getTmpFileStream(); - fwrite($streamExample, 'More Simple Sample Data'); - fclose($streamExample); - $zip->addFileFromPath(fileName: 'test/sample.txt', path: $tmpExample); - - $zip->finish(); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); - - $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); - $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); - - unlink($tmpExample); - } - - public function testAddFileFromPathFileNotFoundException(): void - { - $this->expectException(FileNotFoundException::class); - - // Get ZipStream Object - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); - - // Trigger error by adding a file which doesn't exist - $zip->addFileFromPath(fileName: 'foobar.php', path: '/foo/bar/foobar.php'); - } - - public function testAddFileFromPathFileNotReadableException(): void - { - $this->expectException(FileNotReadableException::class); - - // create new virtual filesystem - $root = vfsStream::setup('vfs'); - // create a virtual file with no permissions - $file = vfsStream::newFile('foo.txt', 0)->at($root)->setContent('bar'); - - // Get ZipStream Object - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); - - $zip->addFileFromPath('foo.txt', $file->url()); - } - - public function testAddFileFromPathWithStorageMethod(): void - { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); - - [$tmpExample, $streamExample] = $this->getTmpFileStream(); - fwrite($streamExample, 'Sample String Data'); - fclose($streamExample); - $zip->addFileFromPath(fileName: 'sample.txt', path: $tmpExample, compressionMethod: CompressionMethod::STORE); + $zip->addFileFromPath('sample.txt', $tmpExample); [$tmpExample, $streamExample] = $this->getTmpFileStream(); fwrite($streamExample, 'More Simple Sample Data'); @@ -210,32 +239,65 @@ class ZipStreamTest extends TestCase $zip->addFileFromPath('test/sample.txt', $tmpExample); $zip->finish(); + fclose($stream); - $zipArchive = new ZipArchive(); - $zipArchive->open($this->tempfile); + $tmpDir = $this->validateAndExtractZip($tmp); - $sample1 = $zipArchive->statName('sample.txt'); - $this->assertSame(CompressionMethod::STORE->value, $sample1['comp_method']); + $files = $this->getRecursiveFileList($tmpDir); + $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); - $sample2 = $zipArchive->statName('test/sample.txt'); - $this->assertSame(CompressionMethod::DEFLATE->value, $sample2['comp_method']); + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } - $zipArchive->close(); + public function testAddFileFromPathWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'Sample String Data'); + fclose($streamExample); + $zip->addFileFromPath('sample.txt', $tmpExample, $fileOptions); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'More Simple Sample Data'); + fclose($streamExample); + $zip->addFileFromPath('test/sample.txt', $tmpExample); + + $zip->finish(); + fclose($stream); + + $zipArch = new ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $this->assertSame(Method::STORE, $sample1['comp_method']); + + $sample2 = $zipArch->statName('test/sample.txt'); + $this->assertSame(Method::DEFLATE, $sample2['comp_method']); + + $zipArch->close(); } public function testAddLargeFileFromPath(): void { - foreach ([CompressionMethod::DEFLATE, CompressionMethod::STORE] as $compressionMethod) { - foreach ([false, true] as $zeroHeader) { - foreach ([false, true] as $zip64) { - if ($zeroHeader && $compressionMethod === CompressionMethod::DEFLATE) { + $methods = [Method::DEFLATE(), Method::STORE()]; + $falseTrue = [false, true]; + foreach ($methods as $method) { + foreach ($falseTrue as $zeroHeader) { + foreach ($falseTrue as $zip64) { + if ($zeroHeader && $method->equals(Method::DEFLATE())) { continue; } - $this->addLargeFileFileFromPath( - compressionMethod: $compressionMethod, - zeroHeader: $zeroHeader, - zip64: $zip64 - ); + $this->addLargeFileFileFromPath($method, $zeroHeader, $zip64); } } } @@ -243,27 +305,33 @@ class ZipStreamTest extends TestCase public function testAddFileFromStream(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); // In this test we can't use temporary stream to feed data // because zlib.deflate filter gives empty string before PHP 7 // it works fine with file stream $streamExample = fopen(__FILE__, 'rb'); $zip->addFileFromStream('sample.txt', $streamExample); - fclose($streamExample); +// fclose($streamExample); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); $streamExample2 = fopen('php://temp', 'wb+'); fwrite($streamExample2, 'More Simple Sample Data'); rewind($streamExample2); // move the pointer back to the beginning of file. - $zip->addFileFromStream('test/sample.txt', $streamExample2); //, $fileOptions); - fclose($streamExample2); + $zip->addFileFromStream('test/sample.txt', $streamExample2, $fileOptions); +// fclose($streamExample2); $zip->finish(); + fclose($stream); - $tmpDir = $this->validateAndExtractZip($this->tempfile); + $tmpDir = $this->validateAndExtractZip($tmp); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); @@ -272,409 +340,92 @@ class ZipStreamTest extends TestCase $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); } - public function testAddFileFromStreamUnreadableInput(): void - { - $this->expectException(StreamNotReadableException::class); - - [$tmpInput] = $this->getTmpFileStream(); - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); - - $streamUnreadable = fopen($tmpInput, 'w'); - - $zip->addFileFromStream('sample.json', $streamUnreadable); - } - - public function testAddFileFromStreamBrokenOutputWrite(): void - { - $this->expectException(ResourceActionException::class); - - $outputStream = FaultInjectionResource::getResource(['stream_write']); - - $zip = new ZipStream( - outputStream: $outputStream, - sendHttpHeaders: false, - ); - - $zip->addFile('sample.txt', 'foobar'); - } - - public function testAddFileFromStreamBrokenInputRewind(): void - { - $this->expectException(ResourceActionException::class); - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - defaultEnableZeroHeader: false, - ); - - $fileStream = FaultInjectionResource::getResource(['stream_seek']); - - $zip->addFileFromStream('sample.txt', $fileStream, maxSize: 0); - } - - public function testAddFileFromStreamUnseekableInputWithoutZeroHeader(): void - { - $this->expectException(StreamNotSeekableException::class); - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - defaultEnableZeroHeader: false, - ); - - if (file_exists('/dev/null')) { - $streamUnseekable = fopen('/dev/null', 'w+'); - } elseif (file_exists('NUL')) { - $streamUnseekable = fopen('NUL', 'w+'); - } else { - $this->markTestSkipped('Needs file /dev/null'); - } - - $zip->addFileFromStream('sample.txt', $streamUnseekable, maxSize: 2); - } - - public function testAddFileFromStreamUnseekableInputWithZeroHeader(): void - { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - defaultEnableZeroHeader: true, - defaultCompressionMethod: CompressionMethod::STORE, - ); - - $streamUnseekable = StreamWrapper::getResource(new class ('test') extends EndlessCycleStream { - public function isSeekable(): bool - { - return false; - } - - public function seek(int $offset, int $whence = SEEK_SET): void - { - throw new RuntimeException('Not seekable'); - } - }); - - $zip->addFileFromStream('sample.txt', $streamUnseekable, maxSize: 7); - - $zip->finish(); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertSame(['sample.txt'], $files); - - $this->assertSame(filesize($tmpDir . '/sample.txt'), 7); - } - public function testAddFileFromStreamWithStorageMethod(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); $streamExample = fopen('php://temp', 'wb+'); fwrite($streamExample, 'Sample String Data'); rewind($streamExample); // move the pointer back to the beginning of file. - $zip->addFileFromStream('sample.txt', $streamExample, compressionMethod: CompressionMethod::STORE); - fclose($streamExample); + $zip->addFileFromStream('sample.txt', $streamExample, $fileOptions); +// fclose($streamExample); $streamExample2 = fopen('php://temp', 'bw+'); fwrite($streamExample2, 'More Simple Sample Data'); rewind($streamExample2); // move the pointer back to the beginning of file. - $zip->addFileFromStream('test/sample.txt', $streamExample2, compressionMethod: CompressionMethod::DEFLATE); - fclose($streamExample2); + $zip->addFileFromStream('test/sample.txt', $streamExample2); +// fclose($streamExample2); $zip->finish(); + fclose($stream); - $zipArchive = new ZipArchive(); - $zipArchive->open($this->tempfile); + $zipArch = new ZipArchive(); + $zipArch->open($tmp); - $sample1 = $zipArchive->statName('sample.txt'); - $this->assertSame(CompressionMethod::STORE->value, $sample1['comp_method']); + $sample1 = $zipArch->statName('sample.txt'); + $this->assertSame(Method::STORE, $sample1['comp_method']); - $sample2 = $zipArchive->statName('test/sample.txt'); - $this->assertSame(CompressionMethod::DEFLATE->value, $sample2['comp_method']); + $sample2 = $zipArch->statName('test/sample.txt'); + $this->assertSame(Method::DEFLATE, $sample2['comp_method']); - $zipArchive->close(); + $zipArch->close(); } public function testAddFileFromPsr7Stream(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); $body = 'Sample String Data'; $response = new Response(200, [], $body); - $zip->addFileFromPsr7Stream('sample.json', $response->getBody()); - $zip->finish(); + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); - $tmpDir = $this->validateAndExtractZip($this->tempfile); + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.json'], $files); $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); } - #[Group('slow')] - public function testAddLargeFileFromPsr7Stream(): void - { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: true, - ); - - $zip->addFileFromPsr7Stream( - fileName: 'sample.json', - stream: new EndlessCycleStream('0'), - maxSize: 0x100000000, - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - $zip->finish(); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertSame(['sample.json'], $files); - $this->assertFileIsReadable($tmpDir . '/sample.json'); - $this->assertStringStartsWith('000000', file_get_contents(filename: $tmpDir . '/sample.json', length: 20)); - } - - public function testContinueFinishedZip(): void - { - $this->expectException(RuntimeException::class); - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); - $zip->finish(); - - $zip->addFile('sample.txt', '1234'); - } - - #[Group('slow')] - public function testManyFilesWithoutZip64(): void - { - $this->expectException(OverflowException::class); - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: false, - ); - - for ($i = 0; $i <= 0xFFFF; $i++) { - $zip->addFile('sample' . $i, ''); - } - - $zip->finish(); - } - - #[Group('slow')] - public function testManyFilesWithZip64(): void - { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: true, - ); - - for ($i = 0; $i <= 0xFFFF; $i++) { - $zip->addFile('sample' . $i, ''); - } - - $zip->finish(); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - - $files = $this->getRecursiveFileList($tmpDir); - - $this->assertSame(count($files), 0x10000); - } - - #[Group('slow')] - public function testLongZipWithout64(): void - { - $this->expectException(OverflowException::class); - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: false, - defaultCompressionMethod: CompressionMethod::STORE, - ); - - for ($i = 0; $i < 4; $i++) { - $zip->addFileFromPsr7Stream( - fileName: 'sample' . $i, - stream: new EndlessCycleStream('0'), - maxSize: 0xFFFFFFFF, - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - } - } - - #[Group('slow')] - public function testLongZipWith64(): void - { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: true, - defaultCompressionMethod: CompressionMethod::STORE, - ); - - for ($i = 0; $i < 4; $i++) { - $zip->addFileFromPsr7Stream( - fileName: 'sample' . $i, - stream: new EndlessCycleStream('0'), - maxSize: 0x5FFFFFFF, - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - } - - $zip->finish(); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertSame(['sample0', 'sample1', 'sample2', 'sample3'], $files); - } - - #[Group('slow')] - public function testAddLargeFileWithoutZip64WithZeroHeader(): void - { - $this->expectException(OverflowException::class); - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: false, - defaultEnableZeroHeader: true, - ); - - $zip->addFileFromPsr7Stream( - fileName: 'sample.json', - stream: new EndlessCycleStream('0'), - maxSize: 0x100000000, - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - } - - #[Group('slow')] - public function testAddsZip64HeaderWhenNeeded(): void - { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: true, - defaultEnableZeroHeader: false, - ); - - $zip->addFileFromPsr7Stream( - fileName: 'sample.json', - stream: new EndlessCycleStream('0'), - maxSize: 0x100000000, - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - - $zip->finish(); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - $files = $this->getRecursiveFileList($tmpDir); - - $this->assertSame(['sample.json'], $files); - $this->assertFileContains($this->tempfile, PackField::pack( - new PackField(format: 'V', value: 0x06064b50) - )); - } - - #[Group('slow')] - public function testDoesNotAddZip64HeaderWhenNotNeeded(): void - { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: true, - defaultEnableZeroHeader: false, - ); - - $zip->addFileFromPsr7Stream( - fileName: 'sample.json', - stream: new EndlessCycleStream('0'), - maxSize: 0x10, - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - - $zip->finish(); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - $files = $this->getRecursiveFileList($tmpDir); - - $this->assertSame(['sample.json'], $files); - $this->assertFileDoesNotContain($this->tempfile, PackField::pack( - new PackField(format: 'V', value: 0x06064b50) - )); - } - - #[Group('slow')] - public function testAddLargeFileWithoutZip64WithoutZeroHeader(): void - { - $this->expectException(OverflowException::class); - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - enableZip64: false, - defaultEnableZeroHeader: false, - ); - - $zip->addFileFromPsr7Stream( - fileName: 'sample.json', - stream: new EndlessCycleStream('0'), - maxSize: 0x100000000, - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - } - public function testAddFileFromPsr7StreamWithOutputToPsr7Stream(): void { - $psr7OutputStream = new ResourceStream($this->tempfileStream); + [$tmp, $resource] = $this->getTmpFileStream(); + $psr7OutputStream = new Stream($resource); - $zip = new ZipStream( - outputStream: $psr7OutputStream, - sendHttpHeaders: false, - ); + $options = new ArchiveOptions(); + $options->setOutputStream($psr7OutputStream); + + $zip = new ZipStream(null, $options); $body = 'Sample String Data'; $response = new Response(200, [], $body); - $zip->addFileFromPsr7Stream( - fileName: 'sample.json', - stream: $response->getBody(), - compressionMethod: CompressionMethod::STORE, - ); + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); $zip->finish(); $psr7OutputStream->close(); - $tmpDir = $this->validateAndExtractZip($this->tempfile); + $tmpDir = $this->validateAndExtractZip($tmp); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.json'], $files); @@ -683,10 +434,12 @@ class ZipStreamTest extends TestCase public function testAddFileFromPsr7StreamWithFileSizeSet(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); $body = 'Sample String Data'; $fileSize = strlen($body); @@ -694,63 +447,37 @@ class ZipStreamTest extends TestCase $fakePadding = "\0\0\0\0\0\0"; $response = new Response(200, [], $body . $fakePadding); - $zip->addFileFromPsr7Stream( - fileName: 'sample.json', - stream: $response->getBody(), - compressionMethod: CompressionMethod::STORE, - maxSize: $fileSize - ); + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + $fileOptions->setSize($fileSize); + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); $zip->finish(); + fclose($stream); - $tmpDir = $this->validateAndExtractZip($this->tempfile); + $tmpDir = $this->validateAndExtractZip($tmp); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.json'], $files); $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); } - public function testCreateArchiveHeaders(): void - { - $headers = []; - - $httpHeaderCallback = function (string $header) use (&$headers) { - $headers[] = $header; - }; - - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: true, - outputName: 'example.zip', - httpHeaderCallback: $httpHeaderCallback, - ); - - $zip->addFile( - fileName: 'sample.json', - data: 'foo', - ); - $zip->finish(); - - $this->assertContains('Content-Type: application/x-zip', $headers); - $this->assertContains("Content-Disposition: attachment; filename*=UTF-8''example.zip", $headers); - $this->assertContains('Pragma: public', $headers); - $this->assertContains('Cache-Control: public, must-revalidate', $headers); - $this->assertContains('Content-Transfer-Encoding: binary', $headers); - } - public function testCreateArchiveWithFlushOptionSet(): void { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - flushOutput: true, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); $zip->addFile('sample.txt', 'Sample String Data'); $zip->addFile('test/sample.txt', 'More Simple Sample Data'); $zip->finish(); + fclose($stream); - $tmpDir = $this->validateAndExtractZip($this->tempfile); + $tmpDir = $this->validateAndExtractZip($tmp); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); @@ -765,433 +492,111 @@ class ZipStreamTest extends TestCase ob_end_flush(); $this->assertSame(0, ob_get_level()); - $zip = new ZipStream( - outputStream: $this->tempfileStream, - flushOutput: true, - sendHttpHeaders: false, - ); + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); $zip->addFile('sample.txt', 'Sample String Data'); $zip->finish(); + fclose($stream); - $tmpDir = $this->validateAndExtractZip($this->tempfile); + $tmpDir = $this->validateAndExtractZip($tmp); $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); // WORKAROUND (2/2): add back output buffering so that PHPUnit doesn't complain that it is missing ob_start(); } - public function testAddEmptyDirectory(): void + /** + * @return array + */ + protected function getTmpFileStream(): array { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - ); + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + $stream = fopen($tmp, 'wb+'); - $zip->addDirectory('foo'); - - $zip->finish(); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - - $files = $this->getRecursiveFileList($tmpDir, includeDirectories: true); - - $this->assertContains('foo', $files); - - $this->assertFileExists($tmpDir . DIRECTORY_SEPARATOR . 'foo'); - $this->assertDirectoryExists($tmpDir . DIRECTORY_SEPARATOR . 'foo'); + return [$tmp, $stream]; } - public function testAddFileSimulate(): void + /** + * @param string $tmp + * @return string + */ + protected function validateAndExtractZip($tmp): string { - $create = function (OperationMode $operationMode): int { - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: $operationMode, - defaultEnableZeroHeader: true, - outputStream: $this->tempfileStream, - ); + $tmpDir = $this->getTmpDir(); - $zip->addFile('sample.txt', 'Sample String Data'); - $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + $zipArch = new ZipArchive(); + $res = $zipArch->open($tmp); - return $zip->finish(); - }; + if ($res !== true) { + $this->fail("Failed to open {$tmp}. Code: $res"); + return $tmpDir; + } - $sizeExpected = $create(OperationMode::NORMAL); - $sizeActual = $create(OperationMode::SIMULATE_LAX); + $this->assertSame(0, $zipArch->status); + $this->assertSame(0, $zipArch->statusSys); - $this->assertEquals($sizeExpected, $sizeActual); + $zipArch->extractTo($tmpDir); + $zipArch->close(); + + return $tmpDir; } - public function testAddFileSimulateWithMaxSize(): void + protected function getTmpDir(): string { - $create = function (OperationMode $operationMode): int { - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: $operationMode, - defaultCompressionMethod: CompressionMethod::STORE, - defaultEnableZeroHeader: true, - outputStream: $this->tempfileStream, - ); + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + unlink($tmp); + mkdir($tmp) or $this->fail('Failed to make directory'); - $zip->addFile('sample.txt', 'Sample String Data', maxSize: 0); - - return $zip->finish(); - }; - - - $sizeExpected = $create(OperationMode::NORMAL); - $sizeActual = $create(OperationMode::SIMULATE_LAX); - - $this->assertEquals($sizeExpected, $sizeActual); + return $tmp; } - public function testAddFileSimulateWithFstat(): void + /** + * @param string $path + * @return string[] + */ + protected function getRecursiveFileList(string $path): array { - $create = function (OperationMode $operationMode): int { - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: $operationMode, - defaultCompressionMethod: CompressionMethod::STORE, - defaultEnableZeroHeader: true, - outputStream: $this->tempfileStream, - ); + $data = []; + $path = (string)realpath($path); + $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); - $zip->addFile('sample.txt', 'Sample String Data'); - $zip->addFile('test/sample.txt', 'More Simple Sample Data'); - - return $zip->finish(); - }; - - - $sizeExpected = $create(OperationMode::NORMAL); - $sizeActual = $create(OperationMode::SIMULATE_LAX); - - $this->assertEquals($sizeExpected, $sizeActual); - } - - public function testAddFileSimulateWithExactSizeZero(): void - { - $create = function (OperationMode $operationMode): int { - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: $operationMode, - defaultCompressionMethod: CompressionMethod::STORE, - defaultEnableZeroHeader: true, - outputStream: $this->tempfileStream, - ); - - $zip->addFile('sample.txt', 'Sample String Data', exactSize: 18); - - return $zip->finish(); - }; - - - $sizeExpected = $create(OperationMode::NORMAL); - $sizeActual = $create(OperationMode::SIMULATE_LAX); - - $this->assertEquals($sizeExpected, $sizeActual); - } - - public function testAddFileSimulateWithExactSizeInitial(): void - { - $create = function (OperationMode $operationMode): int { - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: $operationMode, - defaultCompressionMethod: CompressionMethod::STORE, - defaultEnableZeroHeader: false, - outputStream: $this->tempfileStream, - ); - - $zip->addFile('sample.txt', 'Sample String Data', exactSize: 18); - - return $zip->finish(); - }; - - $sizeExpected = $create(OperationMode::NORMAL); - $sizeActual = $create(OperationMode::SIMULATE_LAX); - - $this->assertEquals($sizeExpected, $sizeActual); - } - - public function testAddFileSimulateWithZeroSizeInFstat(): void - { - $create = function (OperationMode $operationMode): int { - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: $operationMode, - defaultCompressionMethod: CompressionMethod::STORE, - defaultEnableZeroHeader: false, - outputStream: $this->tempfileStream, - ); - - $zip->addFileFromPsr7Stream('sample.txt', new class implements StreamInterface { - public $pos = 0; - - public function __toString(): string - { - return 'test'; - } - - public function close(): void {} - - public function detach() {} - - public function getSize(): ?int - { - return null; - } - - public function tell(): int - { - return $this->pos; - } - - public function eof(): bool - { - return $this->pos >= 4; - } - - public function isSeekable(): bool - { - return true; - } - - public function seek(int $offset, int $whence = SEEK_SET): void - { - $this->pos = $offset; - } - - public function rewind(): void - { - $this->pos = 0; - } - - public function isWritable(): bool - { - return false; - } - - public function write(string $string): int - { - return 0; - } - - public function isReadable(): bool - { - return true; - } - - public function read(int $length): string - { - $data = substr('test', $this->pos, $length); - $this->pos += strlen($data); - return $data; - } - - public function getContents(): string - { - return $this->read(4); - } - - public function getMetadata(?string $key = null) - { - return $key !== null ? null : []; - } - }); - - return $zip->finish(); - }; - - $sizeExpected = $create(OperationMode::NORMAL); - $sizeActual = $create(OperationMode::SIMULATE_LAX); - - - $this->assertEquals($sizeExpected, $sizeActual); - } - - public function testAddFileSimulateWithWrongExactSize(): void - { - $this->expectException(FileSizeIncorrectException::class); - - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: OperationMode::SIMULATE_LAX, - ); - - $zip->addFile('sample.txt', 'Sample String Data', exactSize: 1000); - } - - public function testAddFileSimulateStrictZero(): void - { - $this->expectException(SimulationFileUnknownException::class); - - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: OperationMode::SIMULATE_STRICT, - defaultEnableZeroHeader: true - ); - - $zip->addFile('sample.txt', 'Sample String Data'); - } - - public function testAddFileSimulateStrictInitial(): void - { - $this->expectException(SimulationFileUnknownException::class); - - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: OperationMode::SIMULATE_STRICT, - defaultEnableZeroHeader: false - ); - - $zip->addFile('sample.txt', 'Sample String Data'); - } - - public function testAddFileCallbackStrict(): void - { - $this->expectException(SimulationFileUnknownException::class); - - $zip = new ZipStream( - sendHttpHeaders: false, - operationMode: OperationMode::SIMULATE_STRICT, - defaultEnableZeroHeader: false - ); - - $zip->addFileFromCallback('sample.txt', callback: function () { - return ''; - }); - } - - public function testAddFileCallbackLax(): void - { - $zip = new ZipStream( - operationMode: OperationMode::SIMULATE_LAX, - defaultEnableZeroHeader: false, - sendHttpHeaders: false, - ); - - $zip->addFileFromCallback('sample.txt', callback: function () { - return 'Sample String Data'; - }); - - $size = $zip->finish(); - - $this->assertEquals($size, 142); - } - - public function testExecuteSimulation(): void - { - $zip = new ZipStream( - operationMode: OperationMode::SIMULATE_STRICT, - defaultCompressionMethod: CompressionMethod::STORE, - defaultEnableZeroHeader: false, - sendHttpHeaders: false, - outputStream: $this->tempfileStream, - ); - - $zip->addFileFromCallback( - 'sample.txt', - exactSize: 18, - callback: function () { - return 'Sample String Data'; + $pathLen = strlen($path); + foreach ($files as $file) { + $filePath = $file->getRealPath(); + if (!is_dir($filePath)) { + $data[] = substr($filePath, $pathLen + 1); } - ); + } - $zip->addFileFromCallback( - '.gitkeep', - exactSize: 0, - callback: function () { - return ''; - } - ); + sort($data); - $size = $zip->finish(); - - $this->assertEquals(filesize($this->tempfile), 0); - - $zip->executeSimulation(); - - clearstatcache(); - - $this->assertEquals(filesize($this->tempfile), $size); - - $tmpDir = $this->validateAndExtractZip($this->tempfile); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertSame(['.gitkeep', 'sample.txt'], $files); + return $data; } - public function testExecuteSimulationBeforeFinish(): void - { - $this->expectException(RuntimeException::class); - - $zip = new ZipStream( - operationMode: OperationMode::SIMULATE_LAX, - defaultEnableZeroHeader: false, - sendHttpHeaders: false, - outputStream: $this->tempfileStream, - ); - - $zip->executeSimulation(); - } - - #[Group('slow')] - public function testSimulationWithLargeZip64AndZeroHeader(): void - { - $zip = new ZipStream( - outputStream: $this->tempfileStream, - sendHttpHeaders: false, - operationMode: OperationMode::SIMULATE_STRICT, - defaultCompressionMethod: CompressionMethod::STORE, - outputName: 'archive.zip', - enableZip64: true, - defaultEnableZeroHeader: true - ); - - $zip->addFileFromPsr7Stream( - fileName: 'large', - stream: new EndlessCycleStream('large'), - exactSize: 0x120000000, // ~5gb - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - - $zip->addFileFromPsr7Stream( - fileName: 'small', - stream: new EndlessCycleStream('small'), - exactSize: 0x20, - compressionMethod: CompressionMethod::STORE, - lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), - ); - - $forecastedSize = $zip->finish(); - - $zip->executeSimulation(); - - $this->assertSame($forecastedSize, filesize($this->tempfile)); - - $this->validateAndExtractZip($this->tempfile); - } - - private function addLargeFileFileFromPath(CompressionMethod $compressionMethod, $zeroHeader, $zip64): void + protected function addLargeFileFileFromPath($method, $zeroHeader, $zip64): void { [$tmp, $stream] = $this->getTmpFileStream(); - $zip = new ZipStream( - outputStream: $stream, - sendHttpHeaders: false, - defaultEnableZeroHeader: $zeroHeader, - enableZip64: $zip64, - ); + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setLargeFileMethod($method); + $options->setLargeFileSize(5); + $options->setZeroHeader($zeroHeader); + $options->setEnableZip64($zip64); + + $zip = new ZipStream(null, $options); [$tmpExample, $streamExample] = $this->getTmpFileStream(); for ($i = 0; $i <= 10000; $i++) { - fwrite($streamExample, sha1((string) $i)); + fwrite($streamExample, sha1((string)$i)); if ($i % 100 === 0) { fwrite($streamExample, "\n"); } @@ -1209,8 +614,6 @@ class ZipStreamTest extends TestCase $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt'], $files); - $this->assertSame(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$compressionMethod->value}"); - - unlink($tmp); + $this->assertSame(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$method}"); } } diff --git a/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php b/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php deleted file mode 100644 index 2b8dbed..0000000 --- a/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php +++ /dev/null @@ -1,22 +0,0 @@ -assertSame( - bin2hex((string) $extraField), - '5356' . // 2 bytes; Tag for this "extra" block type - '0000' // 2 bytes; TODO: Document - ); - } -} diff --git a/vendor/maennchen/zipstream-php/test/bug/BugHonorFileTimeTest.php b/vendor/maennchen/zipstream-php/test/bug/BugHonorFileTimeTest.php new file mode 100644 index 0000000..05de4fe --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/bug/BugHonorFileTimeTest.php @@ -0,0 +1,40 @@ +setOutputStream(fopen('php://memory', 'wb')); + $fileOpt->setTime(clone $expectedTime); + + $zip = new ZipStream(null, $archiveOpt); + + $zip->addFile('sample.txt', 'Sample', $fileOpt); + + $zip->finish(); + + $this->assertEquals($expectedTime, $fileOpt->getTime()); + } +} diff --git a/vendor/myclabs/php-enum/LICENSE b/vendor/myclabs/php-enum/LICENSE new file mode 100644 index 0000000..2a8cf22 --- /dev/null +++ b/vendor/myclabs/php-enum/LICENSE @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2015 My C-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/myclabs/php-enum/README.md b/vendor/myclabs/php-enum/README.md new file mode 100644 index 0000000..681d55e --- /dev/null +++ b/vendor/myclabs/php-enum/README.md @@ -0,0 +1,194 @@ +# PHP Enum implementation inspired from SplEnum + +[![GitHub Actions][GA Image]][GA Link] +[![Latest Stable Version](https://poser.pugx.org/myclabs/php-enum/version.png)](https://packagist.org/packages/myclabs/php-enum) +[![Total Downloads](https://poser.pugx.org/myclabs/php-enum/downloads.png)](https://packagist.org/packages/myclabs/php-enum) +[![Psalm Shepherd][Shepherd Image]][Shepherd Link] + +Maintenance for this project is [supported via Tidelift](https://tidelift.com/subscription/pkg/packagist-myclabs-php-enum?utm_source=packagist-myclabs-php-enum&utm_medium=referral&utm_campaign=readme). + +## Why? + +First, and mainly, `SplEnum` is not integrated to PHP, you have to install the extension separately. + +Using an enum instead of class constants provides the following advantages: + +- You can use an enum as a parameter type: `function setAction(Action $action) {` +- You can use an enum as a return type: `function getAction() : Action {` +- You can enrich the enum with methods (e.g. `format`, `parse`, …) +- You can extend the enum to add new values (make your enum `final` to prevent it) +- You can get a list of all the possible values (see below) + +This Enum class is not intended to replace class constants, but only to be used when it makes sense. + +## Installation + +``` +composer require myclabs/php-enum +``` + +## Declaration + +```php +use MyCLabs\Enum\Enum; + +/** + * Action enum + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Usage + +```php +$action = Action::VIEW(); + +// or with a dynamic key: +$action = Action::$key(); +// or with a dynamic value: +$action = Action::from($value); +// or +$action = new Action($value); +``` + +As you can see, static methods are automatically implemented to provide quick access to an enum value. + +One advantage over using class constants is to be able to use an enum as a parameter type: + +```php +function setAction(Action $action) { + // ... +} +``` + +## Documentation + +- `__construct()` The constructor checks that the value exist in the enum +- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) +- `getValue()` Returns the current value of the enum +- `getKey()` Returns the key of the current value on Enum +- `equals()` Tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise) + +Static methods: + +- `from()` Creates an Enum instance, checking that the value exist in the enum +- `toArray()` method Returns all possible values as an array (constant name in key, constant value in value) +- `keys()` Returns the names (keys) of all constants in the Enum class +- `values()` Returns instances of the Enum class of all Enum constants (constant name in key, Enum instance in value) +- `isValid()` Check if tested value is valid on enum set +- `isValidKey()` Check if tested key is valid on enum set +- `assertValidValue()` Assert the value is valid on enum set, throwing exception otherwise +- `search()` Return key for searched value + +### Static methods + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} + +// Static method: +$action = Action::VIEW(); +$action = Action::EDIT(); +``` + +Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). + +If you care about IDE autocompletion, you can either implement the static methods yourself: + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + + /** + * @return Action + */ + public static function VIEW() { + return new Action(self::VIEW); + } +} +``` + +or you can use phpdoc (this is supported in PhpStorm for example): + +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Native enums and migration +Native enum arrived to PHP in version 8.1: https://www.php.net/enumerations +If your project is running PHP 8.1+ or your library has it as a minimum requirement you should use it instead of this library. + +When migrating from `myclabs/php-enum`, the effort should be small if the usage was in the recommended way: +- private constants +- final classes +- no method overridden + +Changes for migration: +- Class definition should be changed from +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + to +```php +enum Action: string +{ + case VIEW = 'view'; + case EDIT = 'edit'; +} +``` +All places where the class was used as a type will continue to work. + +Usages and the change needed: + +| Operation | myclabs/php-enum | native enum | +|----------------------------------------------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Obtain an instance will change from | `$enumCase = Action::VIEW()` | `$enumCase = Action::VIEW` | +| Create an enum from a backed value | `$enumCase = new Action('view')` | `$enumCase = Action::from('view')` | +| Get the backed value of the enum instance | `$enumCase->getValue()` | `$enumCase->value` | +| Compare two enum instances | `$enumCase1 == $enumCase2`
or
`$enumCase1->equals($enumCase2)` | `$enumCase1 === $enumCase2` | +| Get the key/name of the enum instance | `$enumCase->getKey()` | `$enumCase->name` | +| Get a list of all the possible instances of the enum | `Action::values()` | `Action::cases()` | +| Get a map of possible instances of the enum mapped by name | `Action::values()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), Action::cases())`
or
`(new ReflectionEnum(Action::class))->getConstants()` | +| Get a list of all possible names of the enum | `Action::keys()` | `array_map(fn($case) => $case->name, Action::cases())` | +| Get a list of all possible backed values of the enum | `Action::toArray()` | `array_map(fn($case) => $case->value, Action::cases())` | +| Get a map of possible backed values of the enum mapped by name | `Action::toArray()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), array_map(fn($case) => $case->value, Action::cases()))`
or
`array_map(fn($case) => $case->value, (new ReflectionEnum(Action::class))->getConstants()))` | + +## Related projects + +- [PHP 8.1+ native enum](https://www.php.net/enumerations) +- [Doctrine enum mapping](https://github.com/acelaya/doctrine-enum-type) +- [Symfony ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) +- [PHPStan integration](https://github.com/timeweb/phpstan-enum) + + +[GA Image]: https://github.com/myclabs/php-enum/workflows/CI/badge.svg + +[GA Link]: https://github.com/myclabs/php-enum/actions?query=workflow%3A%22CI%22+branch%3Amaster + +[Shepherd Image]: https://shepherd.dev/github/myclabs/php-enum/coverage.svg + +[Shepherd Link]: https://shepherd.dev/github/myclabs/php-enum diff --git a/vendor/myclabs/php-enum/SECURITY.md b/vendor/myclabs/php-enum/SECURITY.md new file mode 100644 index 0000000..84fd4e3 --- /dev/null +++ b/vendor/myclabs/php-enum/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +Only the latest stable release is supported. + +## Reporting a Vulnerability + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). + +Tidelift will coordinate the fix and disclosure. diff --git a/vendor/myclabs/php-enum/composer.json b/vendor/myclabs/php-enum/composer.json new file mode 100644 index 0000000..978cb19 --- /dev/null +++ b/vendor/myclabs/php-enum/composer.json @@ -0,0 +1,36 @@ +{ + "name": "myclabs/php-enum", + "type": "library", + "description": "PHP Enum implementation", + "keywords": ["enum"], + "homepage": "http://github.com/myclabs/php-enum", + "license": "MIT", + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "autoload-dev": { + "psr-4": { + "MyCLabs\\Tests\\Enum\\": "tests/" + } + }, + "require": { + "php": "^7.3 || ^8.0", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + } +} diff --git a/vendor/myclabs/php-enum/src/Enum.php b/vendor/myclabs/php-enum/src/Enum.php new file mode 100644 index 0000000..4c94cf6 --- /dev/null +++ b/vendor/myclabs/php-enum/src/Enum.php @@ -0,0 +1,318 @@ + + * @author Daniel Costa + * @author Mirosław Filip + * + * @psalm-template T + * @psalm-immutable + * @psalm-consistent-constructor + */ +abstract class Enum implements \JsonSerializable, \Stringable +{ + /** + * Enum value + * + * @var mixed + * @psalm-var T + */ + protected $value; + + /** + * Enum key, the constant name + * + * @var string + */ + private $key; + + /** + * Store existing constants in a static cache per object. + * + * + * @var array + * @psalm-var array> + */ + protected static $cache = []; + + /** + * Cache of instances of the Enum class + * + * @var array + * @psalm-var array> + */ + protected static $instances = []; + + /** + * Creates a new value of some type + * + * @psalm-pure + * @param mixed $value + * + * @psalm-param T $value + * @throws \UnexpectedValueException if incompatible type is given. + */ + public function __construct($value) + { + if ($value instanceof static) { + /** @psalm-var T */ + $value = $value->getValue(); + } + + /** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */ + $this->key = static::assertValidValueReturningKey($value); + + /** @psalm-var T */ + $this->value = $value; + } + + /** + * This method exists only for the compatibility reason when deserializing a previously serialized version + * that didn't had the key property + */ + public function __wakeup() + { + /** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */ + if ($this->key === null) { + /** + * @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm + * @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed + */ + $this->key = static::search($this->value); + } + } + + /** + * @param mixed $value + * @return static + */ + public static function from($value): self + { + $key = static::assertValidValueReturningKey($value); + + return self::__callStatic($key, []); + } + + /** + * @psalm-pure + * @return mixed + * @psalm-return T + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the enum key (i.e. the constant name). + * + * @psalm-pure + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @psalm-pure + * @psalm-suppress InvalidCast + * @return string + */ + public function __toString() + { + return (string)$this->value; + } + + /** + * Determines if Enum should be considered equal with the variable passed as a parameter. + * Returns false if an argument is an object of different class or not an object. + * + * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 + * + * @psalm-pure + * @psalm-param mixed $variable + * @return bool + */ + final public function equals($variable = null): bool + { + return $variable instanceof self + && $this->getValue() === $variable->getValue() + && static::class === \get_class($variable); + } + + /** + * Returns the names (keys) of all constants in the Enum class + * + * @psalm-pure + * @psalm-return list + * @return array + */ + public static function keys() + { + return \array_keys(static::toArray()); + } + + /** + * Returns instances of the Enum class of all Enum constants + * + * @psalm-pure + * @psalm-return array + * @return static[] Constant name in key, Enum instance in value + */ + public static function values() + { + $values = array(); + + /** @psalm-var T $value */ + foreach (static::toArray() as $key => $value) { + $values[$key] = new static($value); + } + + return $values; + } + + /** + * Returns all possible values as an array + * + * @psalm-pure + * @psalm-suppress ImpureStaticProperty + * + * @psalm-return array + * @return array Constant name in key, constant value in value + */ + public static function toArray() + { + $class = static::class; + + if (!isset(static::$cache[$class])) { + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + $reflection = new \ReflectionClass($class); + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + static::$cache[$class] = $reflection->getConstants(); + } + + return static::$cache[$class]; + } + + /** + * Check if is valid enum value + * + * @param $value + * @psalm-param mixed $value + * @psalm-pure + * @psalm-assert-if-true T $value + * @return bool + */ + public static function isValid($value) + { + return \in_array($value, static::toArray(), true); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + */ + public static function assertValidValue($value): void + { + self::assertValidValueReturningKey($value); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + * @return string + */ + private static function assertValidValueReturningKey($value): string + { + if (false === ($key = static::search($value))) { + throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); + } + + return $key; + } + + /** + * Check if is valid enum key + * + * @param $key + * @psalm-param string $key + * @psalm-pure + * @return bool + */ + public static function isValidKey($key) + { + $array = static::toArray(); + + return isset($array[$key]) || \array_key_exists($key, $array); + } + + /** + * Return key for value + * + * @param mixed $value + * + * @psalm-param mixed $value + * @psalm-pure + * @return string|false + */ + public static function search($value) + { + return \array_search($value, static::toArray(), true); + } + + /** + * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant + * + * @param string $name + * @param array $arguments + * + * @return static + * @throws \BadMethodCallException + * + * @psalm-pure + */ + public static function __callStatic($name, $arguments) + { + $class = static::class; + if (!isset(self::$instances[$class][$name])) { + $array = static::toArray(); + if (!isset($array[$name]) && !\array_key_exists($name, $array)) { + $message = "No static method or enum constant '$name' in class " . static::class; + throw new \BadMethodCallException($message); + } + return self::$instances[$class][$name] = new static($array[$name]); + } + return clone self::$instances[$class][$name]; + } + + /** + * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() + * natively. + * + * @return mixed + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @psalm-pure + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getValue(); + } +} diff --git a/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php new file mode 100644 index 0000000..302bf80 --- /dev/null +++ b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php @@ -0,0 +1,54 @@ +register(new \MyCLabs\Enum\PHPUnit\Comparator()); + */ +final class Comparator extends \SebastianBergmann\Comparator\Comparator +{ + public function accepts($expected, $actual) + { + return $expected instanceof Enum && ( + $actual instanceof Enum || $actual === null + ); + } + + /** + * @param Enum $expected + * @param Enum|null $actual + * + * @return void + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) + { + if ($expected->equals($actual)) { + return; + } + + throw new ComparisonFailure( + $expected, + $actual, + $this->formatEnum($expected), + $this->formatEnum($actual), + false, + 'Failed asserting that two Enums are equal.' + ); + } + + private function formatEnum(Enum $enum = null) + { + if ($enum === null) { + return "null"; + } + + return get_class($enum)."::{$enum->getKey()}()"; + } +} diff --git a/vendor/myclabs/php-enum/stubs/Stringable.php b/vendor/myclabs/php-enum/stubs/Stringable.php new file mode 100644 index 0000000..4811af7 --- /dev/null +++ b/vendor/myclabs/php-enum/stubs/Stringable.php @@ -0,0 +1,11 @@ +=7.4.0 <8.5.0", "ext-ctype": "*", "ext-dom": "*", "ext-fileinfo": "*", diff --git a/vendor/psr/http-factory/LICENSE b/vendor/psr/http-factory/LICENSE old mode 100755 new mode 100644 diff --git a/vendor/psr/http-factory/README.md b/vendor/psr/http-factory/README.md old mode 100755 new mode 100644 diff --git a/vendor/psr/http-factory/composer.json b/vendor/psr/http-factory/composer.json old mode 100755 new mode 100644 index 82a1d32..d1bbdde --- a/vendor/psr/http-factory/composer.json +++ b/vendor/psr/http-factory/composer.json @@ -1,6 +1,6 @@ { "name": "psr/http-factory", - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "description": "Common interfaces for PSR-7 HTTP message factories", "keywords": [ "psr", "psr-7", @@ -18,11 +18,8 @@ "homepage": "https://www.php-fig.org/" } ], - "support": { - "source": "https://github.com/php-fig/http-factory" - }, "require": { - "php": ">=7.1", + "php": ">=7.0.0", "psr/http-message": "^1.0 || ^2.0" }, "autoload": { diff --git a/vendor/psr/http-factory/src/RequestFactoryInterface.php b/vendor/psr/http-factory/src/RequestFactoryInterface.php old mode 100755 new mode 100644 diff --git a/vendor/psr/http-factory/src/ResponseFactoryInterface.php b/vendor/psr/http-factory/src/ResponseFactoryInterface.php old mode 100755 new mode 100644 diff --git a/vendor/psr/http-factory/src/ServerRequestFactoryInterface.php b/vendor/psr/http-factory/src/ServerRequestFactoryInterface.php old mode 100755 new mode 100644 diff --git a/vendor/psr/http-factory/src/StreamFactoryInterface.php b/vendor/psr/http-factory/src/StreamFactoryInterface.php old mode 100755 new mode 100644 diff --git a/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php b/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php old mode 100755 new mode 100644 index d7adbf0..7db4e30 --- a/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php +++ b/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php @@ -15,10 +15,10 @@ interface UploadedFileFactoryInterface * * @param StreamInterface $stream Underlying stream representing the * uploaded file content. - * @param int|null $size in bytes + * @param int $size in bytes * @param int $error PHP file upload error - * @param string|null $clientFilename Filename as provided by the client, if any. - * @param string|null $clientMediaType Media type as provided by the client, if any. + * @param string $clientFilename Filename as provided by the client, if any. + * @param string $clientMediaType Media type as provided by the client, if any. * * @return UploadedFileInterface * @@ -26,9 +26,9 @@ interface UploadedFileFactoryInterface */ public function createUploadedFile( StreamInterface $stream, - ?int $size = null, + int $size = null, int $error = \UPLOAD_ERR_OK, - ?string $clientFilename = null, - ?string $clientMediaType = null + string $clientFilename = null, + string $clientMediaType = null ): UploadedFileInterface; } diff --git a/vendor/psr/http-factory/src/UriFactoryInterface.php b/vendor/psr/http-factory/src/UriFactoryInterface.php old mode 100755 new mode 100644 diff --git a/vendor/psr/http-message/composer.json b/vendor/psr/http-message/composer.json index c66e5ab..56e8c0a 100644 --- a/vendor/psr/http-message/composer.json +++ b/vendor/psr/http-message/composer.json @@ -7,7 +7,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "homepage": "http://www.php-fig.org/" } ], "require": { @@ -20,7 +20,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.1.x-dev" } } } diff --git a/vendor/psr/http-message/src/MessageInterface.php b/vendor/psr/http-message/src/MessageInterface.php index a83c985..8cdb4ed 100644 --- a/vendor/psr/http-message/src/MessageInterface.php +++ b/vendor/psr/http-message/src/MessageInterface.php @@ -1,5 +1,7 @@ =8.1" + "php": ">=8.0.2" }, "autoload": { "files": [ @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/vendor/symfony/event-dispatcher-contracts/.gitignore b/vendor/symfony/event-dispatcher-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/event-dispatcher-contracts/Event.php b/vendor/symfony/event-dispatcher-contracts/Event.php index 2e7f998..384a650 100644 --- a/vendor/symfony/event-dispatcher-contracts/Event.php +++ b/vendor/symfony/event-dispatcher-contracts/Event.php @@ -32,6 +32,9 @@ class Event implements StoppableEventInterface { private bool $propagationStopped = false; + /** + * {@inheritdoc} + */ public function isPropagationStopped(): bool { return $this->propagationStopped; diff --git a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php index 2d7840d..351dc51 100644 --- a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php +++ b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -21,13 +21,11 @@ interface EventDispatcherInterface extends PsrEventDispatcherInterface /** * Dispatches an event to all registered listeners. * - * @template T of object - * - * @param T $event The event to pass to the event handlers/listeners + * @param object $event The event to pass to the event handlers/listeners * @param string|null $eventName The name of the event to dispatch. If not supplied, * the class of $event should be used instead. * - * @return T The passed $event MUST be returned + * @return object The passed $event MUST be returned */ - public function dispatch(object $event, ?string $eventName = null): object; + public function dispatch(object $event, string $eventName = null): object; } diff --git a/vendor/symfony/event-dispatcher-contracts/LICENSE b/vendor/symfony/event-dispatcher-contracts/LICENSE index 7536cae..74cdc2d 100644 --- a/vendor/symfony/event-dispatcher-contracts/LICENSE +++ b/vendor/symfony/event-dispatcher-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-present Fabien Potencier +Copyright (c) 2018-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/event-dispatcher-contracts/README.md b/vendor/symfony/event-dispatcher-contracts/README.md index 332b961..b1ab4c0 100644 --- a/vendor/symfony/event-dispatcher-contracts/README.md +++ b/vendor/symfony/event-dispatcher-contracts/README.md @@ -3,7 +3,7 @@ Symfony EventDispatcher Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful and +Can be used to build on semantics that the Symfony components proved useful - and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/event-dispatcher-contracts/composer.json b/vendor/symfony/event-dispatcher-contracts/composer.json index d156b44..b4c3933 100644 --- a/vendor/symfony/event-dispatcher-contracts/composer.json +++ b/vendor/symfony/event-dispatcher-contracts/composer.json @@ -16,16 +16,19 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.0.2", "psr/event-dispatcher": "^1" }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, "autoload": { "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/vendor/symfony/finder/CHANGELOG.md b/vendor/symfony/finder/CHANGELOG.md index e838302..9e2fc5a 100644 --- a/vendor/symfony/finder/CHANGELOG.md +++ b/vendor/symfony/finder/CHANGELOG.md @@ -1,17 +1,6 @@ CHANGELOG ========= -6.4 ---- - - * Add early directory pruning to `Finder::filter()` - -6.2 ---- - - * Add `Finder::sortByExtension()` and `Finder::sortBySize()` - * Add `Finder::sortByCaseInsensitiveName()` to sort by name with case insensitive sorting methods - 6.0 --- diff --git a/vendor/symfony/finder/Comparator/Comparator.php b/vendor/symfony/finder/Comparator/Comparator.php index 41c02ac..f1ba97d 100644 --- a/vendor/symfony/finder/Comparator/Comparator.php +++ b/vendor/symfony/finder/Comparator/Comparator.php @@ -16,16 +16,16 @@ namespace Symfony\Component\Finder\Comparator; */ class Comparator { + private string $target; private string $operator; - public function __construct( - private string $target, - string $operator = '==', - ) { + public function __construct(string $target, string $operator = '==') + { if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { - throw new \InvalidArgumentException(\sprintf('Invalid operator "%s".', $operator)); + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } + $this->target = $target; $this->operator = $operator; } @@ -50,13 +50,19 @@ class Comparator */ public function test(mixed $test): bool { - return match ($this->operator) { - '>' => $test > $this->target, - '>=' => $test >= $this->target, - '<' => $test < $this->target, - '<=' => $test <= $this->target, - '!=' => $test != $this->target, - default => $test == $this->target, - }; + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; } } diff --git a/vendor/symfony/finder/Comparator/DateComparator.php b/vendor/symfony/finder/Comparator/DateComparator.php index bcf93cf..8f651e1 100644 --- a/vendor/symfony/finder/Comparator/DateComparator.php +++ b/vendor/symfony/finder/Comparator/DateComparator.php @@ -26,17 +26,17 @@ class DateComparator extends Comparator public function __construct(string $test) { if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { - throw new \InvalidArgumentException(\sprintf('Don\'t understand "%s" as a date test.', $test)); + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); } try { - $date = new \DateTimeImmutable($matches[2]); + $date = new \DateTime($matches[2]); $target = $date->format('U'); - } catch (\Exception) { - throw new \InvalidArgumentException(\sprintf('"%s" is not a valid date.', $matches[2])); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); } - $operator = $matches[1] ?: '=='; + $operator = $matches[1] ?? '=='; if ('since' === $operator || 'after' === $operator) { $operator = '>'; } diff --git a/vendor/symfony/finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Comparator/NumberComparator.php index 0ec0049..ff85d96 100644 --- a/vendor/symfony/finder/Comparator/NumberComparator.php +++ b/vendor/symfony/finder/Comparator/NumberComparator.php @@ -19,7 +19,7 @@ namespace Symfony\Component\Finder\Comparator; * magnitudes. * * The target value may use magnitudes of kilobytes (k, ki), - * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed * with an i use the appropriate 2**n version in accordance with the * IEC standard: http://physics.nist.gov/cuu/Units/binary.html * @@ -35,19 +35,19 @@ namespace Symfony\Component\Finder\Comparator; class NumberComparator extends Comparator { /** - * @param string|null $test A comparison string or null + * @param string|int $test A comparison string or an integer * * @throws \InvalidArgumentException If the test is not understood */ public function __construct(?string $test) { if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { - throw new \InvalidArgumentException(\sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); } $target = $matches[2]; if (!is_numeric($target)) { - throw new \InvalidArgumentException(\sprintf('Invalid number "%s".', $target)); + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); } if (isset($matches[3])) { // magnitude diff --git a/vendor/symfony/finder/Finder.php b/vendor/symfony/finder/Finder.php index 78673af..e5772c4 100644 --- a/vendor/symfony/finder/Finder.php +++ b/vendor/symfony/finder/Finder.php @@ -50,7 +50,6 @@ class Finder implements \IteratorAggregate, \Countable private array $notNames = []; private array $exclude = []; private array $filters = []; - private array $pruneFilters = []; private array $depths = []; private array $sizes = []; private bool $followLinks = false; @@ -124,7 +123,7 @@ class Finder implements \IteratorAggregate, \Countable public function depth(string|int|array $levels): static { foreach ((array) $levels as $level) { - $this->depths[] = new NumberComparator($level); + $this->depths[] = new Comparator\NumberComparator($level); } return $this; @@ -152,7 +151,7 @@ class Finder implements \IteratorAggregate, \Countable public function date(string|array $dates): static { foreach ((array) $dates as $date) { - $this->dates[] = new DateComparator($date); + $this->dates[] = new Comparator\DateComparator($date); } return $this; @@ -163,8 +162,8 @@ class Finder implements \IteratorAggregate, \Countable * * You can use patterns (delimited with / sign), globs or simple strings. * - * $finder->name('/\.php$/') - * $finder->name('*.php') // same as above, without dot files + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above * $finder->name('test.php') * $finder->name(['test.py', 'test.php']) * @@ -307,7 +306,7 @@ class Finder implements \IteratorAggregate, \Countable public function size(string|int|array $sizes): static { foreach ((array) $sizes as $size) { - $this->sizes[] = new NumberComparator($size); + $this->sizes[] = new Comparator\NumberComparator($size); } return $this; @@ -398,7 +397,7 @@ class Finder implements \IteratorAggregate, \Countable * * @param string|string[] $pattern VCS patterns to ignore */ - public static function addVCSPattern(string|array $pattern): void + public static function addVCSPattern(string|array $pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; @@ -425,22 +424,6 @@ class Finder implements \IteratorAggregate, \Countable return $this; } - /** - * Sorts files and directories by extension. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sortByExtension(): static - { - $this->sort = SortableIterator::SORT_BY_EXTENSION; - - return $this; - } - /** * Sorts files and directories by name. * @@ -452,39 +435,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByName(bool $useNaturalSort = false): static { - $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL : SortableIterator::SORT_BY_NAME; - - return $this; - } - - /** - * Sorts files and directories by name case insensitive. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static - { - $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE; - - return $this; - } - - /** - * Sorts files and directories by size. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sortBySize(): static - { - $this->sort = SortableIterator::SORT_BY_SIZE; + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; return $this; } @@ -500,7 +451,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByType(): static { - $this->sort = SortableIterator::SORT_BY_TYPE; + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; return $this; } @@ -518,7 +469,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByAccessedTime(): static { - $this->sort = SortableIterator::SORT_BY_ACCESSED_TIME; + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } @@ -550,7 +501,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByChangedTime(): static { - $this->sort = SortableIterator::SORT_BY_CHANGED_TIME; + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; return $this; } @@ -568,7 +519,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByModifiedTime(): static { - $this->sort = SortableIterator::SORT_BY_MODIFIED_TIME; + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } @@ -579,21 +530,14 @@ class Finder implements \IteratorAggregate, \Countable * The anonymous function receives a \SplFileInfo and must return false * to remove files. * - * @param \Closure(SplFileInfo): bool $closure - * @param bool $prune Whether to skip traversing directories further - * * @return $this * * @see CustomFilterIterator */ - public function filter(\Closure $closure, bool $prune = false): static + public function filter(\Closure $closure): static { $this->filters[] = $closure; - if ($prune) { - $this->pruneFilters[] = $closure; - } - return $this; } @@ -641,9 +585,9 @@ class Finder implements \IteratorAggregate, \Countable $resolvedDirs[] = [$this->normalizeDir($dir)]; } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { sort($glob); - $resolvedDirs[] = array_map($this->normalizeDir(...), $glob); + $resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob); } else { - throw new DirectoryNotFoundException(\sprintf('The "%s" directory does not exist.', $dir)); + throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); } } @@ -671,7 +615,7 @@ class Finder implements \IteratorAggregate, \Countable $iterator = $this->searchInDirectory($this->dirs[0]); if ($this->sort || $this->reverseSorting) { - $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); } return $iterator; @@ -679,7 +623,9 @@ class Finder implements \IteratorAggregate, \Countable $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { - $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); + $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { + return $this->searchInDirectory($dir); + }))); } foreach ($this->iterators as $it) { @@ -687,7 +633,7 @@ class Finder implements \IteratorAggregate, \Countable } if ($this->sort || $this->reverseSorting) { - $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); } return $iterator; @@ -699,6 +645,8 @@ class Finder implements \IteratorAggregate, \Countable * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. * * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable */ public function append(iterable $iterator): static { @@ -706,13 +654,15 @@ class Finder implements \IteratorAggregate, \Countable $this->iterators[] = $iterator->getIterator(); } elseif ($iterator instanceof \Iterator) { $this->iterators[] = $iterator; - } else { + } elseif (is_iterable($iterator)) { $it = new \ArrayIterator(); foreach ($iterator as $file) { $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); $it[$file->getPathname()] = $file; } $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); } return $this; @@ -743,10 +693,6 @@ class Finder implements \IteratorAggregate, \Countable $exclude = $this->exclude; $notPaths = $this->notPaths; - if ($this->pruneFilters) { - $exclude = array_merge($exclude, $this->pruneFilters); - } - if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { $exclude = array_merge($exclude, self::$vcsPatterns); } @@ -786,13 +732,13 @@ class Finder implements \IteratorAggregate, \Countable $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); if ($exclude) { - $iterator = new ExcludeDirectoryFilterIterator($iterator, $exclude); + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); } $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { - $iterator = new DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); } if ($this->mode) { @@ -800,23 +746,23 @@ class Finder implements \IteratorAggregate, \Countable } if ($this->names || $this->notNames) { - $iterator = new FilenameFilterIterator($iterator, $this->names, $this->notNames); + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); } if ($this->contains || $this->notContains) { - $iterator = new FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } if ($this->sizes) { - $iterator = new SizeRangeFilterIterator($iterator, $this->sizes); + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); } if ($this->dates) { - $iterator = new DateRangeFilterIterator($iterator, $this->dates); + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); } if ($this->filters) { - $iterator = new CustomFilterIterator($iterator, $this->filters); + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } if ($this->paths || $notPaths) { diff --git a/vendor/symfony/finder/Gitignore.php b/vendor/symfony/finder/Gitignore.php index bf05c5b..d42cca1 100644 --- a/vendor/symfony/finder/Gitignore.php +++ b/vendor/symfony/finder/Gitignore.php @@ -43,7 +43,7 @@ class Gitignore foreach ($gitignoreLines as $line) { $line = preg_replace('~(? '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex); + $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string { + return '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']'; + }, $regex); $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(? * - * @extends \FilterIterator - * - * @implements \RecursiveIterator + * @extends \FilterIterator + * @implements \RecursiveIterator */ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator { - /** @var \Iterator */ private \Iterator $iterator; private bool $isRecursive; - /** @var array */ private array $excludedDirs = []; private ?string $excludedPattern = null; - /** @var list */ - private array $pruneFilters = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param list $directories An array of directories to exclude + * @param \Iterator $iterator The Iterator to filter + * @param string[] $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { @@ -43,16 +36,6 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi $this->isRecursive = $iterator instanceof \RecursiveIterator; $patterns = []; foreach ($directories as $directory) { - if (!\is_string($directory)) { - if (!\is_callable($directory)) { - throw new \InvalidArgumentException('Invalid PHP callback.'); - } - - $this->pruneFilters[] = $directory; - - continue; - } - $directory = rtrim($directory, '/'); if (!$this->isRecursive || str_contains($directory, '/')) { $patterns[] = preg_quote($directory, '#'); @@ -83,14 +66,6 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi return !preg_match($this->excludedPattern, $path); } - if ($this->pruneFilters && $this->hasChildren()) { - foreach ($this->pruneFilters as $pruneFilter) { - if (!$pruneFilter($this->current())) { - return false; - } - } - } - return true; } diff --git a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php index 0d4a5fd..2ed48fb 100644 --- a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php +++ b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -23,14 +23,16 @@ class FileTypeFilterIterator extends \FilterIterator public const ONLY_FILES = 1; public const ONLY_DIRECTORIES = 2; + private int $mode; + /** - * @param \Iterator $iterator The Iterator to filter - * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ - public function __construct( - \Iterator $iterator, - private int $mode, - ) { + public function __construct(\Iterator $iterator, int $mode) + { + $this->mode = $mode; + parent::__construct($iterator); } diff --git a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php index bdc71ff..eaa7a5d 100644 --- a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php +++ b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -11,15 +11,13 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\SplFileInfo; - /** * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). * * @author Fabien Potencier * @author Włodzimierz Gajda * - * @extends MultiplePcreFilterIterator + * @extends MultiplePcreFilterIterator */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { diff --git a/vendor/symfony/finder/Iterator/LazyIterator.php b/vendor/symfony/finder/Iterator/LazyIterator.php index 5b5806b..71c4be8 100644 --- a/vendor/symfony/finder/Iterator/LazyIterator.php +++ b/vendor/symfony/finder/Iterator/LazyIterator.php @@ -22,7 +22,7 @@ class LazyIterator implements \IteratorAggregate public function __construct(callable $iteratorFactory) { - $this->iteratorFactory = $iteratorFactory(...); + $this->iteratorFactory = $iteratorFactory instanceof \Closure ? $iteratorFactory : \Closure::fromCallable($iteratorFactory); } public function getIterator(): \Traversable diff --git a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php index 3450c49..1e9e7ff 100644 --- a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php +++ b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -23,13 +23,13 @@ namespace Symfony\Component\Finder\Iterator; */ abstract class MultiplePcreFilterIterator extends \FilterIterator { - protected array $matchRegexps = []; - protected array $noMatchRegexps = []; + protected $matchRegexps = []; + protected $noMatchRegexps = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param string[] $matchPatterns An array of patterns that need to match - * @param string[] $noMatchPatterns An array of patterns that need to not match + * @param \Iterator $iterator The Iterator to filter + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { @@ -80,7 +80,11 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator */ protected function isRegex(string $str): bool { - $availableModifiers = 'imsxuADUn'; + $availableModifiers = 'imsxuADU'; + + if (\PHP_VERSION_ID >= 80200) { + $availableModifiers .= 'n'; + } if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { $start = substr($m[1], 0, 1); diff --git a/vendor/symfony/finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Iterator/PathFilterIterator.php index c6d5813..bfe402a 100644 --- a/vendor/symfony/finder/Iterator/PathFilterIterator.php +++ b/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -11,15 +11,13 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\SplFileInfo; - /** * PathFilterIterator filters files by path patterns (e.g. some/special/dir). * * @author Fabien Potencier * @author Włodzimierz Gajda * - * @extends MultiplePcreFilterIterator + * @extends MultiplePcreFilterIterator */ class PathFilterIterator extends MultiplePcreFilterIterator { diff --git a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php index f5fd2d4..4c9779f 100644 --- a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php +++ b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -18,13 +18,11 @@ use Symfony\Component\Finder\SplFileInfo; * Extends the \RecursiveDirectoryIterator to support relative paths. * * @author Victor Berchet - * - * @extends \RecursiveDirectoryIterator */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { private bool $ignoreUnreadableDirs; - private bool $ignoreFirstRewind = true; + private ?bool $rewindable = null; // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations private string $rootPath; @@ -63,9 +61,8 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator $subPathname .= $this->directorySeparator; } $subPathname .= $this->getFilename(); - $basePath = $this->rootPath; - if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) { + if ('/' !== $basePath = $this->rootPath) { $basePath .= $this->directorySeparator; } @@ -84,7 +81,7 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator parent::getChildren(); return true; - } catch (\UnexpectedValueException) { + } catch (\UnexpectedValueException $e) { // If directory is unreadable and finder is set to ignore it, skip children return false; } @@ -103,6 +100,7 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; // performance optimization to avoid redoing the same work in all children + $children->rewindable = &$this->rewindable; $children->rootPath = $this->rootPath; } @@ -112,23 +110,36 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator } } - public function next(): void - { - $this->ignoreFirstRewind = false; - - parent::next(); - } - + /** + * Do nothing for non rewindable stream. + */ public function rewind(): void { - // some streams like FTP are not rewindable, ignore the first rewind after creation, - // as newly created DirectoryIterator does not need to be rewound - if ($this->ignoreFirstRewind) { - $this->ignoreFirstRewind = false; - + if (false === $this->isRewindable()) { return; } parent::rewind(); } + + /** + * Checks if the stream is rewindable. + */ + public function isRewindable(): bool + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } } diff --git a/vendor/symfony/finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Iterator/SortableIterator.php index 177cd0b..b6c34b6 100644 --- a/vendor/symfony/finder/Iterator/SortableIterator.php +++ b/vendor/symfony/finder/Iterator/SortableIterator.php @@ -27,12 +27,7 @@ class SortableIterator implements \IteratorAggregate public const SORT_BY_CHANGED_TIME = 4; public const SORT_BY_MODIFIED_TIME = 5; public const SORT_BY_NAME_NATURAL = 6; - public const SORT_BY_NAME_CASE_INSENSITIVE = 7; - public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8; - public const SORT_BY_EXTENSION = 9; - public const SORT_BY_SIZE = 10; - /** @var \Traversable */ private \Traversable $iterator; private \Closure|int $sort; @@ -48,13 +43,13 @@ class SortableIterator implements \IteratorAggregate $order = $reverseOrder ? -1 : 1; if (self::SORT_BY_NAME === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; } elseif (self::SORT_BY_NAME_NATURAL === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { if ($a->isDir() && $b->isFile()) { @@ -66,19 +61,21 @@ class SortableIterator implements \IteratorAggregate return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime()); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getATime() - $b->getATime()); + }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime()); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getCTime() - $b->getCTime()); + }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime()); - } elseif (self::SORT_BY_EXTENSION === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension()); - } elseif (self::SORT_BY_SIZE === $sort) { - $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize()); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getMTime() - $b->getMTime()); + }; } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { - $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...); + $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : \Closure::fromCallable($sort); } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } diff --git a/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php index b278706..e27158c 100644 --- a/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php +++ b/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -13,37 +13,27 @@ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Gitignore; -/** - * @extends \FilterIterator - */ final class VcsIgnoredFilterIterator extends \FilterIterator { - private string $baseDir; + /** + * @var string + */ + private $baseDir; /** * @var array */ - private array $gitignoreFilesCache = []; + private $gitignoreFilesCache = []; /** * @var array */ - private array $ignoredPathsCache = []; + private $ignoredPathsCache = []; - /** - * @param \Iterator $iterator - */ public function __construct(\Iterator $iterator, string $baseDir) { $this->baseDir = $this->normalizePath($baseDir); - foreach ([$this->baseDir, ...$this->parentDirectoriesUpwards($this->baseDir)] as $directory) { - if (@is_dir("{$directory}/.git")) { - $this->baseDir = $directory; - break; - } - } - parent::__construct($iterator); } @@ -68,7 +58,7 @@ final class VcsIgnoredFilterIterator extends \FilterIterator $ignored = false; - foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) { + foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) { if ($this->isIgnored($parentDirectory)) { // rules in ignored directories are ignored, no need to check further. break; @@ -99,11 +89,11 @@ final class VcsIgnoredFilterIterator extends \FilterIterator /** * @return list */ - private function parentDirectoriesUpwards(string $from): array + private function parentsDirectoryDownward(string $fileRealPath): array { $parentDirectories = []; - $parentDirectory = $from; + $parentDirectory = $fileRealPath; while (true) { $newParentDirectory = \dirname($parentDirectory); @@ -113,28 +103,16 @@ final class VcsIgnoredFilterIterator extends \FilterIterator break; } - $parentDirectories[] = $parentDirectory = $newParentDirectory; + $parentDirectory = $newParentDirectory; + + if (0 !== strpos($parentDirectory, $this->baseDir)) { + break; + } + + $parentDirectories[] = $parentDirectory; } - return $parentDirectories; - } - - private function parentDirectoriesUpTo(string $from, string $upTo): array - { - return array_filter( - $this->parentDirectoriesUpwards($from), - static fn (string $directory): bool => str_starts_with($directory, $upTo) - ); - } - - /** - * @return list - */ - private function parentDirectoriesDownwards(string $fileRealPath): array - { - return array_reverse( - $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir) - ); + return array_reverse($parentDirectories); } /** diff --git a/vendor/symfony/finder/LICENSE b/vendor/symfony/finder/LICENSE index 0138f8f..0083704 100644 --- a/vendor/symfony/finder/LICENSE +++ b/vendor/symfony/finder/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-present Fabien Potencier +Copyright (c) 2004-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/finder/SplFileInfo.php b/vendor/symfony/finder/SplFileInfo.php index 2afc378..867e8e8 100644 --- a/vendor/symfony/finder/SplFileInfo.php +++ b/vendor/symfony/finder/SplFileInfo.php @@ -18,17 +18,19 @@ namespace Symfony\Component\Finder; */ class SplFileInfo extends \SplFileInfo { + private string $relativePath; + private string $relativePathname; + /** * @param string $file The file name * @param string $relativePath The relative path * @param string $relativePathname The relative path name */ - public function __construct( - string $file, - private string $relativePath, - private string $relativePathname, - ) { + public function __construct(string $file, string $relativePath, string $relativePathname) + { parent::__construct($file); + $this->relativePath = $relativePath; + $this->relativePathname = $relativePathname; } /** diff --git a/vendor/symfony/finder/composer.json b/vendor/symfony/finder/composer.json index 2b70600..2e4b324 100644 --- a/vendor/symfony/finder/composer.json +++ b/vendor/symfony/finder/composer.json @@ -16,10 +16,7 @@ } ], "require": { - "php": ">=8.2" - }, - "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "php": ">=8.0.2" }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, diff --git a/vendor/symfony/service-contracts/.gitignore b/vendor/symfony/service-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/service-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/vendor/symfony/service-contracts/Attribute/SubscribedService.php index f850b84..10d1bc3 100644 --- a/vendor/symfony/service-contracts/Attribute/SubscribedService.php +++ b/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -11,15 +11,10 @@ namespace Symfony\Contracts\Service\Attribute; -use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait; -use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Symfony\Contracts\Service\ServiceSubscriberTrait; /** - * For use as the return value for {@see ServiceSubscriberInterface}. - * - * @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi')) - * - * Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type + * Use with {@see ServiceSubscriberTrait} to mark a method's return type * as a subscribed service. * * @author Kevin Bond @@ -27,21 +22,12 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface; #[\Attribute(\Attribute::TARGET_METHOD)] final class SubscribedService { - /** @var object[] */ - public array $attributes; - /** - * @param string|null $key The key to use for the service - * @param class-string|null $type The service class - * @param bool $nullable Whether the service is optional - * @param object|object[] $attributes One or more dependency injection attributes to use + * @param string|null $key The key to use for the service + * If null, use "ClassName::methodName" */ public function __construct( - public ?string $key = null, - public ?string $type = null, - public bool $nullable = false, - array|object $attributes = [], + public ?string $key = null ) { - $this->attributes = \is_array($attributes) ? $attributes : [$attributes]; } } diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE index 7536cae..74cdc2d 100644 --- a/vendor/symfony/service-contracts/LICENSE +++ b/vendor/symfony/service-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-present Fabien Potencier +Copyright (c) 2018-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md index 42841a5..41e054a 100644 --- a/vendor/symfony/service-contracts/README.md +++ b/vendor/symfony/service-contracts/README.md @@ -3,7 +3,7 @@ Symfony Service Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful and +Can be used to build on semantics that the Symfony components proved useful - and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php index a4f389b..1af1075 100644 --- a/vendor/symfony/service-contracts/ResetInterface.php +++ b/vendor/symfony/service-contracts/ResetInterface.php @@ -26,8 +26,5 @@ namespace Symfony\Contracts\Service; */ interface ResetInterface { - /** - * @return void - */ public function reset(); } diff --git a/vendor/symfony/service-contracts/ServiceCollectionInterface.php b/vendor/symfony/service-contracts/ServiceCollectionInterface.php deleted file mode 100644 index 2333139..0000000 --- a/vendor/symfony/service-contracts/ServiceCollectionInterface.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Contracts\Service; - -/** - * A ServiceProviderInterface that is also countable and iterable. - * - * @author Kevin Bond - * - * @template-covariant T of mixed - * - * @extends ServiceProviderInterface - * @extends \IteratorAggregate - */ -interface ServiceCollectionInterface extends ServiceProviderInterface, \Countable, \IteratorAggregate -{ -} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php index bbe4548..19d3e80 100644 --- a/vendor/symfony/service-contracts/ServiceLocatorTrait.php +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -26,22 +26,29 @@ class_exists(NotFoundExceptionInterface::class); */ trait ServiceLocatorTrait { + private array $factories; private array $loading = []; private array $providedTypes; /** - * @param array $factories + * @param callable[] $factories */ - public function __construct( - private array $factories, - ) { + public function __construct(array $factories) + { + $this->factories = $factories; } + /** + * {@inheritdoc} + */ public function has(string $id): bool { return isset($this->factories[$id]); } + /** + * {@inheritdoc} + */ public function get(string $id): mixed { if (!isset($this->factories[$id])) { @@ -64,6 +71,9 @@ trait ServiceLocatorTrait } } + /** + * {@inheritdoc} + */ public function getProvidedServices(): array { if (!isset($this->providedTypes)) { @@ -90,16 +100,16 @@ trait ServiceLocatorTrait } else { $last = array_pop($alternatives); if ($alternatives) { - $message = \sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); } else { - $message = \sprintf('only knows about the "%s" service.', $last); + $message = sprintf('only knows about the "%s" service.', $last); } } if ($this->loading) { - $message = \sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); } else { - $message = \sprintf('Service "%s" not found: the current service locator %s', $id, $message); + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); } return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { @@ -108,7 +118,7 @@ trait ServiceLocatorTrait private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface { - return new class(\sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { }; } } diff --git a/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php deleted file mode 100644 index 844be89..0000000 --- a/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Contracts\Service; - -use Psr\Container\ContainerInterface; -use Symfony\Contracts\Service\Attribute\Required; -use Symfony\Contracts\Service\Attribute\SubscribedService; - -/** - * Implementation of ServiceSubscriberInterface that determines subscribed services - * from methods that have the #[SubscribedService] attribute. - * - * Service ids are available as "ClassName::methodName" so that the implementation - * of subscriber methods can be just `return $this->container->get(__METHOD__);`. - * - * @author Kevin Bond - */ -trait ServiceMethodsSubscriberTrait -{ - protected ContainerInterface $container; - - public static function getSubscribedServices(): array - { - $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; - - foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { - if (self::class !== $method->getDeclaringClass()->name) { - continue; - } - - if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { - continue; - } - - if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { - throw new \LogicException(\sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); - } - - if (!$returnType = $method->getReturnType()) { - throw new \LogicException(\sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); - } - - /* @var SubscribedService $attribute */ - $attribute = $attribute->newInstance(); - $attribute->key ??= self::class.'::'.$method->name; - $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; - $attribute->nullable = $attribute->nullable ?: $returnType->allowsNull(); - - if ($attribute->attributes) { - $services[] = $attribute; - } else { - $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; - } - } - - return $services; - } - - #[Required] - public function setContainer(ContainerInterface $container): ?ContainerInterface - { - $ret = null; - if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { - $ret = parent::setContainer($container); - } - - $this->container = $container; - - return $ret; - } -} diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php index 2e71f00..c60ad0b 100644 --- a/vendor/symfony/service-contracts/ServiceProviderInterface.php +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -18,18 +18,9 @@ use Psr\Container\ContainerInterface; * * @author Nicolas Grekas * @author Mateusz Sip - * - * @template-covariant T of mixed */ interface ServiceProviderInterface extends ContainerInterface { - /** - * @return T - */ - public function get(string $id): mixed; - - public function has(string $id): bool; - /** * Returns an associative array of service types keyed by the identifiers provided by the current container. * @@ -39,7 +30,7 @@ interface ServiceProviderInterface extends ContainerInterface * * ['foo' => '?'] means the container provides service name "foo" of unspecified type * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null * - * @return array The provided service types, keyed by service names + * @return string[] The provided service types, keyed by service names */ public function getProvidedServices(): array; } diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php index 3da1916..881ab97 100644 --- a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -11,8 +11,6 @@ namespace Symfony\Contracts\Service; -use Symfony\Contracts\Service\Attribute\SubscribedService; - /** * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. * @@ -31,8 +29,7 @@ use Symfony\Contracts\Service\Attribute\SubscribedService; interface ServiceSubscriberInterface { /** - * Returns an array of service types (or {@see SubscribedService} objects) required - * by such instances, optionally keyed by the service names used internally. + * Returns an array of service types required by such instances, optionally keyed by the service names used internally. * * For mandatory dependencies: * @@ -50,13 +47,7 @@ interface ServiceSubscriberInterface * * ['?Psr\Log\LoggerInterface'] is a shortcut for * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] * - * additionally, an array of {@see SubscribedService}'s can be returned: - * - * * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)] - * * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)] - * * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))] - * - * @return string[]|SubscribedService[] The required service types, optionally keyed by service names + * @return string[] The required service types, optionally keyed by service names */ public static function getSubscribedServices(): array; } diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php index ed4cec0..ee9d9d9 100644 --- a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -12,26 +12,22 @@ namespace Symfony\Contracts\Service; use Psr\Container\ContainerInterface; -use Symfony\Contracts\Service\Attribute\Required; use Symfony\Contracts\Service\Attribute\SubscribedService; -trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class); - /** - * Implementation of ServiceSubscriberInterface that determines subscribed services - * from methods that have the #[SubscribedService] attribute. - * - * Service ids are available as "ClassName::methodName" so that the implementation - * of subscriber methods can be just `return $this->container->get(__METHOD__);`. - * - * @property ContainerInterface $container + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * method return types. Service ids are available as "ClassName::methodName". * * @author Kevin Bond - * - * @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead */ trait ServiceSubscriberTrait { + /** @var ContainerInterface */ + protected $container; + + /** + * {@inheritdoc} + */ public static function getSubscribedServices(): array { $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; @@ -46,39 +42,36 @@ trait ServiceSubscriberTrait } if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { - throw new \LogicException(\sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); } if (!$returnType = $method->getReturnType()) { - throw new \LogicException(\sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); } - /* @var SubscribedService $attribute */ - $attribute = $attribute->newInstance(); - $attribute->key ??= self::class.'::'.$method->name; - $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; - $attribute->nullable = $attribute->nullable ?: $returnType->allowsNull(); + $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; - if ($attribute->attributes) { - $services[] = $attribute; - } else { - $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; + if ($returnType->allowsNull()) { + $serviceId = '?'.$serviceId; } + + $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId; } return $services; } - #[Required] + /** + * @required + */ public function setContainer(ContainerInterface $container): ?ContainerInterface { - $ret = null; - if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { - $ret = parent::setContainer($container); - } - $this->container = $container; - return $ret; + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + return parent::setContainer($container); + } + + return null; } } diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php index 07d12b4..88f6a06 100644 --- a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -11,13 +11,82 @@ namespace Symfony\Contracts\Service\Test; -class_alias(ServiceLocatorTestCase::class, ServiceLocatorTest::class); +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; -if (false) { - /** - * @deprecated since PHPUnit 9.6 - */ - class ServiceLocatorTest +abstract class ServiceLocatorTest extends TestCase +{ + protected function getServiceLocator(array $factories): ContainerInterface { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + if (!$this->getExpectedException()) { + $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException(\Psr\Container\ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); } } diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php deleted file mode 100644 index fdd5b27..0000000 --- a/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Contracts\Service\Test; - -use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerExceptionInterface; -use Psr\Container\ContainerInterface; -use Psr\Container\NotFoundExceptionInterface; -use Symfony\Contracts\Service\ServiceLocatorTrait; - -abstract class ServiceLocatorTestCase extends TestCase -{ - /** - * @param array $factories - */ - protected function getServiceLocator(array $factories): ContainerInterface - { - return new class($factories) implements ContainerInterface { - use ServiceLocatorTrait; - }; - } - - public function testHas() - { - $locator = $this->getServiceLocator([ - 'foo' => fn () => 'bar', - 'bar' => fn () => 'baz', - fn () => 'dummy', - ]); - - $this->assertTrue($locator->has('foo')); - $this->assertTrue($locator->has('bar')); - $this->assertFalse($locator->has('dummy')); - } - - public function testGet() - { - $locator = $this->getServiceLocator([ - 'foo' => fn () => 'bar', - 'bar' => fn () => 'baz', - ]); - - $this->assertSame('bar', $locator->get('foo')); - $this->assertSame('baz', $locator->get('bar')); - } - - public function testGetDoesNotMemoize() - { - $i = 0; - $locator = $this->getServiceLocator([ - 'foo' => function () use (&$i) { - ++$i; - - return 'bar'; - }, - ]); - - $this->assertSame('bar', $locator->get('foo')); - $this->assertSame('bar', $locator->get('foo')); - $this->assertSame(2, $i); - } - - public function testThrowsOnUndefinedInternalService() - { - $locator = $this->getServiceLocator([ - 'foo' => function () use (&$locator) { return $locator->get('bar'); }, - ]); - - $this->expectException(NotFoundExceptionInterface::class); - $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); - - $locator->get('foo'); - } - - public function testThrowsOnCircularReference() - { - $locator = $this->getServiceLocator([ - 'foo' => function () use (&$locator) { return $locator->get('bar'); }, - 'bar' => function () use (&$locator) { return $locator->get('baz'); }, - 'baz' => function () use (&$locator) { return $locator->get('bar'); }, - ]); - - $this->expectException(ContainerExceptionInterface::class); - $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); - - $locator->get('foo'); - } -} diff --git a/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json index bc2e99a..d3b047f 100644 --- a/vendor/symfony/service-contracts/composer.json +++ b/vendor/symfony/service-contracts/composer.json @@ -16,23 +16,22 @@ } ], "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.0.2", + "psr/container": "^2.0" }, "conflict": { "ext-psr": "<1.1|>=2" }, + "suggest": { + "symfony/service-implementation": "" + }, "autoload": { - "psr-4": { "Symfony\\Contracts\\Service\\": "" }, - "exclude-from-classmap": [ - "/Test/" - ] + "psr-4": { "Symfony\\Contracts\\Service\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/vendor/symfony/var-exporter/CHANGELOG.md b/vendor/symfony/var-exporter/CHANGELOG.md index fdca002..3406c30 100644 --- a/vendor/symfony/var-exporter/CHANGELOG.md +++ b/vendor/symfony/var-exporter/CHANGELOG.md @@ -1,19 +1,6 @@ CHANGELOG ========= -6.4 ---- - - * Deprecate per-property lazy-initializers - -6.2 ---- - - * Add support for lazy ghost objects and virtual proxies - * Add `Hydrator::hydrate()` - * Preserve PHP references also when using `Hydrator::hydrate()` or `Instantiator::instantiate()` - * Add support for hydrating from native (array) casts - 5.1.0 ----- diff --git a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php index 2acecc4..4cebe44 100644 --- a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php +++ b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php @@ -13,8 +13,8 @@ namespace Symfony\Component\VarExporter\Exception; class ClassNotFoundException extends \Exception implements ExceptionInterface { - public function __construct(string $class, ?\Throwable $previous = null) + public function __construct(string $class, \Throwable $previous = null) { - parent::__construct(\sprintf('Class "%s" not found.', $class), 0, $previous); + parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous); } } diff --git a/vendor/symfony/var-exporter/Exception/LogicException.php b/vendor/symfony/var-exporter/Exception/LogicException.php deleted file mode 100644 index 619d055..0000000 --- a/vendor/symfony/var-exporter/Exception/LogicException.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter\Exception; - -class LogicException extends \LogicException implements ExceptionInterface -{ -} diff --git a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php index bc2bcaa..771ee61 100644 --- a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php +++ b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php @@ -13,8 +13,8 @@ namespace Symfony\Component\VarExporter\Exception; class NotInstantiableTypeException extends \Exception implements ExceptionInterface { - public function __construct(string $type, ?\Throwable $previous = null) + public function __construct(string $type, \Throwable $previous = null) { - parent::__construct(\sprintf('Type "%s" is not instantiable.', $type), 0, $previous); + parent::__construct(sprintf('Type "%s" is not instantiable.', $type), 0, $previous); } } diff --git a/vendor/symfony/var-exporter/Hydrator.php b/vendor/symfony/var-exporter/Hydrator.php deleted file mode 100644 index b718921..0000000 --- a/vendor/symfony/var-exporter/Hydrator.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter; - -use Symfony\Component\VarExporter\Internal\Hydrator as InternalHydrator; - -/** - * Utility class to hydrate the properties of an object. - * - * @author Nicolas Grekas - */ -final class Hydrator -{ - /** - * Sets the properties of an object, including private and protected ones. - * - * For example: - * - * // Sets the public or protected $object->propertyName property - * Hydrator::hydrate($object, ['propertyName' => $propertyValue]); - * - * // Sets a private property defined on its parent Bar class: - * Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); - * - * // Alternative way to set the private $object->privateBarProperty property - * Hydrator::hydrate($object, [], [ - * Bar::class => ['privateBarProperty' => $propertyValue], - * ]); - * - * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be hydrated - * by using the special "\0" property name to define their internal value: - * - * // Hydrates an SplObjectStorage where $info1 is attached to $obj1, etc. - * Hydrator::hydrate($object, ["\0" => [$obj1, $info1, $obj2, $info2...]]); - * - * // Hydrates an ArrayObject populated with $inputArray - * Hydrator::hydrate($object, ["\0" => [$inputArray]]); - * - * @template T of object - * - * @param T $instance The object to hydrate - * @param array $properties The properties to set on the instance - * @param array> $scopedProperties The properties to set on the instance, - * keyed by their declaring class - * - * @return T - */ - public static function hydrate(object $instance, array $properties = [], array $scopedProperties = []): object - { - if ($properties) { - $class = $instance::class; - $propertyScopes = InternalHydrator::$propertyScopes[$class] ??= InternalHydrator::getPropertyScopes($class); - - foreach ($properties as $name => &$value) { - [$scope, $name, $writeScope] = $propertyScopes[$name] ?? [$class, $name, $class]; - $scopedProperties[$writeScope ?? $scope][$name] = &$value; - } - unset($value); - } - - foreach ($scopedProperties as $scope => $properties) { - if ($properties) { - (InternalHydrator::$simpleHydrators[$scope] ??= InternalHydrator::getSimpleHydrator($scope))($properties, $instance); - } - } - - return $instance; - } -} diff --git a/vendor/symfony/var-exporter/Instantiator.php b/vendor/symfony/var-exporter/Instantiator.php index 10200c0..38fce27 100644 --- a/vendor/symfony/var-exporter/Instantiator.php +++ b/vendor/symfony/var-exporter/Instantiator.php @@ -13,6 +13,7 @@ namespace Symfony\Component\VarExporter; use Symfony\Component\VarExporter\Exception\ExceptionInterface; use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; +use Symfony\Component\VarExporter\Internal\Hydrator; use Symfony\Component\VarExporter\Internal\Registry; /** @@ -25,35 +26,67 @@ final class Instantiator /** * Creates an object and sets its properties without calling its constructor nor any other methods. * - * @see Hydrator::hydrate() for examples + * For example: * - * @template T of object + * // creates an empty instance of Foo + * Instantiator::instantiate(Foo::class); * - * @param class-string $class The class of the instance to create - * @param array $properties The properties to set on the instance - * @param array> $scopedProperties The properties to set on the instance, - * keyed by their declaring class + * // creates a Foo instance and sets one of its properties + * Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]); * - * @return T + * // creates a Foo instance and sets a private property defined on its parent Bar class + * Instantiator::instantiate(Foo::class, [], [ + * Bar::class => ['privateBarProperty' => $propertyValue], + * ]); + * + * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be created + * by using the special "\0" property name to define their internal value: + * + * // creates an SplObjectStorage where $info1 is attached to $obj1, etc. + * Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]); + * + * // creates an ArrayObject populated with $inputArray + * Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]); + * + * @param string $class The class of the instance to create + * @param array $properties The properties to set on the instance + * @param array $privateProperties The private properties to set on the instance, + * keyed by their declaring class * * @throws ExceptionInterface When the instance cannot be created */ - public static function instantiate(string $class, array $properties = [], array $scopedProperties = []): object + public static function instantiate(string $class, array $properties = [], array $privateProperties = []): object { - $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); + $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); if (Registry::$cloneable[$class]) { - $instance = clone Registry::$prototypes[$class]; + $wrappedInstance = [clone Registry::$prototypes[$class]]; } elseif (Registry::$instantiableWithoutConstructor[$class]) { - $instance = $reflector->newInstanceWithoutConstructor(); + $wrappedInstance = [$reflector->newInstanceWithoutConstructor()]; } elseif (null === Registry::$prototypes[$class]) { throw new NotInstantiableTypeException($class); } elseif ($reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize')) { - $instance = unserialize('C:'.\strlen($class).':"'.$class.'":0:{}'); + $wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')]; } else { - $instance = unserialize('O:'.\strlen($class).':"'.$class.'":0:{}'); + $wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')]; } - return $properties || $scopedProperties ? Hydrator::hydrate($instance, $properties, $scopedProperties) : $instance; + if ($properties) { + $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties; + } + + foreach ($privateProperties as $class => $properties) { + if (!$properties) { + continue; + } + foreach ($properties as $name => $value) { + // because they're also used for "unserialization", hydrators + // deal with array of instances, so we need to wrap values + $properties[$name] = [$value]; + } + (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance); + } + + return $wrappedInstance[0]; } } diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php index 13141a4..6ee3ee7 100644 --- a/vendor/symfony/var-exporter/Internal/Exporter.php +++ b/vendor/symfony/var-exporter/Internal/Exporter.php @@ -31,11 +31,9 @@ class Exporter * @param int &$objectsCount * @param bool &$valuesAreStatic * - * @return array - * * @throws NotInstantiableTypeException When a value cannot be serialized */ - public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic) + public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic): array { $refs = $values; foreach ($values as $k => $value) { @@ -73,26 +71,26 @@ class Exporter goto handle_value; } - $class = $value::class; - $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); + $class = \get_class($value); + $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + + if ($reflector->hasMethod('__serialize')) { + if (!$reflector->getMethod('__serialize')->isPublic()) { + throw new \Error(sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); + } + + if (!\is_array($properties = $value->__serialize())) { + throw new \TypeError($class.'::__serialize() must return an array'); + } + + goto prepare_value; + } + $properties = []; $sleep = null; $proto = Registry::$prototypes[$class]; - if ($reflector->hasMethod('__serialize')) { - if (!$reflector->getMethod('__serialize')->isPublic()) { - throw new \Error(\sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); - } - - if (!\is_array($arrayValue = $value->__serialize())) { - throw new \TypeError($class.'::__serialize() must return an array'); - } - - if ($reflector->hasMethod('__unserialize')) { - $properties = $arrayValue; - goto prepare_value; - } - } elseif (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { + if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { // ArrayIterator and ArrayObject need special care because their "flags" // option changes the behavior of the (array) casting operator. [$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto); @@ -108,7 +106,10 @@ class Exporter } $properties = ['SplObjectStorage' => ["\0" => $properties]]; $arrayValue = (array) $value; - } elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod) { + } elseif ($value instanceof \Serializable + || $value instanceof \__PHP_Incomplete_Class + || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod + ) { ++$objectsCount; $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0]; $value = new Reference($id); @@ -132,40 +133,36 @@ class Exporter $i = 0; $n = (string) $name; if ('' === $n || "\0" !== $n[0]) { - $parent = $reflector; - do { - $p = $parent->hasProperty($n) ? $parent->getProperty($n) : null; - } while (!$p && $parent = $parent->getParentClass()); - - $c = $p && (!$p->isPublic() || (\PHP_VERSION_ID >= 80400 ? $p->isProtectedSet() || $p->isPrivateSet() : $p->isReadOnly())) ? $p->class : 'stdClass'; + $c = \PHP_VERSION_ID >= 80100 && $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; } elseif ('*' === $n[1]) { $n = substr($n, 3); $c = $reflector->getProperty($n)->class; + if ('Error' === $c) { + $c = 'TypeError'; + } elseif ('Exception' === $c) { + $c = 'ErrorException'; + } } else { $i = strpos($n, "\0", 2); $c = substr($n, 1, $i - 1); $n = substr($n, 1 + $i); } if (null !== $sleep) { - if (!isset($sleep[$name]) && (!isset($sleep[$n]) || ($i && $c !== $class))) { + if (!isset($sleep[$n]) || ($i && $c !== $class)) { unset($arrayValue[$name]); continue; } - unset($sleep[$name], $sleep[$n]); + $sleep[$n] = false; } - if ("\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { + if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { $properties[$c][$n] = $v; - } elseif (!\array_key_exists($name, $proto) || $proto[$name] !== $v) { - $properties[match ($c) { - 'Error' => 'TypeError', - 'Exception' => 'ErrorException', - default => $c, - }][$n] = $v; } } if ($sleep) { foreach ($sleep as $n => $v) { - trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); + if (false !== $v) { + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); + } } } if (method_exists($class, '__unserialize')) { @@ -192,7 +189,7 @@ class Exporter return $values; } - public static function export($value, $indent = '') + public static function export($value, string $indent = '') { switch (true) { case \is_int($value) || \is_float($value): return var_export($value, true); @@ -218,10 +215,10 @@ class Exporter $subIndent = $indent.' '; if (\is_string($value)) { - $code = \sprintf("'%s'", addcslashes($value, "'\\")); + $code = sprintf("'%s'", addcslashes($value, "'\\")); $code = preg_replace_callback("/((?:[\\0\\r\\n]|\u{202A}|\u{202B}|\u{202D}|\u{202E}|\u{2066}|\u{2067}|\u{2068}|\u{202C}|\u{2069})++)(.)/", function ($m) use ($subIndent) { - $m[1] = \sprintf('\'."%s".\'', str_replace( + $m[1] = sprintf('\'."%s".\'', str_replace( ["\0", "\r", "\n", "\u{202A}", "\u{202B}", "\u{202D}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{202C}", "\u{2069}", '\n\\'], ['\0', '\r', '\n', '\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}', '\u{2069}', '\n"'."\n".$subIndent.'."\\'], $m[1] @@ -231,7 +228,7 @@ class Exporter return substr($m[1], 0, -2); } - if (str_ends_with($m[1], 'n".\'')) { + if ('n".\'' === substr($m[1], -4)) { return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2); } @@ -279,7 +276,7 @@ class Exporter return self::exportHydrator($value, $indent, $subIndent); } - throw new \UnexpectedValueException(\sprintf('Cannot export value of type "%s".', get_debug_type($value))); + throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', get_debug_type($value))); } private static function exportRegistry(Registry $value, string $indent, string $subIndent): string @@ -369,7 +366,7 @@ class Exporter self::export($value->wakeups, $subIndent), ]; - return '\\'.$value::class."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; + return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; } /** @@ -379,7 +376,7 @@ class Exporter private static function getArrayObjectProperties($value, $proto): array { $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; - $reflector = Registry::$reflectors[$reflector] ??= Registry::getClassReflector($reflector); + $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector); $properties = [ $arrayValue = (array) $value, diff --git a/vendor/symfony/var-exporter/Internal/Hydrator.php b/vendor/symfony/var-exporter/Internal/Hydrator.php index ebbceed..5ed6bdc 100644 --- a/vendor/symfony/var-exporter/Internal/Hydrator.php +++ b/vendor/symfony/var-exporter/Internal/Hydrator.php @@ -20,12 +20,7 @@ use Symfony\Component\VarExporter\Exception\ClassNotFoundException; */ class Hydrator { - public const PROPERTY_HAS_HOOKS = 1; - public const PROPERTY_NOT_BY_REF = 2; - - public static array $hydrators = []; - public static array $simpleHydrators = []; - public static array $propertyScopes = []; + public static $hydrators = []; public $registry; public $values; @@ -45,7 +40,7 @@ class Hydrator public static function hydrate($objects, $values, $properties, $value, $wakeups) { foreach ($properties as $class => $vars) { - (self::$hydrators[$class] ??= self::getHydrator($class))($vars, $objects); + (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects); } foreach ($wakeups as $k => $v) { if (\is_array($v)) { @@ -60,33 +55,31 @@ class Hydrator public static function getHydrator($class) { - $baseHydrator = self::$hydrators['stdClass'] ??= static function ($properties, $objects) { - foreach ($properties as $name => $values) { - foreach ($values as $i => $v) { - $objects[$i]->$name = $v; - } - } - }; - switch ($class) { case 'stdClass': - return $baseHydrator; + return self::$hydrators[$class] = static function ($properties, $objects) { + foreach ($properties as $name => $values) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; case 'ErrorException': - return $baseHydrator->bindTo(null, new class extends \ErrorException { + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException { }); case 'TypeError': - return $baseHydrator->bindTo(null, new class extends \Error { + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error { }); case 'SplObjectStorage': - return static function ($properties, $objects) { + return self::$hydrators[$class] = static function ($properties, $objects) { foreach ($properties as $name => $values) { if ("\0" === $name) { foreach ($values as $i => $v) { for ($j = 0; $j < \count($v); ++$j) { - $objects[$i][$v[$j]] = $v[++$j]; + $objects[$i]->attach($v[$j], $v[++$j]); } } continue; @@ -106,9 +99,9 @@ class Hydrator switch ($class) { case 'ArrayIterator': case 'ArrayObject': - $constructor = $classReflector->getConstructor()->invokeArgs(...); + $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']); - return static function ($properties, $objects) use ($constructor) { + return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) { foreach ($properties as $name => $values) { if ("\0" !== $name) { foreach ($values as $i => $v) { @@ -123,25 +116,26 @@ class Hydrator } if (!$classReflector->isInternal()) { - return $baseHydrator->bindTo(null, $class); + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class); } if ($classReflector->name !== $class) { - return self::$hydrators[$classReflector->name] ??= self::getHydrator($classReflector->name); + return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name); } $propertySetters = []; foreach ($classReflector->getProperties() as $propertyReflector) { if (!$propertyReflector->isStatic()) { - $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); + $propertyReflector->setAccessible(true); + $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']); } } if (!$propertySetters) { - return $baseHydrator; + return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'); } - return static function ($properties, $objects) use ($propertySetters) { + return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) { foreach ($properties as $name => $values) { if ($setValue = $propertySetters[$name] ?? null) { foreach ($values as $i => $v) { @@ -155,175 +149,4 @@ class Hydrator } }; } - - public static function getSimpleHydrator($class) - { - $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) { - $notByRef = (array) $this; - - foreach ($properties as $name => &$value) { - if (!$noRef = $notByRef[$name] ?? false) { - $object->$name = $value; - $object->$name = &$value; - } elseif (true !== $noRef) { - $noRef($object, $value); - } else { - $object->$name = $value; - } - } - })->bindTo(new \stdClass()); - - switch ($class) { - case 'stdClass': - return $baseHydrator; - - case 'ErrorException': - return $baseHydrator->bindTo(new \stdClass(), new class extends \ErrorException { - }); - - case 'TypeError': - return $baseHydrator->bindTo(new \stdClass(), new class extends \Error { - }); - - case 'SplObjectStorage': - return static function ($properties, $object) { - foreach ($properties as $name => &$value) { - if ("\0" !== $name) { - $object->$name = $value; - $object->$name = &$value; - continue; - } - for ($i = 0; $i < \count($value); ++$i) { - $object[$value[$i]] = $value[++$i]; - } - } - }; - } - - if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { - throw new ClassNotFoundException($class); - } - $classReflector = new \ReflectionClass($class); - - switch ($class) { - case 'ArrayIterator': - case 'ArrayObject': - $constructor = $classReflector->getConstructor()->invokeArgs(...); - - return static function ($properties, $object) use ($constructor) { - foreach ($properties as $name => &$value) { - if ("\0" === $name) { - $constructor($object, $value); - } else { - $object->$name = $value; - $object->$name = &$value; - } - } - }; - } - - if (!$classReflector->isInternal()) { - $notByRef = new \stdClass(); - foreach ($classReflector->getProperties() as $propertyReflector) { - if ($propertyReflector->isStatic()) { - continue; - } - if (\PHP_VERSION_ID >= 80400 && !$propertyReflector->isAbstract() && $propertyReflector->getHooks()) { - $notByRef->{$propertyReflector->name} = $propertyReflector->setRawValue(...); - } elseif ($propertyReflector->isReadOnly()) { - $notByRef->{$propertyReflector->name} = true; - } - } - - return $baseHydrator->bindTo($notByRef, $class); - } - - if ($classReflector->name !== $class) { - return self::$simpleHydrators[$classReflector->name] ??= self::getSimpleHydrator($classReflector->name); - } - - $propertySetters = []; - foreach ($classReflector->getProperties() as $propertyReflector) { - if (!$propertyReflector->isStatic()) { - $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); - } - } - - if (!$propertySetters) { - return $baseHydrator; - } - - return static function ($properties, $object) use ($propertySetters) { - foreach ($properties as $name => &$value) { - if ($setValue = $propertySetters[$name] ?? null) { - $setValue($object, $value); - } else { - $object->$name = $value; - $object->$name = &$value; - } - } - }; - } - - /** - * @return array - */ - public static function getPropertyScopes($class) - { - $propertyScopes = []; - $r = new \ReflectionClass($class); - - foreach ($r->getProperties() as $property) { - $flags = $property->getModifiers(); - - if (\ReflectionProperty::IS_STATIC & $flags) { - continue; - } - $name = $property->name; - $access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0); - - if (\PHP_VERSION_ID >= 80400 && !$property->isAbstract() && $h = $property->getHooks()) { - $access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0); - } - - if (\ReflectionProperty::IS_PRIVATE & $flags) { - $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, null, $access, $property]; - - continue; - } - - $propertyScopes[$name] = [$class, $name, null, $access, $property]; - - if ($flags & (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY)) { - $propertyScopes[$name][2] = $property->class; - } - - if (\ReflectionProperty::IS_PROTECTED & $flags) { - $propertyScopes["\0*\0$name"] = $propertyScopes[$name]; - } - } - - while ($r = $r->getParentClass()) { - $class = $r->name; - - foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) { - $flags = $property->getModifiers(); - - if (\ReflectionProperty::IS_STATIC & $flags) { - continue; - } - $name = $property->name; - $access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0); - - if (\PHP_VERSION_ID >= 80400 && $h = $property->getHooks()) { - $access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0); - } - - $propertyScopes["\0$class\0$name"] = [$class, $name, null, $access, $property]; - $propertyScopes[$name] ??= $propertyScopes["\0$class\0$name"]; - } - } - - return $propertyScopes; - } } diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php deleted file mode 100644 index d096be8..0000000 --- a/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php +++ /dev/null @@ -1,177 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter\Internal; - -/** - * Stores the state of lazy objects and caches related reflection information. - * - * As a micro-optimization, this class uses no type declarations. - * - * @internal - */ -class LazyObjectRegistry -{ - /** - * @var array - */ - public static array $classReflectors = []; - - /** - * @var array> - */ - public static array $defaultProperties = []; - - /** - * @var array> - */ - public static array $classResetters = []; - - /** - * @var array - */ - public static array $classAccessors = []; - - /** - * @var array - */ - public static array $parentMethods = []; - - public static ?\Closure $noInitializerState = null; - - public static function getClassResetters($class) - { - $classProperties = []; - $hookedProperties = []; - - if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) { - $propertyScopes = []; - } else { - $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); - } - - foreach ($propertyScopes as $key => [$scope, $name, $writeScope, $access]) { - $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; - - if ($k !== $key || "\0$class\0lazyObjectState" === $k) { - continue; - } - - if ($access & Hydrator::PROPERTY_HAS_HOOKS) { - $hookedProperties[$k] = true; - } else { - $classProperties[$writeScope ?? $scope][$name] = $key; - } - } - - $resetters = []; - foreach ($classProperties as $scope => $properties) { - $resetters[] = \Closure::bind(static function ($instance, $skippedProperties, $onlyProperties = null) use ($properties) { - foreach ($properties as $name => $key) { - if (!\array_key_exists($key, $skippedProperties) && (null === $onlyProperties || \array_key_exists($key, $onlyProperties))) { - unset($instance->$name); - } - } - }, null, $scope); - } - - $resetters[] = static function ($instance, $skippedProperties, $onlyProperties = null) use ($hookedProperties) { - foreach ((array) $instance as $name => $value) { - if ("\0" !== ($name[0] ?? '') - && !\array_key_exists($name, $skippedProperties) - && (null === $onlyProperties || \array_key_exists($name, $onlyProperties)) - && !isset($hookedProperties[$name]) - ) { - unset($instance->$name); - } - } - }; - - return $resetters; - } - - public static function getClassAccessors($class) - { - return \Closure::bind(static fn () => [ - 'get' => static function &($instance, $name, $notByRef) { - if (!$notByRef) { - return $instance->$name; - } - $value = $instance->$name; - - return $value; - }, - 'set' => static function ($instance, $name, $value) { - $instance->$name = $value; - }, - 'isset' => static fn ($instance, $name) => isset($instance->$name), - 'unset' => static function ($instance, $name) { - unset($instance->$name); - }, - ], null, \Closure::class === $class ? null : $class)(); - } - - public static function getParentMethods($class) - { - $parent = get_parent_class($class); - $methods = []; - - foreach (['set', 'isset', 'unset', 'clone', 'serialize', 'unserialize', 'sleep', 'wakeup', 'destruct', 'get'] as $method) { - if (!$parent || !method_exists($parent, '__'.$method)) { - $methods[$method] = false; - } else { - $m = new \ReflectionMethod($parent, '__'.$method); - $methods[$method] = !$m->isAbstract() && !$m->isPrivate(); - } - } - - $methods['get'] = $methods['get'] ? ($m->returnsReference() ? 2 : 1) : 0; - - return $methods; - } - - public static function getScopeForRead($propertyScopes, $class, $property) - { - if (!isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) { - return null; - } - $frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]; - - if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) { - $scope = $frame['object']->class; - } - if ('*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { - return null; - } - - return $scope; - } - - public static function getScopeForWrite($propertyScopes, $class, $property, $flags) - { - if (!($flags & (\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_READONLY | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET | \ReflectionProperty::IS_PROTECTED_SET : 0)))) { - return null; - } - $frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]; - - if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) { - $scope = $frame['object']->class; - } - if ($flags & (\ReflectionProperty::IS_PRIVATE | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY))) { - return $scope; - } - if ($flags & (\ReflectionProperty::IS_PROTECTED | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PROTECTED_SET : 0)) && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { - return null; - } - - return $scope; - } -} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectState.php b/vendor/symfony/var-exporter/Internal/LazyObjectState.php deleted file mode 100644 index 619555e..0000000 --- a/vendor/symfony/var-exporter/Internal/LazyObjectState.php +++ /dev/null @@ -1,133 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter\Internal; - -use Symfony\Component\VarExporter\Hydrator as PublicHydrator; - -/** - * Keeps the state of lazy objects. - * - * As a micro-optimization, this class uses no type declarations. - * - * @internal - */ -class LazyObjectState -{ - public const STATUS_UNINITIALIZED_FULL = 1; - public const STATUS_UNINITIALIZED_PARTIAL = 2; - public const STATUS_INITIALIZED_FULL = 3; - public const STATUS_INITIALIZED_PARTIAL = 4; - - /** - * @var array - */ - public readonly array $skippedProperties; - - /** - * @var self::STATUS_* - */ - public int $status = 0; - - public object $realInstance; - - public function __construct(public readonly \Closure|array $initializer, $skippedProperties = []) - { - $this->skippedProperties = $skippedProperties; - $this->status = \is_array($initializer) ? self::STATUS_UNINITIALIZED_PARTIAL : self::STATUS_UNINITIALIZED_FULL; - } - - public function initialize($instance, $propertyName, $writeScope) - { - if (self::STATUS_INITIALIZED_FULL === $this->status) { - return self::STATUS_INITIALIZED_FULL; - } - - if (\is_array($this->initializer)) { - $class = $instance::class; - $writeScope ??= $class; - $propertyScopes = Hydrator::$propertyScopes[$class]; - $propertyScopes[$k = "\0$writeScope\0$propertyName"] ?? $propertyScopes[$k = "\0*\0$propertyName"] ?? $k = $propertyName; - - if ($initializer = $this->initializer[$k] ?? null) { - $value = $initializer(...[$instance, $propertyName, $writeScope, LazyObjectRegistry::$defaultProperties[$class][$k] ?? null]); - $accessor = LazyObjectRegistry::$classAccessors[$writeScope] ??= LazyObjectRegistry::getClassAccessors($writeScope); - $accessor['set']($instance, $propertyName, $value); - - return $this->status = self::STATUS_INITIALIZED_PARTIAL; - } - - if ($initializer = $this->initializer["\0"] ?? null) { - if (!\is_array($values = $initializer($instance, LazyObjectRegistry::$defaultProperties[$class]))) { - throw new \TypeError(\sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values))); - } - $properties = (array) $instance; - foreach ($values as $key => $value) { - if (!\array_key_exists($key, $properties) && [$scope, $name, $writeScope] = $propertyScopes[$key] ?? null) { - $scope = $writeScope ?? $scope; - $accessor = LazyObjectRegistry::$classAccessors[$scope] ??= LazyObjectRegistry::getClassAccessors($scope); - $accessor['set']($instance, $name, $value); - - if ($k === $key) { - $this->status = self::STATUS_INITIALIZED_PARTIAL; - } - } - } - } - - return $this->status; - } - - if (self::STATUS_INITIALIZED_PARTIAL === $this->status) { - return self::STATUS_INITIALIZED_PARTIAL; - } - - $this->status = self::STATUS_INITIALIZED_PARTIAL; - - try { - if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) { - PublicHydrator::hydrate($instance, $defaultProperties); - } - - ($this->initializer)($instance); - } catch (\Throwable $e) { - $this->status = self::STATUS_UNINITIALIZED_FULL; - $this->reset($instance); - - throw $e; - } - - return $this->status = self::STATUS_INITIALIZED_FULL; - } - - public function reset($instance): void - { - $class = $instance::class; - $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); - $skippedProperties = $this->skippedProperties; - $properties = (array) $instance; - $onlyProperties = \is_array($this->initializer) ? $this->initializer : null; - - foreach ($propertyScopes as $key => [$scope, $name, , $access]) { - $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; - - if ($k === $key && ($access & Hydrator::PROPERTY_HAS_HOOKS || ($access >> 2) & \ReflectionProperty::IS_READONLY || !\array_key_exists($k, $properties))) { - $skippedProperties[$k] = true; - } - } - - foreach (LazyObjectRegistry::$classResetters[$class] as $reset) { - $reset($instance, $skippedProperties, $onlyProperties); - } - - $this->status = self::STATUS_INITIALIZED_FULL === $this->status ? self::STATUS_UNINITIALIZED_FULL : self::STATUS_UNINITIALIZED_PARTIAL; - } -} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php deleted file mode 100644 index 4a6f232..0000000 --- a/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter\Internal; - -use Symfony\Component\Serializer\Attribute\Ignore; - -if (\PHP_VERSION_ID >= 80300) { - /** - * @internal - */ - trait LazyObjectTrait - { - #[Ignore] - private readonly LazyObjectState $lazyObjectState; - } -} else { - /** - * @internal - */ - trait LazyObjectTrait - { - #[Ignore] - private LazyObjectState $lazyObjectState; - } -} diff --git a/vendor/symfony/var-exporter/Internal/Reference.php b/vendor/symfony/var-exporter/Internal/Reference.php index 2c7bd7b..e371c07 100644 --- a/vendor/symfony/var-exporter/Internal/Reference.php +++ b/vendor/symfony/var-exporter/Internal/Reference.php @@ -18,11 +18,13 @@ namespace Symfony\Component\VarExporter\Internal; */ class Reference { - public int $count = 0; + public $id; + public $value; + public $count = 0; - public function __construct( - public readonly int $id, - public readonly mixed $value = null, - ) { + public function __construct(int $id, $value = null) + { + $this->id = $id; + $this->value = $value; } } diff --git a/vendor/symfony/var-exporter/Internal/Registry.php b/vendor/symfony/var-exporter/Internal/Registry.php index db05bbb..a9fb061 100644 --- a/vendor/symfony/var-exporter/Internal/Registry.php +++ b/vendor/symfony/var-exporter/Internal/Registry.php @@ -21,11 +21,11 @@ use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; */ class Registry { - public static array $reflectors = []; - public static array $prototypes = []; - public static array $factories = []; - public static array $cloneable = []; - public static array $instantiableWithoutConstructor = []; + public static $reflectors = []; + public static $prototypes = []; + public static $factories = []; + public static $cloneable = []; + public static $instantiableWithoutConstructor = []; public $classes = []; @@ -58,9 +58,9 @@ class Registry public static function f($class) { - $reflector = self::$reflectors[$class] ??= self::getClassReflector($class, true, false); + $reflector = self::$reflectors[$class] ?? self::getClassReflector($class, true, false); - return self::$factories[$class] = [$reflector, 'newInstanceWithoutConstructor'](...); + return self::$factories[$class] = \Closure::fromCallable([$reflector, 'newInstanceWithoutConstructor']); } public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null) @@ -75,17 +75,17 @@ class Registry } elseif (!$isClass || $reflector->isAbstract()) { throw new NotInstantiableTypeException($class); } elseif ($reflector->name !== $class) { - $reflector = self::$reflectors[$name = $reflector->name] ??= self::getClassReflector($name, false, $cloneable); + $reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, false, $cloneable); self::$cloneable[$class] = self::$cloneable[$name]; self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; self::$prototypes[$class] = self::$prototypes[$name]; - return $reflector; + return self::$reflectors[$class] = $reflector; } else { try { $proto = $reflector->newInstanceWithoutConstructor(); $instantiableWithoutConstructor = true; - } catch (\ReflectionException) { + } catch (\ReflectionException $e) { $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:'; if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { $proto = null; @@ -132,13 +132,15 @@ class Registry new \ReflectionProperty(\Error::class, 'trace'), new \ReflectionProperty(\Exception::class, 'trace'), ]; - $setTrace[0] = $setTrace[0]->setValue(...); - $setTrace[1] = $setTrace[1]->setValue(...); + $setTrace[0]->setAccessible(true); + $setTrace[1]->setAccessible(true); + $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']); + $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']); } $setTrace[$proto instanceof \Exception]($proto, []); } - return $reflector; + return self::$reflectors[$class] = $reflector; } } diff --git a/vendor/symfony/var-exporter/LICENSE b/vendor/symfony/var-exporter/LICENSE index 7536cae..99757d5 100644 --- a/vendor/symfony/var-exporter/LICENSE +++ b/vendor/symfony/var-exporter/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-present Fabien Potencier +Copyright (c) 2018-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/var-exporter/LazyGhostTrait.php b/vendor/symfony/var-exporter/LazyGhostTrait.php deleted file mode 100644 index 79d8a0f..0000000 --- a/vendor/symfony/var-exporter/LazyGhostTrait.php +++ /dev/null @@ -1,409 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter; - -use Symfony\Component\Serializer\Attribute\Ignore; -use Symfony\Component\VarExporter\Internal\Hydrator; -use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; -use Symfony\Component\VarExporter\Internal\LazyObjectState; -use Symfony\Component\VarExporter\Internal\LazyObjectTrait; - -trait LazyGhostTrait -{ - use LazyObjectTrait; - - /** - * Creates a lazy-loading ghost instance. - * - * Skipped properties should be indexed by their array-cast identifier, see - * https://php.net/manual/language.types.array#language.types.array.casting - * - * @param (\Closure(static):void $initializer The closure should initialize the object it receives as argument - * @param array|null $skippedProperties An array indexed by the properties to skip, a.k.a. the ones - * that the initializer doesn't initialize, if any - * @param static|null $instance - */ - public static function createLazyGhost(\Closure|array $initializer, ?array $skippedProperties = null, ?object $instance = null): static - { - if (\is_array($initializer)) { - trigger_deprecation('symfony/var-exporter', '6.4', 'Per-property lazy-initializers are deprecated and won\'t be supported anymore in 7.0, use an object initializer instead.'); - } - - $onlyProperties = null === $skippedProperties && \is_array($initializer) ? $initializer : null; - - if (self::class !== $class = $instance ? $instance::class : static::class) { - $skippedProperties["\0".self::class."\0lazyObjectState"] = true; - } elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { - Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; - } - - $instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor(); - Registry::$defaultProperties[$class] ??= (array) $instance; - $instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []); - - foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { - $reset($instance, $skippedProperties, $onlyProperties); - } - - return $instance; - } - - /** - * Returns whether the object is initialized. - * - * @param $partial Whether partially initialized objects should be considered as initialized - */ - #[Ignore] - public function isLazyObjectInitialized(bool $partial = false): bool - { - if (!$state = $this->lazyObjectState ?? null) { - return true; - } - - if (!\is_array($state->initializer)) { - return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status; - } - - $class = $this::class; - $properties = (array) $this; - - if ($partial) { - return (bool) array_intersect_key($state->initializer, $properties); - } - - $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); - foreach ($state->initializer as $key => $initializer) { - if (!\array_key_exists($key, $properties) && isset($propertyScopes[$key])) { - return false; - } - } - - return true; - } - - /** - * Forces initialization of a lazy object and returns it. - */ - public function initializeLazyObject(): static - { - if (!$state = $this->lazyObjectState ?? null) { - return $this; - } - - if (!\is_array($state->initializer)) { - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, '', null); - } - - return $this; - } - - $values = isset($state->initializer["\0"]) ? null : []; - - $class = $this::class; - $properties = (array) $this; - $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); - foreach ($state->initializer as $key => $initializer) { - if (\array_key_exists($key, $properties) || ![$scope, $name, $writeScope] = $propertyScopes[$key] ?? null) { - continue; - } - $scope = $writeScope ?? $scope; - - if (null === $values) { - if (!\is_array($values = ($state->initializer["\0"])($this, Registry::$defaultProperties[$class]))) { - throw new \TypeError(\sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values))); - } - - if (\array_key_exists($key, $properties = (array) $this)) { - continue; - } - } - - if (\array_key_exists($key, $values)) { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['set']($this, $name, $properties[$key] = $values[$key]); - } else { - $state->initialize($this, $name, $scope); - $properties = (array) $this; - } - } - - return $this; - } - - /** - * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object - */ - public function resetLazyObject(): bool - { - if (!$state = $this->lazyObjectState ?? null) { - return false; - } - - if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $state->status) { - $state->reset($this); - } - - return true; - } - - public function &__get($name): mixed - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $notByRef = 0; - - if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForRead($propertyScopes, $class, $name); - $state = $this->lazyObjectState ?? null; - - if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) { - $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF; - - if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) { - // Work around php/php-src#12695 - $property = null === $scope ? $name : "\0$scope\0$name"; - $property = $propertyScopes[$property][4] - ?? Hydrator::$propertyScopes[$this::class][$property][4] = new \ReflectionProperty($scope ?? $class, $name); - } else { - $property = null; - } - if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) { - $scope ??= $writeScope; - } - - if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope)) { - goto get_in_scope; - } - } - } - - if ($parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']) { - if (2 === $parent) { - return parent::__get($name); - } - $value = parent::__get($name); - - return $value; - } - - if (null === $class) { - $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; - trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $this::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); - } - - get_in_scope: - - try { - if (null === $scope) { - if (!$notByRef) { - return $this->$name; - } - $value = $this->$name; - - return $value; - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['get']($this, $name, $notByRef); - } catch (\Error $e) { - if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { - throw $e; - } - - try { - if (null === $scope) { - $this->$name = []; - - return $this->$name; - } - - $accessor['set']($this, $name, []); - - return $accessor['get']($this, $name, $notByRef); - } catch (\Error) { - if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) { - throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious()); - } - - throw $e; - } - } - } - - public function __set($name, $value): void - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - - if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); - $state = $this->lazyObjectState ?? null; - - if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status - ) { - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, $name, $writeScope ?? $scope); - } - goto set_in_scope; - } - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { - parent::__set($name, $value); - - return; - } - - set_in_scope: - - if (null === $scope) { - $this->$name = $value; - } else { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['set']($this, $name, $value); - } - } - - public function __isset($name): bool - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - - if ([$class, , $writeScope] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForRead($propertyScopes, $class, $name); - $state = $this->lazyObjectState ?? null; - - if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status - && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope) - ) { - goto isset_in_scope; - } - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { - return parent::__isset($name); - } - - isset_in_scope: - - if (null === $scope) { - return isset($this->$name); - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['isset']($this, $name); - } - - public function __unset($name): void - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - - if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); - $state = $this->lazyObjectState ?? null; - - if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) - && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status - ) { - if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { - $state->initialize($this, $name, $writeScope ?? $scope); - } - goto unset_in_scope; - } - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { - parent::__unset($name); - - return; - } - - unset_in_scope: - - if (null === $scope) { - unset($this->$name); - } else { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['unset']($this, $name); - } - } - - public function __clone(): void - { - if ($state = $this->lazyObjectState ?? null) { - $this->lazyObjectState = clone $state; - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { - parent::__clone(); - } - } - - public function __serialize(): array - { - $class = self::class; - - if ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { - $properties = parent::__serialize(); - } else { - $this->initializeLazyObject(); - $properties = (array) $this; - } - unset($properties["\0$class\0lazyObjectState"]); - - if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { - return $properties; - } - - $scope = get_parent_class($class); - $data = []; - - foreach (parent::__sleep() as $name) { - $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; - - if (null === $k) { - trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); - } else { - $data[$k] = $value; - } - } - - return $data; - } - - public function __destruct() - { - $state = $this->lazyObjectState ?? null; - - if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) { - return; - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { - parent::__destruct(); - } - } - - #[Ignore] - private function setLazyObjectAsInitialized(bool $initialized): void - { - $state = $this->lazyObjectState ?? null; - - if ($state && !\is_array($state->initializer)) { - $state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL; - } - } -} diff --git a/vendor/symfony/var-exporter/LazyObjectInterface.php b/vendor/symfony/var-exporter/LazyObjectInterface.php deleted file mode 100644 index 3670884..0000000 --- a/vendor/symfony/var-exporter/LazyObjectInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter; - -interface LazyObjectInterface -{ - /** - * Returns whether the object is initialized. - * - * @param $partial Whether partially initialized objects should be considered as initialized - */ - public function isLazyObjectInitialized(bool $partial = false): bool; - - /** - * Forces initialization of a lazy object and returns it. - */ - public function initializeLazyObject(): object; - - /** - * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object - */ - public function resetLazyObject(): bool; -} diff --git a/vendor/symfony/var-exporter/LazyProxyTrait.php b/vendor/symfony/var-exporter/LazyProxyTrait.php deleted file mode 100644 index af009ae..0000000 --- a/vendor/symfony/var-exporter/LazyProxyTrait.php +++ /dev/null @@ -1,355 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter; - -use Symfony\Component\Serializer\Attribute\Ignore; -use Symfony\Component\VarExporter\Hydrator as PublicHydrator; -use Symfony\Component\VarExporter\Internal\Hydrator; -use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; -use Symfony\Component\VarExporter\Internal\LazyObjectState; -use Symfony\Component\VarExporter\Internal\LazyObjectTrait; - -trait LazyProxyTrait -{ - use LazyObjectTrait; - - /** - * Creates a lazy-loading virtual proxy. - * - * @param \Closure():object $initializer Returns the proxied object - * @param static|null $instance - */ - public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static - { - if (self::class !== $class = $instance ? $instance::class : static::class) { - $skippedProperties = ["\0".self::class."\0lazyObjectState" => true]; - } elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { - Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; - } - - $instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor(); - $instance->lazyObjectState = new LazyObjectState($initializer); - - foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { - $reset($instance, $skippedProperties ??= []); - } - - return $instance; - } - - /** - * Returns whether the object is initialized. - * - * @param $partial Whether partially initialized objects should be considered as initialized - */ - #[Ignore] - public function isLazyObjectInitialized(bool $partial = false): bool - { - return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer; - } - - /** - * Forces initialization of a lazy object and returns it. - */ - public function initializeLazyObject(): parent - { - if ($state = $this->lazyObjectState ?? null) { - return $state->realInstance ??= ($state->initializer)(); - } - - return $this; - } - - /** - * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object - */ - public function resetLazyObject(): bool - { - if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) { - return false; - } - - unset($this->lazyObjectState->realInstance); - - return true; - } - - public function &__get($name): mixed - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $instance = $this; - $notByRef = 0; - - if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF; - $scope = Registry::getScopeForRead($propertyScopes, $class, $name); - - if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) { - $scope ??= $writeScope; - } - $parent = 2; - goto get_in_scope; - } - } - $parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']; - - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } else { - if (2 === $parent) { - return parent::__get($name); - } - $value = parent::__get($name); - - return $value; - } - - if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) { - $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; - trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); - } - - get_in_scope: - $notByRef = $notByRef || 1 === $parent; - - try { - if (null === $scope) { - if (!$notByRef) { - return $instance->$name; - } - $value = $instance->$name; - - return $value; - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['get']($instance, $name, $notByRef); - } catch (\Error $e) { - if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { - throw $e; - } - - try { - if (null === $scope) { - $instance->$name = []; - - return $instance->$name; - } - - $accessor['set']($instance, $name, []); - - return $accessor['get']($instance, $name, $notByRef); - } catch (\Error) { - throw $e; - } - } - } - - public function __set($name, $value): void - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $instance = $this; - - if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); - - if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - goto set_in_scope; - } - } - - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { - parent::__set($name, $value); - - return; - } - - set_in_scope: - - if (null === $scope) { - $instance->$name = $value; - } else { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['set']($instance, $name, $value); - } - } - - public function __isset($name): bool - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $instance = $this; - - if ([$class] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForRead($propertyScopes, $class, $name); - - if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - goto isset_in_scope; - } - } - - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { - return parent::__isset($name); - } - - isset_in_scope: - - if (null === $scope) { - return isset($instance->$name); - } - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - - return $accessor['isset']($instance, $name); - } - - public function __unset($name): void - { - $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); - $scope = null; - $instance = $this; - - if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { - $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); - - if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } - goto unset_in_scope; - } - } - - if ($state = $this->lazyObjectState ?? null) { - $instance = $state->realInstance ??= ($state->initializer)(); - } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { - parent::__unset($name); - - return; - } - - unset_in_scope: - - if (null === $scope) { - unset($instance->$name); - } else { - $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); - $accessor['unset']($instance, $name); - } - } - - public function __clone(): void - { - if (!isset($this->lazyObjectState)) { - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { - parent::__clone(); - } - - return; - } - - $this->lazyObjectState = clone $this->lazyObjectState; - - if (isset($this->lazyObjectState->realInstance)) { - $this->lazyObjectState->realInstance = clone $this->lazyObjectState->realInstance; - } - } - - public function __serialize(): array - { - $class = self::class; - $state = $this->lazyObjectState ?? null; - - if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { - $properties = parent::__serialize(); - } else { - $properties = (array) $this; - - if ($state) { - unset($properties["\0$class\0lazyObjectState"]); - $properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)(); - } - } - - if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { - return $properties; - } - - $scope = get_parent_class($class); - $data = []; - - foreach (parent::__sleep() as $name) { - $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; - - if (null === $k) { - trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); - } else { - $data[$k] = $value; - } - } - - return $data; - } - - public function __unserialize(array $data): void - { - $class = self::class; - - if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) { - unset($data["\0$class\0lazyObjectReal"]); - - foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { - $reset($this, $data); - } - - if ($data) { - PublicHydrator::hydrate($this, $data); - } - $this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.')); - $this->lazyObjectState->realInstance = $instance; - } elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) { - parent::__unserialize($data); - } else { - PublicHydrator::hydrate($this, $data); - - if (Registry::$parentMethods[$class]['wakeup']) { - parent::__wakeup(); - } - } - } - - public function __destruct() - { - if (isset($this->lazyObjectState)) { - return; - } - - if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { - parent::__destruct(); - } - } -} diff --git a/vendor/symfony/var-exporter/ProxyHelper.php b/vendor/symfony/var-exporter/ProxyHelper.php deleted file mode 100644 index 56fd00e..0000000 --- a/vendor/symfony/var-exporter/ProxyHelper.php +++ /dev/null @@ -1,553 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarExporter; - -use Symfony\Component\VarExporter\Exception\LogicException; -use Symfony\Component\VarExporter\Internal\Hydrator; -use Symfony\Component\VarExporter\Internal\LazyObjectRegistry; - -/** - * @author Nicolas Grekas - */ -final class ProxyHelper -{ - /** - * Helps generate lazy-loading ghost objects. - * - * @throws LogicException When the class is incompatible with ghost objects - */ - public static function generateLazyGhost(\ReflectionClass $class): string - { - if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class->isReadOnly()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost with PHP < 8.3: class "%s" is readonly.', $class->name)); - } - if ($class->isFinal()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name)); - } - if ($class->isInterface() || $class->isAbstract() || $class->isTrait()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name)); - } - if (\stdClass::class !== $class->name && $class->isInternal()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name)); - } - if ($class->hasMethod('__get') && 'mixed' !== (self::exportType($class->getMethod('__get')) ?? 'mixed')) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name)); - } - - static $traitMethods; - $traitMethods ??= (new \ReflectionClass(LazyGhostTrait::class))->getMethods(); - - foreach ($traitMethods as $method) { - if ($class->hasMethod($method->name) && $class->getMethod($method->name)->isFinal()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: method "%s::%s()" is final.', $class->name, $method->name)); - } - } - - $parent = $class; - while ($parent = $parent->getParentClass()) { - if (\stdClass::class !== $parent->name && $parent->isInternal()) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name)); - } - } - - $hooks = ''; - $propertyScopes = Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name); - foreach ($propertyScopes as $key => [$scope, $name, , $access]) { - $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; - $flags = $access >> 2; - - if ($k !== $key || !($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) { - continue; - } - - if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) { - throw new LogicException(\sprintf('Cannot generate lazy ghost: property "%s::$%s" is final or private(set).', $class->name, $name)); - } - - $p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); - - $type = self::exportType($p); - $hooks .= "\n " - .($p->isProtected() ? 'protected' : 'public') - .($p->isProtectedSet() ? ' protected(set)' : '') - ." {$type} \${$name}" - .($p->hasDefaultValue() ? ' = '.VarExporter::export($p->getDefaultValue()) : '') - ." {\n"; - - foreach ($p->getHooks() as $hook => $method) { - if ('get' === $hook) { - $ref = ($method->returnsReference() ? '&' : ''); - $hooks .= " {$ref}get { \$this->initializeLazyObject(); return parent::\${$name}::get(); }\n"; - } elseif ('set' === $hook) { - $parameters = self::exportParameters($method, true); - $arg = '$'.$method->getParameters()[0]->name; - $hooks .= " set({$parameters}) { \$this->initializeLazyObject(); parent::\${$name}::set({$arg}); }\n"; - } else { - throw new LogicException(\sprintf('Cannot generate lazy ghost: hook "%s::%s()" is not supported.', $class->name, $method->name)); - } - } - - $hooks .= " }\n"; - } - - $propertyScopes = self::exportPropertyScopes($class->name, $propertyScopes); - - return <<name} implements \Symfony\Component\VarExporter\LazyObjectInterface - { - use \Symfony\Component\VarExporter\LazyGhostTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes}; - {$hooks}} - - // Help opcache.preload discover always-needed symbols - class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); - class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); - class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); - - EOPHP; - } - - /** - * Helps generate lazy-loading virtual proxies. - * - * @param \ReflectionClass[] $interfaces - * - * @throws LogicException When the class is incompatible with virtual proxies - */ - public static function generateLazyProxy(?\ReflectionClass $class, array $interfaces = []): string - { - if (!class_exists($class?->name ?? \stdClass::class, false)) { - throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not a class.', $class->name)); - } - if ($class?->isFinal()) { - throw new LogicException(\sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name)); - } - if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class?->isReadOnly()) { - throw new LogicException(\sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name)); - } - - $propertyScopes = $class ? Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name) : []; - $abstractProperties = []; - $hookedProperties = []; - if (\PHP_VERSION_ID >= 80400 && $class) { - foreach ($propertyScopes as $key => [$scope, $name, , $access]) { - $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; - $flags = $access >> 2; - - if ($k !== $key) { - continue; - } - - if ($flags & \ReflectionProperty::IS_ABSTRACT) { - $abstractProperties[$name] = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); - continue; - } - $abstractProperties[$name] = false; - - if (!($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) { - continue; - } - - if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) { - throw new LogicException(\sprintf('Cannot generate lazy proxy: property "%s::$%s" is final or private(set).', $class->name, $name)); - } - - $p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); - $hookedProperties[$name] = [$p, $p->getHooks()]; - } - } - - $methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []]; - foreach ($interfaces as $interface) { - if (!$interface->isInterface()) { - throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name)); - } - $methodReflectors[] = $interface->getMethods(); - - if (\PHP_VERSION_ID >= 80400) { - foreach ($interface->getProperties() as $p) { - $abstractProperties[$p->name] ??= $p; - $hookedProperties[$p->name] ??= [$p, []]; - $hookedProperties[$p->name][1] += $p->getHooks(); - } - } - } - - $hooks = ''; - - foreach (array_filter($abstractProperties) as $name => $p) { - $type = self::exportType($p); - $hooks .= "\n " - .($p->isProtected() ? 'protected' : 'public') - .($p->isProtectedSet() ? ' protected(set)' : '') - ." {$type} \${$name};\n"; - } - - foreach ($hookedProperties as $name => [$p, $methods]) { - $type = self::exportType($p); - $hooks .= "\n " - .($p->isProtected() ? 'protected' : 'public') - .($p->isProtectedSet() ? ' protected(set)' : '') - ." {$type} \${$name} {\n"; - - foreach ($methods as $hook => $method) { - if ('get' === $hook) { - $ref = ($method->returnsReference() ? '&' : ''); - $hooks .= <<lazyObjectState)) { - return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$p->name}; - } - - return parent::\${$p->name}::get(); - } - - EOPHP; - } elseif ('set' === $hook) { - $parameters = self::exportParameters($method, true); - $arg = '$'.$method->getParameters()[0]->name; - $hooks .= <<lazyObjectState)) { - \$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)(); - \$this->lazyObjectState->realInstance->{$p->name} = {$arg}; - } - - parent::\${$p->name}::set({$arg}); - } - - EOPHP; - } else { - throw new LogicException(\sprintf('Cannot generate lazy proxy: hook "%s::%s()" is not supported.', $class->name, $method->name)); - } - } - - $hooks .= " }\n"; - } - - $extendsInternalClass = false; - if ($parent = $class) { - do { - $extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal(); - } while (!$extendsInternalClass && $parent = $parent->getParentClass()); - } - $methodsHaveToBeProxied = $extendsInternalClass; - $methods = []; - $methodReflectors = array_merge(...$methodReflectors); - - foreach ($methodReflectors as $method) { - if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) { - continue; - } - $methodsHaveToBeProxied = true; - $trait = new \ReflectionMethod(LazyProxyTrait::class, '__get'); - $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); - $body[0] = str_replace('): mixed', '): '.$type, $body[0]); - $methods['__get'] = strtr(implode('', $body).' }', [ - 'Hydrator' => '\\'.Hydrator::class, - 'Registry' => '\\'.LazyObjectRegistry::class, - ]); - break; - } - - foreach ($methodReflectors as $method) { - if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) { - continue; - } - if ($method->isFinal()) { - if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) { - throw new LogicException(\sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name)); - } - continue; - } - if (method_exists(LazyProxyTrait::class, $method->name) || ($method->isProtected() && !$method->isAbstract())) { - continue; - } - - $signature = self::exportSignature($method, true, $args); - $parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})"; - - if ($method->isStatic()) { - $body = " $parentCall;"; - } elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) { - $body = <<lazyObjectState)) { - (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); - } else { - {$parentCall}; - } - EOPHP; - } else { - if (!$methodsHaveToBeProxied && !$method->isAbstract()) { - // Skip proxying methods that might return $this - foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) { - if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) { - continue 2; - } - foreach ([$class, ...$interfaces] as $r) { - if ($r && is_a($r->name, $type, true)) { - continue 3; - } - } - } - } - - $body = <<lazyObjectState)) { - return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); - } - - return {$parentCall}; - EOPHP; - } - $methods[$lcName] = " {$signature}\n {\n{$body}\n }"; - } - - $types = $interfaces = array_unique(array_column($interfaces, 'name')); - $interfaces[] = LazyObjectInterface::class; - $interfaces = implode(', \\', $interfaces); - $parent = $class ? ' extends \\'.$class->name : ''; - array_unshift($types, $class ? 'parent' : ''); - $type = ltrim(implode('&\\', $types), '&'); - - if (!$class) { - $trait = new \ReflectionMethod(LazyProxyTrait::class, 'initializeLazyObject'); - $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); - $body[0] = str_replace('): parent', '): '.$type, $body[0]); - $methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods; - } - $body = $methods ? "\n".implode("\n\n", $methods)."\n" : ''; - $propertyScopes = $class ? self::exportPropertyScopes($class->name, $propertyScopes) : '[]'; - - if ( - $class?->hasMethod('__unserialize') - && !$class->getMethod('__unserialize')->getParameters()[0]->getType() - ) { - // fix contravariance type problem when $class declares a `__unserialize()` method without typehint. - $lazyProxyTraitStatement = <<__doUnserialize(\$data); - } - - EOPHP; - } else { - $lazyProxyTraitStatement = <<class : $function->getNamespaceName().'\\'; - $namespace = substr($namespace, 0, strrpos($namespace, '\\') ?: 0); - foreach ($function->getParameters() as $param) { - $parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '') - .($withParameterTypes && $param->hasType() ? self::exportType($param).' ' : '') - .($param->isPassedByReference() ? '&' : '') - .($param->isVariadic() ? '...' : '').'$'.$param->name - .($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param, $namespace) : ''); - if ($param->isPassedByReference()) { - $byRefIndex = 1 + $param->getPosition(); - } - $args .= ($param->isVariadic() ? '...$' : '$').$param->name.', '; - } - - if (!$param || !$byRefIndex) { - $args = '...\func_get_args()'; - } elseif ($param->isVariadic()) { - $args = substr($args, 0, -2); - } else { - $args = explode(', ', $args, 1 + $byRefIndex); - $args[$byRefIndex] = \sprintf('...\array_slice(\func_get_args(), %d)', $byRefIndex); - $args = implode(', ', $args); - } - - return implode(', ', $parameters); - } - - public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string - { - $parameters = self::exportParameters($function, $withParameterTypes, $args); - - $signature = 'function '.($function->returnsReference() ? '&' : '') - .($function->isClosure() ? '' : $function->name).'('.$parameters.')'; - - if ($function instanceof \ReflectionMethod) { - $signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private ')) - .($function->isStatic() ? 'static ' : '').$signature; - } - if ($function->hasReturnType()) { - $signature .= ': '.self::exportType($function); - } - - static $getPrototype; - $getPrototype ??= (new \ReflectionMethod(\ReflectionMethod::class, 'getPrototype'))->invoke(...); - - while ($function) { - if ($function->hasTentativeReturnType()) { - return '#[\ReturnTypeWillChange] '.$signature; - } - - try { - $function = $function instanceof \ReflectionMethod && $function->isAbstract() ? false : $getPrototype($function); - } catch (\ReflectionException) { - break; - } - } - - return $signature; - } - - public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, ?\ReflectionType $type = null): ?string - { - if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) { - return null; - } - $class = null; - $types = []; - if ($type instanceof \ReflectionUnionType) { - $reflectionTypes = $type->getTypes(); - $glue = '|'; - } elseif ($type instanceof \ReflectionIntersectionType) { - $reflectionTypes = $type->getTypes(); - $glue = '&'; - } else { - $reflectionTypes = [$type]; - $glue = null; - } - - foreach ($reflectionTypes as $type) { - if ($type instanceof \ReflectionIntersectionType) { - if ('' !== $name = '('.self::exportType($owner, $noBuiltin, $type).')') { - $types[] = $name; - } - continue; - } - $name = $type->getName(); - - if ($noBuiltin && $type->isBuiltin()) { - continue; - } - if (\in_array($name, ['parent', 'self'], true) && $class ??= $owner->getDeclaringClass()) { - $name = 'parent' === $name ? ($class->getParentClass() ?: null)?->name ?? 'parent' : $class->name; - } - - $types[] = ($noBuiltin || $type->isBuiltin() || 'static' === $name ? '' : '\\').$name; - } - - if (!$types) { - return ''; - } - if (null === $glue) { - $defaultNull = $owner instanceof \ReflectionParameter && 'NULL' === rtrim(substr(explode('$'.$owner->name.' = ', (string) $owner, 2)[1] ?? '', 0, -2)); - - return (!$noBuiltin && ($type->allowsNull() || $defaultNull) && !\in_array($name, ['mixed', 'null'], true) ? '?' : '').$types[0]; - } - sort($types); - - return implode($glue, $types); - } - - private static function exportPropertyScopes(string $parent, array $propertyScopes): string - { - uksort($propertyScopes, 'strnatcmp'); - foreach ($propertyScopes as $k => $v) { - unset($propertyScopes[$k][4]); - } - $propertyScopes = VarExporter::export($propertyScopes); - $propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes); - $propertyScopes = preg_replace("/(?|(,)\n( ) |\n |,\n (\]))/", '$1$2', $propertyScopes); - $propertyScopes = str_replace("\n", "\n ", $propertyScopes); - - return $propertyScopes; - } - - private static function exportDefault(\ReflectionParameter $param, $namespace): string - { - $default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2)); - - if (\in_array($default, ['', 'NULL'], true)) { - return 'null'; - } - if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) { - return VarExporter::export($param->getDefaultValue()); - } - - $regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/"; - $parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); - - $regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(\(?)(?!: )/'; - $callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass()) - ? fn ($m) => $m[1].match ($m[2]) { - 'new', 'false', 'true', 'null' => $m[2], - 'NULL' => 'null', - 'self' => '\\'.$class->name, - 'namespace\\parent', - 'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent', - default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), - }.$m[3] - : fn ($m) => $m[1].match ($m[2]) { - 'new', 'false', 'true', 'null', 'self', 'parent' => $m[2], - 'NULL' => 'null', - default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), - }.$m[3]; - - return implode('', array_map(fn ($part) => match ($part[0]) { - '"' => $part, // for internal classes only - "'" => false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part, - default => preg_replace_callback($regexp, $callback, $part), - }, $parts)); - } - - private static function exportSymbol(string $symbol, bool $mightBeRootConst, string $namespace): string - { - if (!$mightBeRootConst - || false === ($ns = strrpos($symbol, '\\')) - || substr($symbol, 0, $ns) !== $namespace - || \defined($symbol) - || !\defined(substr($symbol, $ns + 1)) - ) { - return '\\'.$symbol; - } - - return '\\'.substr($symbol, $ns + 1); - } -} diff --git a/vendor/symfony/var-exporter/README.md b/vendor/symfony/var-exporter/README.md index 7195270..a34e4c2 100644 --- a/vendor/symfony/var-exporter/README.md +++ b/vendor/symfony/var-exporter/README.md @@ -1,22 +1,15 @@ VarExporter Component ===================== -The VarExporter component provides various tools to deal with the internal state -of objects: +The VarExporter component allows exporting any serializable PHP data structure to +plain PHP code. While doing so, it preserves all the semantics associated with +the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, +`__serialize`, `__unserialize`). -- `VarExporter::export()` allows exporting any serializable PHP data structure to - plain PHP code. While doing so, it preserves all the semantics associated with - the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, - `__serialize`, `__unserialize`); -- `Instantiator::instantiate()` creates an object and sets its properties without - calling its constructor nor any other methods; -- `Hydrator::hydrate()` can set the properties of an existing object; -- `Lazy*Trait` can make a class behave as a lazy-loading ghost or virtual proxy. +It also provides an instantiator that allows creating and populating objects +without calling their constructor nor any other methods. -VarExporter::export() ---------------------- - -The reason to use `VarExporter::export()` *vs* `serialize()` or +The reason to use this component *vs* `serialize()` or [igbinary](https://github.com/igbinary/igbinary) is performance: thanks to OPcache, the resulting code is significantly faster and more memory efficient than using `unserialize()` or `igbinary_unserialize()`. @@ -26,107 +19,15 @@ Unlike `var_export()`, this works on any serializable PHP value. It also provides a few improvements over `var_export()`/`serialize()`: * the output is PSR-2 compatible; - * the output can be re-indented without messing up with `\r` or `\n` in the data; - * missing classes throw a `ClassNotFoundException` instead of being unserialized - to `PHP_Incomplete_Class` objects; + * the output can be re-indented without messing up with `\r` or `\n` in the data + * missing classes throw a `ClassNotFoundException` instead of being unserialized to + `PHP_Incomplete_Class` objects; * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` instances are preserved; * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes throw an exception when being serialized (their unserialized version is broken anyway, see https://bugs.php.net/76737). -Instantiator and Hydrator -------------------------- - -`Instantiator::instantiate($class)` creates an object of the given class without -calling its constructor nor any other methods. - -`Hydrator::hydrate()` sets the properties of an existing object, including -private and protected ones. For example: - -```php -// Sets the public or protected $object->propertyName property -Hydrator::hydrate($object, ['propertyName' => $propertyValue]); - -// Sets a private property defined on its parent Bar class: -Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); - -// Alternative way to set the private $object->privateBarProperty property -Hydrator::hydrate($object, [], [ - Bar::class => ['privateBarProperty' => $propertyValue], -]); -``` - -`Lazy*Trait` ------------- - -The component provides two lazy-loading patterns: ghost objects and virtual -proxies (see https://martinfowler.com/eaaCatalog/lazyLoad.html for reference). - -Ghost objects work only with concrete and non-internal classes. In the generic -case, they are not compatible with using factories in their initializer. - -Virtual proxies work with concrete, abstract or internal classes. They provide an -API that looks like the actual objects and forward calls to them. They can cause -identity problems because proxies might not be seen as equivalents to the actual -objects they proxy. - -Because of this identity problem, ghost objects should be preferred when -possible. Exceptions thrown by the `ProxyHelper` class can help decide when it -can be used or not. - -Ghost objects and virtual proxies both provide implementations for the -`LazyObjectInterface` which allows resetting them to their initial state or to -forcibly initialize them when needed. Note that resetting a ghost object skips -its read-only properties. You should use a virtual proxy to reset read-only -properties. - -### `LazyGhostTrait` - -By using `LazyGhostTrait` either directly in your classes or by using -`ProxyHelper::generateLazyGhost()`, you can make their instances lazy-loadable. -This works by creating these instances empty and by computing their state only -when accessing a property. - -```php -class FooLazyGhost extends Foo -{ - use LazyGhostTrait; -} - -$foo = FooLazyGhost::createLazyGhost(initializer: function (Foo $instance): void { - // [...] Use whatever heavy logic you need here - // to compute the $dependencies of the $instance - $instance->__construct(...$dependencies); - // [...] Call setters, etc. if needed -}); - -// $foo is now a lazy-loading ghost object. The initializer will -// be called only when and if a *property* is accessed. -``` - -### `LazyProxyTrait` - -Alternatively, `LazyProxyTrait` can be used to create virtual proxies: - -```php -$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(Foo::class)); -// $proxyCode contains the reference to LazyProxyTrait -// and should be dumped into a file in production envs -eval('class FooLazyProxy'.$proxyCode); - -$foo = FooLazyProxy::createLazyProxy(initializer: function (): Foo { - // [...] Use whatever heavy logic you need here - // to compute the $dependencies of the $instance - $instance = new Foo(...$dependencies); - // [...] Call setters, etc. if needed - - return $instance; -}); -// $foo is now a lazy-loading virtual proxy object. The initializer will -// be called only when and if a *method* is called. -``` - Resources --------- diff --git a/vendor/symfony/var-exporter/VarExporter.php b/vendor/symfony/var-exporter/VarExporter.php index 22e9b51..3e2a4cc 100644 --- a/vendor/symfony/var-exporter/VarExporter.php +++ b/vendor/symfony/var-exporter/VarExporter.php @@ -32,12 +32,12 @@ final class VarExporter /** * Exports a serializable PHP value to PHP code. * - * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise - * @param array &$foundClasses Classes found in the value are added to this list as both keys and values + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * @param bool &$classes Classes found in the value are added to this list as both keys and values * * @throws ExceptionInterface When the provided value cannot be serialized */ - public static function export(mixed $value, ?bool &$isStaticValue = null, array &$foundClasses = []): string + public static function export(mixed $value, bool &$isStaticValue = null, array &$foundClasses = []): string { $isStaticValue = true; @@ -82,7 +82,7 @@ final class VarExporter ksort($states); $wakeups = [null]; - foreach ($states as $v) { + foreach ($states as $k => $v) { if (\is_array($v)) { $wakeups[-$v[0]] = $v[1]; } else { diff --git a/vendor/symfony/var-exporter/composer.json b/vendor/symfony/var-exporter/composer.json index e7b0fb0..3bf21a6 100644 --- a/vendor/symfony/var-exporter/composer.json +++ b/vendor/symfony/var-exporter/composer.json @@ -2,7 +2,7 @@ "name": "symfony/var-exporter", "type": "library", "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone", "lazy-loading", "proxy"], + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone"], "homepage": "https://symfony.com", "license": "MIT", "authors": [ @@ -16,13 +16,10 @@ } ], "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.0.2" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^5.4|^6.0" }, "autoload": { "psr-4": { "Symfony\\Component\\VarExporter\\": "" }, diff --git a/vendor/workerman/_www_wwwroot_fengketrade_think.pid b/vendor/workerman/_www_wwwroot_fengketrade_think.pid new file mode 100644 index 0000000..4a79507 --- /dev/null +++ b/vendor/workerman/_www_wwwroot_fengketrade_think.pid @@ -0,0 +1 @@ +584743 \ No newline at end of file diff --git a/vendor/workerman/channel/README.md b/vendor/workerman/channel/README.md new file mode 100644 index 0000000..0a4f7d8 --- /dev/null +++ b/vendor/workerman/channel/README.md @@ -0,0 +1,101 @@ +# Channel +基于订阅的多进程通讯组件,用于workerman进程间通讯或者服务器集群通讯,类似redis订阅发布机制。基于workerman开发。 + +Channel 提供两种通讯形式,分别是发布订阅的事件机制和消息队列机制。 + +它们的主要区别是: +- 事件机制是消息发出后,所有订阅该事件的客户端都能收到消息。 +- 消息队列机制是消息发出后,所有订阅该消息的客户端只有一个会收到消息,如果客户端忙消息会进行排队直到有客户端闲置后重新取到消息。 +- 需要注意的是 Channel 只是提供一种通讯方式,本身并不提供消息确认、重试、延迟、持久化等功能,请根据实际情况合理使用。 + +# 手册地址 +[Channel手册](http://doc.workerman.net/components/channel.html) + +# 服务端 +```php +use Workerman\Worker; + +//Tcp 通讯方式 +$channel_server = new Channel\Server('0.0.0.0', 2206); + +//Unix Domain Socket 通讯方式 +//$channel_server = new Channel\Server('unix:///tmp/workerman-channel.sock'); + +if(!defined('GLOBAL_START')) +{ + Worker::runAll(); +} +``` + +# 客户端 +```php +use Workerman\Worker; + +$worker = new Worker(); +$worker->onWorkerStart = function() +{ + // Channel客户端连接到Channel服务端 + Channel\Client::connect('', 2206); + + // 使用 Unix Domain Socket 通讯 + //Channel\Client::connect('unix:///tmp/workerman-channel.sock'); + + // 要订阅的事件名称(名称可以为任意的数字和字符串组合) + $event_name = 'event_xxxx'; + // 订阅某个自定义事件并注册回调,收到事件后会自动触发此回调 + Channel\Client::on($event_name, function($event_data){ + var_dump($event_data); + }); +}; +$worker->onMessage = function($connection, $data) +{ + // 要发布的事件名称 + $event_name = 'event_xxxx'; + // 事件数据(数据格式可以为数字、字符串、数组),会传递给客户端回调函数作为参数 + $event_data = array('some data.', 'some data..'); + // 发布某个自定义事件,订阅这个事件的客户端会收到事件数据,并触发客户端对应的事件回调 + Channel\Client::publish($event_name, $event_data); +}; + +if(!defined('GLOBAL_START')) +{ + Worker::runAll(); +} +```` + +## 消息队列示例 +```php +use Workerman\Worker; +use Workerman\Timer; + +$worker = new Worker(); +$worker->name = 'Producer'; +$worker->onWorkerStart = function() +{ + Client::connect(); + + $count = 0; + Timer::add(1, function() { + Client::enqueue('queue', 'Hello World '.time()); + }); +}; + +$mq = new Worker(); +$mq->name = 'Consumer'; +$mq->count = 4; +$mq->onWorkerStart = function($worker) { + Client::connect(); + + //订阅消息 queue + Client::watch('queue', function($data) use ($worker) { + echo "Worker {$worker->id} get queue: $data\n"; + }); + + //10 秒后取消订阅该消息 + Timer::add(10, function() { + Client::unwatch('queue'); + }, [], false); +}; + +Worker::runAll(); +``` \ No newline at end of file diff --git a/vendor/workerman/channel/composer.json b/vendor/workerman/channel/composer.json new file mode 100644 index 0000000..df63a66 --- /dev/null +++ b/vendor/workerman/channel/composer.json @@ -0,0 +1,12 @@ +{ + "name" : "workerman/channel", + "type" : "library", + "homepage": "http://www.workerman.net", + "license" : "MIT", + "require": { + "workerman/workerman" : ">=4.0.12" + }, + "autoload": { + "psr-4": {"Channel\\": "./src"} + } +} diff --git a/vendor/workerman/channel/src/Client.php b/vendor/workerman/channel/src/Client.php new file mode 100644 index 0000000..add1ec4 --- /dev/null +++ b/vendor/workerman/channel/src/Client.php @@ -0,0 +1,393 @@ +protocol = Frame::class; + } + + $conn->onClose = [self::class, 'onRemoteClose']; + $conn->onConnect = [self::class, 'onRemoteConnect']; + $conn->onMessage = [self::class , 'onRemoteMessage']; + $conn->connect(); + + if (empty(self::$_pingTimer)) { + self::$_pingTimer = Timer::add(self::$pingInterval, 'Channel\Client::ping'); + } + // Not workerman environment. + } else { + $remote = strpos($ip, 'unix://') === false ? 'tcp://'.self::$_remoteIp.':'.self::$_remotePort : $ip; + $conn = stream_socket_client($remote, $code, $message, 5); + if (!$conn) { + throw new \Exception($message); + } + } + + self::$_remoteConnection = $conn; + } + + /** + * onRemoteMessage. + * @param \Workerman\Connection\TcpConnection $connection + * @param string $data + * @throws \Exception + */ + public static function onRemoteMessage($connection, $data) + { + $data = unserialize($data); + $type = $data['type']; + $event = $data['channel']; + $event_data = $data['data']; + + $callback = null; + + if ($type == 'event') { + if (!empty(self::$_events[$event])) { + call_user_func(self::$_events[$event], $event_data); + } elseif (!empty(Client::$onMessage)) { + call_user_func(Client::$onMessage, $event, $event_data); + } else { + throw new \Exception("event:$event have not callback"); + } + } else { + if (isset(self::$_queues[$event])) { + call_user_func(self::$_queues[$event], $event_data); + } else { + throw new \Exception("queue:$event have not callback"); + } + } + } + + /** + * Ping. + * @return void + */ + public static function ping() + { + if(self::$_remoteConnection) + { + self::$_remoteConnection->send(''); + } + } + + /** + * onRemoteClose. + * @return void + */ + public static function onRemoteClose() + { + Timer::add(0.5, function() { + echo "Waring channel connection closed and try to reconnect\n"; + }, array(), false); + self::$_remoteConnection = null; + self::clearTimer(); + self::$_reconnectTimer = Timer::add(1, 'Channel\Client::connect', array(self::$_remoteIp, self::$_remotePort)); + if (self::$onClose) { + call_user_func(Client::$onClose); + } + } + + /** + * onRemoteConnect. + * @return void + */ + public static function onRemoteConnect() + { + $all_event_names = array_keys(self::$_events); + if($all_event_names) + { + self::subscribe($all_event_names); + } + self::clearTimer(); + + if (self::$onConnect) { + call_user_func(Client::$onConnect); + } + } + + /** + * clearTimer. + * @return void + */ + public static function clearTimer() + { + if (!self::$_isWorkermanEnv) { + throw new \Exception('Channel\\Client not support clearTimer method when it is not in the workerman environment.'); + } + if(self::$_reconnectTimer) + { + Timer::del(self::$_reconnectTimer); + self::$_reconnectTimer = null; + } + } + + /** + * On. + * @param string $event + * @param callback $callback + * @throws \Exception + */ + public static function on($event, $callback) + { + if (!is_callable($callback)) { + throw new \Exception('callback is not callable for event.'); + } + self::$_events[$event] = $callback; + self::subscribe($event); + } + + /** + * Subscribe. + * @param string $events + * @return void + */ + public static function subscribe($events) + { + $events = (array)$events; + self::send(array('type' => 'subscribe', 'channels'=>$events)); + foreach ($events as $event) { + if(!isset(self::$_events[$event])) { + self::$_events[$event] = null; + } + } + } + + /** + * Unsubscribe. + * @param string $events + * @return void + */ + public static function unsubscribe($events) + { + $events = (array)$events; + self::send(array('type' => 'unsubscribe', 'channels'=>$events)); + foreach($events as $event) { + unset(self::$_events[$event]); + } + } + + /** + * Publish. + * @param string $events + * @param mixed $data + */ + public static function publish($events, $data , $is_loop = false) + { + $type = $is_loop == true ? 'publishLoop' : 'publish'; + self::sendAnyway(array('type' => $type, 'channels' => (array)$events, 'data' => $data)); + } + + /** + * Watch a channel of queue + * @param string|array $channels + * @param callable $callback + * @param boolean $autoReserve Auto reserve after callback finished. + * But sometime you may don't want reserve immediately, or in some asynchronous job, + * you want reserve in finished callback, so you should set $autoReserve to false + * and call Client::reserve() after watch() and in finish callback manually. + * @throws \Exception + */ + public static function watch($channels, $callback, $autoReserve=true) + { + if (!is_callable($callback)) { + throw new \Exception('callback is not callable for watch.'); + } + + if ($autoReserve) { + $callback = static function($data) use ($callback) { + try { + call_user_func($callback, $data); + } catch (\Exception $e) { + throw $e; + } catch (\Error $e) { + throw $e; + } finally { + self::reserve(); + } + }; + } + + $channels = (array)$channels; + self::send(array('type' => 'watch', 'channels'=>$channels)); + + foreach ($channels as $channel) { + self::$_queues[$channel] = $callback; + } + + if ($autoReserve) { + self::reserve(); + } + } + + /** + * Unwatch a channel of queue + * @param string $channel + * @throws \Exception + */ + public static function unwatch($channels) + { + $channels = (array)$channels; + self::send(array('type' => 'unwatch', 'channels'=>$channels)); + foreach ($channels as $channel) { + if (isset(self::$_queues[$channel])) { + unset(self::$_queues[$channel]); + } + } + } + + /** + * Put data to queue + * @param string|array $channels + * @param mixed $data + * @throws \Exception + */ + public static function enqueue($channels, $data) + { + self::sendAnyway(array('type' => 'enqueue', 'channels' => (array)$channels, 'data' => $data)); + } + + /** + * Start reserve queue manual + * @throws \Exception + */ + public static function reserve() + { + self::send(array('type' => 'reserve')); + } + + /** + * Send through workerman environment + * @param $data + * @throws \Exception + */ + protected static function send($data) + { + if (!self::$_isWorkermanEnv) { + throw new \Exception("Channel\\Client not support {$data['type']} method when it is not in the workerman environment."); + } + self::connect(self::$_remoteIp, self::$_remotePort); + self::$_remoteConnection->send(serialize($data)); + } + + /** + * Send from any environment + * @param $data + * @throws \Exception + */ + protected static function sendAnyway($data) + { + self::connect(self::$_remoteIp, self::$_remotePort); + $body = serialize($data); + if (self::$_isWorkermanEnv) { + self::$_remoteConnection->send($body); + } else { + $buffer = pack('N', 4+strlen($body)) . $body; + fwrite(self::$_remoteConnection, $buffer); + } + } + +} diff --git a/vendor/workerman/channel/src/Queue.php b/vendor/workerman/channel/src/Queue.php new file mode 100644 index 0000000..3b1b56e --- /dev/null +++ b/vendor/workerman/channel/src/Queue.php @@ -0,0 +1,89 @@ +name = $name; + $this->queue = new \SplQueue(); + } + + /** + * @param TcpConnection $connection + */ + public function addWatch($connection) + { + if (!isset($this->watcher[$connection->id])) { + $this->watcher[$connection->id] = $connection; + $connection->watchs[] = $this->name; + } + } + + /** + * @param TcpConnection $connection + */ + public function removeWatch($connection) + { + if (isset($connection->watchs) && in_array($this->name, $connection->watchs)) { + $idx = array_search($this->name, $connection->watchs); + unset($connection->watchs[$idx]); + } + if (isset($this->watcher[$connection->id])) { + unset($this->watcher[$connection->id]); + } + if (isset($this->consumer[$connection->id])) { + unset($this->consumer[$connection->id]); + } + } + + /** + * @param TcpConnection $connection + */ + public function addConsumer($connection) + { + if (isset($this->watcher[$connection->id]) && !isset($this->consumer[$connection->id])) { + $this->consumer[$connection->id] = $connection; + } + $this->dispatch(); + } + + public function enqueue($data) + { + $this->queue->enqueue($data); + $this->dispatch(); + } + + private function dispatch() + { + if ($this->queue->isEmpty() || count($this->consumer) == 0) { + return; + } + + while (!$this->queue->isEmpty()) { + $data = $this->queue->dequeue(); + $idx = key($this->consumer); + $connection = $this->consumer[$idx]; + unset($this->consumer[$idx]); + $connection->send(serialize(array('type'=>'queue', 'channel'=>$this->name, 'data' => $data))); + if (count($this->consumer) == 0) { + break; + } + } + } + + public function isEmpty() + { + return empty($this->watcher) && $this->queue->isEmpty(); + } + +} \ No newline at end of file diff --git a/vendor/workerman/channel/src/Server.php b/vendor/workerman/channel/src/Server.php new file mode 100644 index 0000000..1bb9f93 --- /dev/null +++ b/vendor/workerman/channel/src/Server.php @@ -0,0 +1,179 @@ +protocol = Frame::class; + } + $this->ip = $ip; + $worker->count = 1; + $worker->name = 'ChannelServer'; + $worker->channels = array(); + $worker->onMessage = array($this, 'onMessage') ; + $worker->onClose = array($this, 'onClose'); + $this->_worker = $worker; + } + + /** + * onClose + * @return void + */ + public function onClose($connection) + { + if (!empty($connection->channels)) { + foreach ($connection->channels as $channel) { + unset($this->_worker->channels[$channel][$connection->id]); + if (empty($this->_worker->channels[$channel])) { + unset($this->_worker->channels[$channel]); + } + } + } + + if (!empty($connection->watchs)) { + foreach ($connection->watchs as $channel) { + if (isset($this->_queues[$channel])) { + $this->_queues[$channel]->removeWatch($connection); + if ($this->_queues[$channel]->isEmpty()) { + unset($this->_queues[$channel]); + } + } + } + } + } + + /** + * onMessage. + * @param \Workerman\Connection\TcpConnection $connection + * @param string $data + */ + public function onMessage($connection, $data) + { + if(!$data) + { + return; + } + $worker = $this->_worker; + $data = unserialize($data); + $type = $data['type']; + switch($type) + { + case 'subscribe': + foreach($data['channels'] as $channel) + { + $connection->channels[$channel] = $channel; + $worker->channels[$channel][$connection->id] = $connection; + } + break; + case 'unsubscribe': + foreach($data['channels'] as $channel) { + if (isset($connection->channels[$channel])) { + unset($connection->channels[$channel]); + } + if (isset($worker->channels[$channel][$connection->id])) { + unset($worker->channels[$channel][$connection->id]); + if (empty($worker->channels[$channel])) { + unset($worker->channels[$channel]); + } + } + } + break; + case 'publish': + foreach ($data['channels'] as $channel) { + if (empty($worker->channels[$channel])) { + continue; + } + $buffer = serialize(array('type' => 'event', 'channel' => $channel, 'data' => $data['data'])); + foreach ($worker->channels[$channel] as $connection) { + $connection->send($buffer); + } + } + break; + case 'publishLoop': + //choose one subscriber from the list + foreach ($data['channels'] as $channel) { + if (empty($worker->channels[$channel])) { + continue; + } + $buffer = serialize(array('type' => 'event', 'channel' => $channel, 'data' => $data['data'])); + + //这是要点,每次取出一个元素,如果取不到,说明已经到最后,重置到第一个 + $connection = next($worker->channels[$channel]); + if( $connection == false ){ + $connection = reset($worker->channels[$channel]); + } + $connection->send($buffer); + } + break; + case 'watch': + foreach ($data['channels'] as $channel) { + $this->getQueue($channel)->addWatch($connection); + } + break; + case 'unwatch': + foreach ($data['channels'] as $channel) { + if (isset($this->_queues[$channel])) { + $this->_queues[$channel]->removeWatch($connection); + if ($this->_queues[$channel]->isEmpty()) { + unset($this->_queues[$channel]); + } + } + } + break; + case 'enqueue': + foreach ($data['channels'] as $channel) { + $this->getQueue($channel)->enqueue($data['data']); + } + break; + case 'reserve': + if (isset($connection->watchs)) { + foreach ($connection->watchs as $channel) { + if (isset($this->_queues[$channel])) { + $this->_queues[$channel]->addConsumer($connection); + } + } + } + break; + } + } + + private function getQueue($channel) + { + if (isset($this->_queues[$channel])) { + return $this->_queues[$channel]; + } + return ($this->_queues[$channel] = new Queue($channel)); + } + +} diff --git a/vendor/workerman/channel/test/queue.php b/vendor/workerman/channel/test/queue.php new file mode 100644 index 0000000..72dbb63 --- /dev/null +++ b/vendor/workerman/channel/test/queue.php @@ -0,0 +1,53 @@ +name = 'Event'; +$worker->onWorkerStart = function() +{ + Client::connect(); + + $count = 0; + $timerId = Timer::add(0.01, function() use (&$timerId, &$count) { + Client::publish('test event', 'some data'); + $count++; + Client::enqueue('task-queue', time()); + if ($count == 1000) { + Timer::del($timerId); + } + }); + + Timer::add(10, function() { + Client::enqueue('task-queue', 'hello every 10 seconds'); + }); +}; + +$mq = new Worker(); +$mq->name = 'Queue'; +$mq->count = 4; +$mq->onWorkerStart = function($worker) { + Client::connect(); + $countDown = 20; + $id = 1; + Client::watch('task-queue', function($data) use ($worker, &$countDown, &$id) { + echo "[$id] Worker {$worker->id} get queue: $data\n"; + sleep(0.2); + $countDown--; + $id++; + if ($worker->id > 1 && $countDown == 0) { + Client::unwatch('task-queue'); + } + Timer::add(1, [Client::class, 'reserve'], [], false); + }); +}; + +Worker::runAll(); diff --git a/vendor/workerman/channel/test/server.php b/vendor/workerman/channel/test/server.php new file mode 100644 index 0000000..dbb9ca5 --- /dev/null +++ b/vendor/workerman/channel/test/server.php @@ -0,0 +1,28 @@ +onWorkerStart = function() +{ + Client::connect(); + + Client::on('test event', function($event_data){ + echo 'test event triggered event_data :'; + var_dump($event_data); + }); + + Timer::add(2, function(){ + Client::publish('test event', 'some data'); + }); +}; + +Worker::runAll(); diff --git a/vendor/workerman/channel/test/start_channel.php b/vendor/workerman/channel/test/start_channel.php new file mode 100644 index 0000000..4366dd1 --- /dev/null +++ b/vendor/workerman/channel/test/start_channel.php @@ -0,0 +1,23 @@ +count = 8; +$processName = "client"; +$worker->name = $processName; +$worker->reusePort = true; //开启均衡负载模式 + +Worker::$pidFile = "var/{$processName}.pid"; +Worker::$logFile = "var/{$processName}_logFile.log"; +Worker::$stdoutFile = "var/{$processName}_stdout.log"; + +$worker->onWorkerStart = function() use($worker){ + usleep(10); + Channel\Client::connect('127.0.0.1' , 2206); + $event_name = "test_channel"; + Channel\Client::on($event_name, function($event_data)use($worker ,$event_name ){ + $log_str = "{$worker->id} on {$event_name}:".json_encode($event_data,320)."\n"; + echo $log_str; + }); +}; + +Worker::runAll(); \ No newline at end of file diff --git a/vendor/workerman/channel/test/start_send.php b/vendor/workerman/channel/test/start_send.php new file mode 100644 index 0000000..c5c30f8 --- /dev/null +++ b/vendor/workerman/channel/test/start_send.php @@ -0,0 +1,35 @@ +count = 1; +$processName = "send"; +$worker->name = $processName; +$worker->reusePort = true; //开启均衡负载模式 + +Worker::$pidFile = "var/{$processName}.pid"; +Worker::$logFile = "var/{$processName}_logFile.log"; +Worker::$stdoutFile = "var/{$processName}_stdout.log"; + +$worker->onWorkerStart = function() use($worker){ + Channel\Client::connect('127.0.0.1' , 2206); + Timer::add( 1 , function ()use($worker){ + $data_arr = [ + 'time' => microtime(true), + 'date' => date("Y-m-d H:i:s"), + ]; + $event_name = "test_channel"; + Channel\Client::publish($event_name, $data_arr , true); + }); +}; +Worker::runAll(); \ No newline at end of file diff --git a/vendor/workerman/phpsocket.io/README.md b/vendor/workerman/phpsocket.io/README.md new file mode 100644 index 0000000..d54328f --- /dev/null +++ b/vendor/workerman/phpsocket.io/README.md @@ -0,0 +1,184 @@ +# phpsocket.io +A server side alternative implementation of [socket.io](https://github.com/socketio/socket.io) in PHP based on [Workerman](https://github.com/walkor/Workerman).
+ +# Notice +Only support socket.io >= v1.3.0 and <= v2.x
+This project is just translate socket.io by [workerman](https://github.com/walkor/Workerman).
+More api just see [https://socket.io/docs/v2/server-api/](https://socket.io/docs/v2/server-api/) + +# Install +composer require workerman/phpsocket.io + +# Examples +## Simple chat +start.php +```php + +use Workerman\Worker; +use PHPSocketIO\SocketIO; +require_once __DIR__ . '/vendor/autoload.php'; + +// Listen port 2021 for socket.io client +$io = new SocketIO(2021); +$io->on('connection', function ($socket) use ($io) { + $socket->on('chat message', function ($msg) use ($io) { + $io->emit('chat message', $msg); + }); +}); + +Worker::runAll(); +``` + +## Another chat demo + +https://github.com/walkor/phpsocket.io/blob/master/examples/chat/start_io.php +```php + +use Workerman\Worker; +use PHPSocketIO\SocketIO; +require_once __DIR__ . '/vendor/autoload.php'; + +// Listen port 2020 for socket.io client +$io = new SocketIO(2020); +$io->on('connection', function ($socket) { + $socket->addedUser = false; + + // When the client emits 'new message', this listens and executes + $socket->on('new message', function ($data) use ($socket) { + // We tell the client to execute 'new message' + $socket->broadcast->emit('new message', array( + 'username' => $socket->username, + 'message' => $data + )); + }); + + // When the client emits 'add user', this listens and executes + $socket->on('add user', function ($username) use ($socket) { + global $usernames, $numUsers; + + // We store the username in the socket session for this client + $socket->username = $username; + // Add the client's username to the global list + $usernames[$username] = $username; + ++$numUsers; + + $socket->addedUser = true; + $socket->emit('login', array( + 'numUsers' => $numUsers + )); + + // echo globally (all clients) that a person has connected + $socket->broadcast->emit('user joined', array( + 'username' => $socket->username, + 'numUsers' => $numUsers + )); + }); + + // When the client emits 'typing', we broadcast it to others + $socket->on('typing', function () use ($socket) { + $socket->broadcast->emit('typing', array( + 'username' => $socket->username + )); + }); + + // When the client emits 'stop typing', we broadcast it to others + $socket->on('stop typing', function () use ($socket) { + $socket->broadcast->emit('stop typing', array( + 'username' => $socket->username + )); + }); + + // When the user disconnects, perform this + $socket->on('disconnect', function () use ($socket) { + global $usernames, $numUsers; + + // Remove the username from global usernames list + if ($socket->addedUser) { + unset($usernames[$socket->username]); + --$numUsers; + + // echo globally that this client has left + $socket->broadcast->emit('user left', array( + 'username' => $socket->username, + 'numUsers' => $numUsers + )); + } + }); +}); + +Worker::runAll(); +``` + +## Enable SSL for https +**```(phpsocket.io>=1.1.1 && workerman>=3.3.7 required)```** + +start.php +```php + array( + 'local_cert' => '/your/path/of/server.pem', + 'local_pk' => '/your/path/of/server.key', + 'verify_peer' => false + ) +); +$io = new SocketIO(2021, $context); + +$io->on('connection', function ($connection) use ($io) { + echo "New connection coming\n"; +}); + +Worker::runAll(); +``` + +## Acknowledgement callback +```php + +use Workerman\Worker; +use PHPSocketIO\SocketIO; + +require_once __DIR__ . '/vendor/autoload.php'; + +$io = new SocketIO(2021); + +$io->on('connection', function ($connection) use ($io) { + $socket->on('message with ack', function ($data, $callback) use ($socket, $io) { + // acknowledgement callback + if ($callback && is_callable($callback)) { + $callback(0); + } + }); +}); + +Worker::runAll(); +``` + +# 手册 +[中文手册](https://github.com/walkor/phpsocket.io/tree/master/docs/zh) + +# Livedemo +[chat demo](http://demos.workerman.net/phpsocketio-chat/) + +# Run chat example +cd examples/chat + +## Start +```php start.php start``` for debug mode + +```php start.php start -d ``` for daemon mode + +## Stop +```php start.php stop``` + +## Status +```php start.php status``` + +# License +MIT diff --git a/vendor/workerman/phpsocket.io/composer.json b/vendor/workerman/phpsocket.io/composer.json new file mode 100644 index 0000000..7bbf16b --- /dev/null +++ b/vendor/workerman/phpsocket.io/composer.json @@ -0,0 +1,30 @@ +{ + "name": "workerman/phpsocket.io", + "description": "A server side alternative implementation of socket.io in PHP based on Workerman", + "type": "library", + "keywords": [ + "socket.io", + "phpsocket.io", + "workerman", + "sockets", + "async", + "stream", + "server", + "non-blocking" + ], + "homepage": "https://www.workerman.net", + "license": "MIT", + "require": { + "workerman/workerman": "^4.0.0", + "workerman/channel": ">=1.0.0", + "ext-json": "*" + }, + "autoload": { + "psr-4": { + "PHPSocketIO\\": "./src" + } + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.7" + } +} diff --git a/vendor/workerman/phpsocket.io/src/ChannelAdapter.php b/vendor/workerman/phpsocket.io/src/ChannelAdapter.php new file mode 100644 index 0000000..01ee3ea --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/ChannelAdapter.php @@ -0,0 +1,116 @@ +_channelId = (function_exists('random_int') ? random_int(1, 10000000) : rand(1, 10000000)) . "-" . (function_exists('posix_getpid') ? posix_getpid() : 1); + \Channel\Client::connect(self::$ip, self::$port); + \Channel\Client::$onMessage = [$this, 'onChannelMessage']; + \Channel\Client::subscribe("socket.io#/#"); + Debug::debug('ChannelAdapter __construct'); + } + + public function __destruct() + { + Debug::debug('ChannelAdapter __destruct'); + } + + public function add($id, $room) + { + $this->sids[$id][$room] = true; + $this->rooms[$room][$id] = true; + $channel = "socket.io#/#$room#"; + \Channel\Client::subscribe($channel); + } + + public function del($id, $room) + { + unset($this->sids[$id][$room]); + unset($this->rooms[$room][$id]); + if (empty($this->rooms[$room])) { + unset($this->rooms[$room]); + $channel = "socket.io#/#$room#"; + \Channel\Client::unsubscribe($channel); + } + } + + public function delAll($id) + { + $rooms = isset($this->sids[$id]) ? array_keys($this->sids[$id]) : []; + if ($rooms) { + foreach ($rooms as $room) { + if (isset($this->rooms[$room][$id])) { + unset($this->rooms[$room][$id]); + $channel = "socket.io#/#$room#"; + \Channel\Client::unsubscribe($channel); + } + if (isset($this->rooms[$room]) && empty($this->rooms[$room])) { + unset($this->rooms[$room]); + } + } + } + unset($this->sids[$id]); + } + + public function onChannelMessage($channel, $msg) + { + if ($this->_channelId === array_shift($msg)) { + return; + } + + $packet = $msg[0]; + + $opts = $msg[1]; + + if (! $packet) { + echo "invalid channel:$channel packet \n"; + return; + } + + if (empty($packet['nsp'])) { + $packet['nsp'] = '/'; + } + + if ($packet['nsp'] != $this->nsp->name) { + echo "ignore different namespace {$packet['nsp']} != {$this->nsp->name}\n"; + return; + } + + $this->broadcast($packet, $opts, true); + } + + public function broadcast($packet, $opts, $remote = false) + { + parent::broadcast($packet, $opts); + if (! $remote) { + $packet['nsp'] = '/'; + + if (! empty($opts['rooms'])) { + foreach ($opts['rooms'] as $room) { + $chn = "socket.io#/#$room#"; + $msg = [$this->_channelId, $packet, $opts]; + \Channel\Client::publish($chn, $msg); + } + } else { + $chn = "socket.io#/#"; + $msg = [$this->_channelId, $packet, $opts]; + \Channel\Client::publish($chn, $msg); + } + } + } +} diff --git a/vendor/workerman/phpsocket.io/src/Client.php b/vendor/workerman/phpsocket.io/src/Client.php new file mode 100644 index 0000000..9a4a435 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Client.php @@ -0,0 +1,250 @@ +server = $server; + $this->conn = $conn; + $this->encoder = new Encoder(); + $this->decoder = new Decoder(); + $this->id = $conn->id; + $this->request = $conn->request; + $this->setup(); + Debug::debug('Client __construct'); + } + + public function __destruct() + { + Debug::debug('Client __destruct'); + } + + /** + * Sets up event listeners. + * + * @api private + */ + + public function setup() + { + $this->decoder->on('decoded', [$this, 'ondecoded']); + $this->conn->on('data', [$this, 'ondata']); + $this->conn->on('error', [$this, 'onerror']); + $this->conn->on('close', [$this, 'onclose']); + } + + /** + * Connects a client to a namespace. + * + * @param {String} namespace name + * @api private + */ + + public function connect($name) + { + if (! isset($this->server->nsps[$name])) { + $this->packet(['type' => Parser::ERROR, 'nsp' => $name, 'data' => 'Invalid namespace']); + return; + } + $nsp = $this->server->of($name); + if ('/' !== $name && ! isset($this->nsps['/'])) { + $this->connectBuffer[$name] = $name; + return; + } + $nsp->add($this, $nsp, [$this, 'nspAdd']); + } + + public function nspAdd($socket, $nsp) + { + $this->sockets[$socket->id] = $socket; + $this->nsps[$nsp->name] = $socket; + if ('/' === $nsp->name && $this->connectBuffer) { + foreach ($this->connectBuffer as $name) { + $this->connect($name); + } + $this->connectBuffer = []; + } + } + + /** + * Disconnects from all namespaces and closes transport. + * + * @api private + */ + public function disconnect() + { + foreach ($this->sockets as $socket) { + $socket->disconnect(); + } + $this->sockets = []; + $this->close(); + } + + /** + * Removes a socket. Called by each `Socket`. + * + * @api private + */ + public function remove($socket) + { + if (isset($this->sockets[$socket->id])) { + $nsp = $this->sockets[$socket->id]->nsp->name; + unset($this->sockets[$socket->id]); + unset($this->nsps[$nsp]); + } + } + + /** + * Closes the underlying connection. + * + * @api private + */ + public function close() + { + if (empty($this->conn)) { + return; + } + if ('open' === $this->conn->readyState) { + $this->conn->close(); + $this->onclose('forced server close'); + } + } + + /** + * Writes a packet to the transport. + * + * @param {Object} packet object + * @param {Object} options + * @api private + */ + public function packet($packet, $preEncoded = false, $volatile = false) + { + if (! empty($this->conn) && 'open' === $this->conn->readyState) { + if (! $preEncoded) { + // not broadcasting, need to encode + $encodedPackets = $this->encoder->encode($packet); + $this->writeToEngine($encodedPackets, $volatile); + } else { // a broadcast pre-encodes a packet + $this->writeToEngine($packet); + } + } + } + + public function writeToEngine($encodedPackets, $volatile = false) + { + if ($volatile) { + echo new Exception('volatile'); + } + if ($volatile && ! $this->conn->transport->writable) { + return; + } + if (isset($encodedPackets['nsp'])) { + unset($encodedPackets['nsp']); + } + foreach ($encodedPackets as $packet) { + $this->conn->write($packet); + } + } + + /** + * Called with incoming transport data. + * + * @api private + */ + public function ondata($data) + { + try { + // todo chek '2["chat message","2"]' . "\0" . '' + $this->decoder->add(trim($data)); + } catch (Exception $e) { + $this->onerror($e); + } + } + + /** + * Called when parser fully decodes a packet. + * + * @api private + */ + public function ondecoded($packet) + { + if (Parser::CONNECT == $packet['type']) { + $this->connect($packet['nsp']); + } else { + if (isset($this->nsps[$packet['nsp']])) { + $this->nsps[$packet['nsp']]->onpacket($packet); + } + } + } + + /** + * Handles an error. + * + * @param {Objcet} error object + * @api private + */ + public function onerror($err) + { + foreach ($this->sockets as $socket) { + $socket->onerror($err); + } + $this->onclose('client error'); + } + + /** + * Called upon transport close. + * + * @param {String} reason + * @api private + */ + public function onclose($reason) + { + if (empty($this->conn)) { + return; + } + // ignore a potential subsequent `close` event + $this->destroy(); + + // `nsps` and `sockets` are cleaned up seamlessly + foreach ($this->sockets as $socket) { + $socket->onclose($reason); + } + $this->sockets = null; + } + + /** + * Cleans up event listeners. + * + * @api private + */ + public function destroy() + { + if (! $this->conn) { + return; + } + $this->conn->removeAllListeners(); + $this->decoder->removeAllListeners(); + $this->encoder->removeAllListeners(); + $this->server = $this->conn = $this->encoder = $this->decoder = $this->request = $this->nsps = null; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Debug.php b/vendor/workerman/phpsocket.io/src/Debug.php new file mode 100644 index 0000000..bc5f0cd --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Debug.php @@ -0,0 +1,14 @@ +nsp = $nsp; + $this->encoder = new Parser\Encoder(); + Debug::debug('DefaultAdapter __construct'); + } + + public function __destruct() + { + Debug::debug('DefaultAdapter __destruct'); + } + + public function add($id, $room) + { + $this->sids[$id][$room] = true; + $this->rooms[$room][$id] = true; + } + + public function del($id, $room) + { + unset($this->sids[$id][$room]); + unset($this->rooms[$room][$id]); + if (empty($this->rooms[$room])) { + unset($this->rooms[$room]); + } + } + + public function delAll($id) + { + $rooms = array_keys($this->sids[$id] ?? []); + foreach ($rooms as $room) { + $this->del($id, $room); + } + unset($this->sids[$id]); + } + + public function broadcast($packet, $opts, $remote = false) + { + $rooms = $opts['rooms'] ?? []; + $except = $opts['except'] ?? []; + $flags = $opts['flags'] ?? []; + $packetOpts = [ + 'preEncoded' => true, + 'volatile' => $flags['volatile'] ?? null, + 'compress' => $flags['compress'] ?? null + ]; + $packet['nsp'] = $this->nsp->name; + $encodedPackets = $this->encoder->encode($packet); + if ($rooms) { + $ids = []; + foreach ($rooms as $i => $room) { + if (! isset($this->rooms[$room])) { + continue; + } + + $room = $this->rooms[$room]; + foreach ($room as $id => $item) { + if (isset($ids[$id]) || isset($except[$id])) { + continue; + } + if (isset($this->nsp->connected[$id])) { + $ids[$id] = true; + $this->nsp->connected[$id]->packet($encodedPackets, $packetOpts); + } + } + } + } else { + foreach ($this->sids as $id => $sid) { + if (isset($except[$id])) { + continue; + } + if (isset($this->nsp->connected[$id])) { + $socket = $this->nsp->connected[$id]; + $volatile = $flags['volatile'] ?? null; + $socket->packet($encodedPackets, true, $volatile); + } + } + } + } + + public function clients($rooms, $fn) + { + $sids = []; + foreach ($rooms as $room) { + $sids = array_merge($sids, $this->rooms[$room]); + } + $fn(); + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Engine.php b/vendor/workerman/phpsocket.io/src/Engine/Engine.php new file mode 100644 index 0000000..14903f2 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Engine.php @@ -0,0 +1,276 @@ + 'polling', + 'websocket' => 'websocket' + ]; + + public static $errorMessages = [ + 'Transport unknown', + 'Session ID unknown', + 'Bad handshake method', + 'Bad request' + ]; + + private const ERROR_UNKNOWN_TRANSPORT = 0; + + private const ERROR_UNKNOWN_SID = 1; + + private const ERROR_BAD_HANDSHAKE_METHOD = 2; + + private const ERROR_BAD_REQUEST = 3; + + public function __construct($opts = []) + { + $ops_map = [ + 'pingTimeout', + 'pingInterval', + 'upgradeTimeout', + 'transports', + 'allowUpgrades', + 'allowRequest' + ]; + + foreach ($ops_map as $key) { + if (isset($opts[$key])) { + $this->$key = $opts[$key]; + } + } + Debug::debug('Engine __construct'); + } + + public function __destruct() + { + Debug::debug('Engine __destruct'); + } + + public function handleRequest(object $req, object $res) + { + $this->prepare($req); + $req->res = $res; + $this->verify($req, $res, false, [$this, 'dealRequest']); + } + + /** + * @throws Exception + */ + public function dealRequest($err, bool $success, object $req) + { + if (! $success) { + self::sendErrorMessage($req, $req->res, $err); + return; + } + + if (isset($req->_query['sid'])) { + $this->clients[$req->_query['sid']]->transport->onRequest($req); + } else { + $this->handshake($req->_query['transport'], $req); + } + } + + protected function sendErrorMessage(object $req, object $res, string $code): void + { + $headers = ['Content-Type' => 'application/json']; + if (isset($req->headers['origin'])) { + $headers['Access-Control-Allow-Credentials'] = 'true'; + $headers['Access-Control-Allow-Origin'] = $req->headers['origin']; + } else { + $headers['Access-Control-Allow-Origin'] = '*'; + } + + $res->writeHead(403, '', $headers); + $res->end( + json_encode( + [ + 'code' => $code, + 'message' => self::$errorMessages[$code] ?? $code + ] + ) + ); + } + + protected function verify(object $req, object $res, bool $upgrade, callable $fn) + { + if (! isset($req->_query['transport']) || ! isset(self::$allowTransports[$req->_query['transport']])) { + return call_user_func($fn, self::ERROR_UNKNOWN_TRANSPORT, false, $req, $res); + } + $transport = $req->_query['transport']; + $sid = $req->_query['sid'] ?? ''; + if ($sid) { + if (! isset($this->clients[$sid])) { + return call_user_func($fn, self::ERROR_UNKNOWN_SID, false, $req, $res); + } + if (! $upgrade && $this->clients[$sid]->transport->name !== $transport) { + return call_user_func($fn, self::ERROR_BAD_REQUEST, false, $req, $res); + } + } else { + if ('GET' !== $req->method) { + return call_user_func($fn, self::ERROR_BAD_HANDSHAKE_METHOD, false, $req, $res); + } + return $this->checkRequest($req, $res, $fn); + } + call_user_func($fn, null, true, $req, $res); + } + + public function checkRequest(object $req, object $res, callable $fn) + { + if ($this->origins === "*:*" || empty($this->origins)) { + return call_user_func($fn, null, true, $req, $res); + } + $origin = null; + if (isset($req->headers['origin'])) { + $origin = $req->headers['origin']; + } elseif (isset($req->headers['referer'])) { + $origin = $req->headers['referer']; + } + + // file:// URLs produce a null Origin which can't be authorized via echo-back + if ('null' === $origin || null === $origin) { + return call_user_func($fn, null, true, $req, $res); + } + + if ($origin) { + $parts = parse_url($origin); + $defaultPort = 'https:' === $parts['scheme'] ? 443 : 80; + $parts['port'] = $parts['port'] ?? $defaultPort; + $allowed_origins = explode(' ', $this->origins); + foreach ($allowed_origins as $allow_origin) { + $ok = + $allow_origin === $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'] || + $allow_origin === $parts['scheme'] . '://' . $parts['host'] || + $allow_origin === $parts['scheme'] . '://' . $parts['host'] . ':*' || + $allow_origin === '*:' . $parts['port']; + if ($ok) { + return call_user_func($fn, null, true, $req, $res); + } + } + } + call_user_func($fn, null, false, $req, $res); + } + + protected function prepare(object $req) + { + if (! isset($req->_query)) { + $info = parse_url($req->url); + if (isset($info['query'])) { + parse_str($info['query'], $req->_query); + } + } + } + + /** + * @throws Exception + */ + public function handshake(string $transport, object $req) + { + $id = bin2hex(pack('d', microtime(true)) . pack('N', function_exists('random_int') ? random_int(1, 100000000) : rand(1, 100000000))); + if ($transport == 'websocket') { + $transport = '\\PHPSocketIO\\Engine\\Transports\\WebSocket'; + } elseif (isset($req->_query['j'])) { + $transport = '\\PHPSocketIO\\Engine\\Transports\\PollingJsonp'; + } else { + $transport = '\\PHPSocketIO\\Engine\\Transports\\PollingXHR'; + } + + $transport = new $transport($req); + + $transport->supportsBinary = ! isset($req->_query['b64']); + + $socket = new Socket($id, $this, $transport, $req); + + $transport->onRequest($req); + + $this->clients[$id] = $socket; + $socket->once('close', [$this, 'onSocketClose']); + $this->emit('connection', $socket); + } + + public function onSocketClose($id): void + { + unset($this->clients[$id]); + } + + public function attach($worker): void + { + $this->server = $worker; + $worker->onConnect = [$this, 'onConnect']; + } + + public function onConnect(object $connection): void + { + $connection->onRequest = [$this, 'handleRequest']; + $connection->onWebSocketConnect = [$this, 'onWebSocketConnect']; + // clean + $connection->onClose = function ($connection) { + if (! empty($connection->httpRequest)) { + $connection->httpRequest->destroy(); + $connection->httpRequest = null; + } + if (! empty($connection->httpResponse)) { + $connection->httpResponse->destroy(); + $connection->httpResponse = null; + } + if (! empty($connection->onRequest)) { + $connection->onRequest = null; + } + if (! empty($connection->onWebSocketConnect)) { + $connection->onWebSocketConnect = null; + } + }; + } + + public function onWebSocketConnect($connection, object $req, object $res): void + { + $this->prepare($req); + $this->verify($req, $res, true, [$this, 'dealWebSocketConnect']); + } + + /** + * @throws Exception + */ + public function dealWebSocketConnect($err, bool $success, object $req, object $res): void + { + if (! $success) { + self::sendErrorMessage($req, $res, $err); + return; + } + + if (isset($req->_query['sid'])) { + if (! isset($this->clients[$req->_query['sid']])) { + self::sendErrorMessage($req, $res, 'upgrade attempt for closed client'); + return; + } + $client = $this->clients[$req->_query['sid']]; + if ($client->upgrading) { + self::sendErrorMessage($req, $res, 'transport has already been trying to upgrade'); + return; + } + if ($client->upgraded) { + self::sendErrorMessage($req, $res, 'transport had already been upgraded'); + return; + } + $transport = new WebSocket($req); + $client->maybeUpgrade($transport); + } else { + $this->handshake($req->_query['transport'], $req); + } + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Parser.php b/vendor/workerman/phpsocket.io/src/Engine/Parser.php new file mode 100644 index 0000000..3023eb7 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Parser.php @@ -0,0 +1,253 @@ + 0, // non-ws + 'close' => 1, // non-ws + 'ping' => 2, + 'pong' => 3, + 'message' => 4, + 'upgrade' => 5, + 'noop' => 6, + ]; + + public static $packetsList = [ + 'open', + 'close', + 'ping', + 'pong', + 'message', + 'upgrade', + 'noop' + ]; + + public static $err = [ + 'type' => 'error', + 'data' => 'parser error' + ]; + + public static function encodePacket($packet): string + { + $data = ! isset($packet['data']) ? '' : $packet['data']; + return self::$packets[$packet['type']] . $data; + } + + /** + * Decodes a packet. Data also available as an ArrayBuffer if requested. + * + * @return array|string[] {Object} with `type` and `data` (if any) + */ + public static function decodePacket(string $data): array + { + if ($data[0] === 'b') { + return self::decodeBase64Packet(substr($data, 1)); + } + + $type = $data[0]; + if (! isset(self::$packetsList[$type])) { + return self::$err; + } + + if (isset($data[1])) { + return ['type' => self::$packetsList[$type], 'data' => substr($data, 1)]; + } else { + return ['type' => self::$packetsList[$type]]; + } + } + + /** + * Decodes a packet encoded in a base64 string. + * + * @param $msg + * @return array {Object} with `type` and `data` (if any) + */ + public static function decodeBase64Packet($msg): array + { + $type = self::$packetsList[$msg[0]]; + $data = base64_decode(substr($msg, 1)); + return ['type' => $type, 'data' => $data]; + } + + /** + * Encodes multiple messages (payload). + * + * :data + * + * Example: + * + * 11:hello world2:hi + * + * If any contents are binary, they will be encoded as base64 strings. Base64 + * encoded strings are marked with a b before the length specifier + * + * @param {Array} packets + * @api private + */ + public static function encodePayload($packets, $supportsBinary = null): string + { + if ($supportsBinary) { + return self::encodePayloadAsBinary($packets); + } + + if (! $packets) { + return '0:'; + } + + $results = ''; + foreach ($packets as $msg) { + $results .= self::encodeOne($msg); + } + return $results; + } + + public static function encodeOne($packet): string + { + $message = self::encodePacket($packet); + return strlen($message) . ':' . $message; + } + + /* + * Decodes data when a payload is maybe expected. Possible binary contents are + * decoded from their base64 representation + * + * @api public + */ + public static function decodePayload($data, $binaryType = null) + { + if (! preg_match('/^\d+:\d/', $data)) { + return self::decodePayloadAsBinary($data, $binaryType); + } + + if ($data === '') { + // parser error - ignoring payload + return self::$err; + } + + $length = '';//, n, msg; + + for ($i = 0, $l = strlen($data); $i < $l; $i++) { + $chr = $data[$i]; + + if (':' != $chr) { + $length .= $chr; + } else { + if ('' == $length || ($length != ($n = intval($length)))) { + // parser error - ignoring payload + return self::$err; + } + + $msg = substr($data, $i + 1); + + if (isset($msg[0])) { + $packet = self::decodePacket($msg); + + if (self::$err['type'] == $packet['type'] && self::$err['data'] == $packet['data']) { + // parser error in individual packet - ignoring payload + return self::$err; + } + + return $packet; + } + + // advance cursor + $i += $n; + $length = ''; + } + } + + if ($length !== '') { + // parser error - ignoring payload + echo new Exception('parser error'); + return self::$err; + } + } + + /** + * Encodes multiple messages (payload) as binary. + * + * <1 = binary, 0 = string>[...] + * + * Example: + * 1 3 255 1 2 3, if the binary contents are interpreted as 8-bit integers + * + * @param {Array} packets + * @return string {Buffer} encoded payload + * @api private + */ + public static function encodePayloadAsBinary($packets): string + { + $results = ''; + foreach ($packets as $msg) { + $results .= self::encodeOneAsBinary($msg); + } + return $results; + } + + public static function encodeOneAsBinary($p): string + { + $packet = self::encodePacket($p); + $encodingLength = '' . strlen($packet); + $sizeBuffer = chr(0); + for ($i = 0; $i < strlen($encodingLength); $i++) { + $sizeBuffer .= chr($encodingLength[$i]); + } + $sizeBuffer .= chr(255); + return $sizeBuffer . $packet; + } + + /* + * Decodes data when a payload is maybe expected. Strings are decoded by + * interpreting each byte as a key code for entries marked to start with 0. See + * description of encodePayloadAsBinary + * @api public + */ + public static function decodePayloadAsBinary($data, $binaryType = null): array + { + $bufferTail = $data; + $buffers = []; + + while (strlen($bufferTail) > 0) { + $strLen = ''; + $numberTooLong = false; + for ($i = 1;; $i++) { + $tail = ord($bufferTail[$i]); + if ($tail === 255) { + break; + } + // 310 = char length of Number.MAX_VALUE + if (strlen($strLen) > 310) { + $numberTooLong = true; + break; + } + $strLen .= $tail; + } + if ($numberTooLong) { + return self::$err; + } + $bufferTail = substr($bufferTail, strlen($strLen) + 1); + + $msgLength = intval($strLen); + + $msg = substr($bufferTail, 1, $msgLength + 1); + $buffers[] = $msg; + $bufferTail = substr($bufferTail, $msgLength + 1); + } + $packets = []; + foreach ($buffers as $i => $buffer) { + $packets[] = self::decodePacket($buffer); + } + return $packets; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Request.php b/vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Request.php new file mode 100644 index 0000000..6326eea --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Request.php @@ -0,0 +1,58 @@ +connection = $connection; + $this->parseHead($raw_head); + } + + public function parseHead($raw_head) + { + $header_data = explode("\r\n", $raw_head); + list($this->method, $this->url, $protocol) = explode(' ', $header_data[0]); + list($null, $this->httpVersion) = explode('/', $protocol); + unset($header_data[0]); + foreach ($header_data as $content) { + if (empty($content)) { + continue; + } + $this->rawHeaders[] = $content; + list($key, $value) = explode(':', $content, 2); + $this->headers[strtolower($key)] = trim($value); + } + } + + public function destroy() + { + $this->onData = $this->onEnd = $this->onClose = null; + $this->connection = null; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Response.php b/vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Response.php new file mode 100644 index 0000000..85a1f7f --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Response.php @@ -0,0 +1,189 @@ +_connection = $connection; + } + + protected function initHeader() + { + $this->_headers['Connection'] = 'keep-alive'; + $this->_headers['Content-Type'] = 'Content-Type: text/html;charset=utf-8'; + } + + public function writeHead($status_code, $reason_phrase = '', $headers = null) + { + if ($this->headersSent) { + echo "header has already send\n"; + return false; + } + $this->statusCode = $status_code; + if ($reason_phrase) { + $this->_statusPhrase = $reason_phrase; + } + if ($headers) { + foreach ($headers as $key => $val) { + $this->_headers[$key] = $val; + } + } + $this->_buffer = $this->getHeadBuffer(); + $this->headersSent = true; + } + + public function getHeadBuffer(): string + { + if (! $this->_statusPhrase) { + $this->_statusPhrase = self::$codes[$this->statusCode] ?? ''; + } + $head_buffer = "HTTP/1.1 $this->statusCode $this->_statusPhrase\r\n"; + if (! isset($this->_headers['Content-Length']) && ! isset($this->_headers['Transfer-Encoding'])) { + $head_buffer .= "Transfer-Encoding: chunked\r\n"; + } + if (! isset($this->_headers['Connection'])) { + $head_buffer .= "Connection: keep-alive\r\n"; + } + foreach ($this->_headers as $key => $val) { + if ($key === 'Set-Cookie' && is_array($val)) { + foreach ($val as $v) { + $head_buffer .= "Set-Cookie: $v\r\n"; + } + continue; + } + $head_buffer .= "$key: $val\r\n"; + } + return $head_buffer . "\r\n"; + } + + public function setHeader($key, $val) + { + $this->_headers[$key] = $val; + } + + public function getHeader($name) + { + return $this->_headers[$name] ?? ''; + } + + public function removeHeader($name) + { + unset($this->_headers[$name]); + } + + public function write($chunk) + { + if (! isset($this->_headers['Content-Length'])) { + $chunk = dechex(strlen($chunk)) . "\r\n" . $chunk . "\r\n"; + } + if (! $this->headersSent) { + $head_buffer = $this->getHeadBuffer(); + $this->_buffer = $head_buffer . $chunk; + $this->headersSent = true; + } else { + $this->_buffer .= $chunk; + } + } + + public function end($data = null) + { + if (! $this->writable) { + echo new Exception('unwirtable'); + return false; + } + if ($data !== null) { + $this->write($data); + } + + if (! $this->headersSent) { + $head_buffer = $this->getHeadBuffer(); + $this->_buffer = $head_buffer; + $this->headersSent = true; + } + + if (! isset($this->_headers['Content-Length'])) { + $ret = $this->_connection->send($this->_buffer . "0\r\n\r\n", true); + $this->destroy(); + return $ret; + } + $ret = $this->_connection->send($this->_buffer, true); + $this->destroy(); + return $ret; + } + + public function destroy() + { + if (! empty($this->_connection->httpRequest)) { + $this->_connection->httpRequest->destroy(); + } + if (! empty($this->_connection)) { + $this->_connection->httpResponse = $this->_connection->httpRequest = null; + } + $this->_connection = null; + $this->writable = false; + } + + public static $codes = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ]; +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Protocols/SocketIO.php b/vendor/workerman/phpsocket.io/src/Engine/Protocols/SocketIO.php new file mode 100644 index 0000000..e029840 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Protocols/SocketIO.php @@ -0,0 +1,170 @@ +hasReadedHead)) { + return strlen($http_buffer); + } + $pos = strpos($http_buffer, "\r\n\r\n"); + if (! $pos) { + if (strlen($http_buffer) >= $connection->maxPackageSize) { + $connection->close("HTTP/1.1 400 bad request\r\n\r\nheader too long"); + return 0; + } + return 0; + } + $head_len = $pos + 4; + $raw_head = substr($http_buffer, 0, $head_len); + $raw_body = substr($http_buffer, $head_len); + $req = new Request($connection, $raw_head); + $res = new Response($connection); + $connection->httpRequest = $req; + $connection->httpResponse = $res; + $connection->hasReadedHead = true; + TcpConnection::$statistics['total_request']++; + $connection->onClose = '\PHPSocketIO\Engine\Protocols\SocketIO::emitClose'; + if (isset($req->headers['upgrade']) && strtolower($req->headers['upgrade']) === 'websocket') { + $connection->consumeRecvBuffer(strlen($http_buffer)); + WebSocket::dealHandshake($connection, $req, $res); + self::cleanup($connection); + return 0; + } + if (! empty($connection->onRequest)) { + $connection->consumeRecvBuffer(strlen($http_buffer)); + self::emitRequest($connection, $req, $res); + if ($req->method === 'GET' || $req->method === 'OPTIONS') { + self::emitEnd($connection, $req); + return 0; + } + + // POST + if ('\PHPSocketIO\Engine\Protocols\SocketIO::onData' !== $connection->onMessage) { + $connection->onMessage = '\PHPSocketIO\Engine\Protocols\SocketIO::onData'; + } + if (! $raw_body) { + return 0; + } + self::onData($connection, $raw_body); + return 0; + } else { + if ($req->method === 'GET') { + return $pos + 4; + } elseif (isset($req->headers['content-length'])) { + return $req->headers['content-length']; + } else { + $connection->close("HTTP/1.1 400 bad request\r\n\r\ntrunk not support"); + return 0; + } + } + } + + public static function onData($connection, $data) + { + $req = $connection->httpRequest; + self::emitData($connection, $req, $data); + if ((isset($req->headers['content-length']) && $req->headers['content-length'] <= strlen($data)) + || substr($data, -5) === "0\r\n\r\n" + ) { + self::emitEnd($connection, $req); + } + } + + protected static function emitRequest($connection, $req, $res) + { + try { + call_user_func($connection->onRequest, $req, $res); + } catch (Exception $e) { + echo $e; + } + } + + public static function emitClose($connection) + { + $req = $connection->httpRequest; + if (isset($req->onClose)) { + try { + call_user_func($req->onClose, $req); + } catch (Exception $e) { + echo $e; + } + } + $res = $connection->httpResponse; + if (isset($res->onClose)) { + try { + call_user_func($res->onClose, $res); + } catch (Exception $e) { + echo $e; + } + } + self::cleanup($connection); + } + + public static function cleanup($connection) + { + if (! empty($connection->onRequest)) { + $connection->onRequest = null; + } + if (! empty($connection->onWebSocketConnect)) { + $connection->onWebSocketConnect = null; + } + if (! empty($connection->httpRequest)) { + $connection->httpRequest->destroy(); + $connection->httpRequest = null; + } + if (! empty($connection->httpResponse)) { + $connection->httpResponse->destroy(); + $connection->httpResponse = null; + } + } + + public static function emitData($connection, $req, $data) + { + if (isset($req->onData)) { + try { + call_user_func($req->onData, $req, $data); + } catch (Exception $e) { + echo $e; + } + } + } + + public static function emitEnd($connection, $req) + { + if (isset($req->onEnd)) { + try { + call_user_func($req->onEnd, $req); + } catch (Exception $e) { + echo $e; + } + } + $connection->hasReadedHead = false; + } + + public static function encode($buffer, $connection) + { + if (! isset($connection->onRequest)) { + $connection->httpResponse->setHeader('Content-Length', strlen($buffer)); + return $connection->httpResponse->getHeadBuffer() . $buffer; + } + return $buffer; + } + + public static function decode($http_buffer, $connection) + { + if (isset($connection->onRequest)) { + return $http_buffer; + } else { + list($head, $body) = explode("\r\n\r\n", $http_buffer, 2); + return $body; + } + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket.php b/vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket.php new file mode 100644 index 0000000..1487152 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket.php @@ -0,0 +1,84 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace PHPSocketIO\Engine\Protocols; + +use PHPSocketIO\Engine\Protocols\Http\Request; +use PHPSocketIO\Engine\Protocols\Http\Response; +use PHPSocketIO\Engine\Protocols\WebSocket\RFC6455; +use Workerman\Connection\TcpConnection; + +/** + * WebSocket 协议服务端解包和打包 + */ +class WebSocket +{ + /** + * 最小包头 + * + * @var int + */ + const MIN_HEAD_LEN = 7; + + /** + * 检查包的完整性 + * + * @param string $buffer + */ + public static function input($buffer, $connection) + { + if (strlen($buffer) < self::MIN_HEAD_LEN) { + return 0; + } + // flash policy file + if (0 === strpos($buffer, 'send($policy_xml, true); + $connection->consumeRecvBuffer(strlen($buffer)); + return 0; + } + // http head + $pos = strpos($buffer, "\r\n\r\n"); + if (! $pos) { + if (strlen($buffer) >= TcpConnection::$maxPackageSize) { + $connection->close("HTTP/1.1 400 bad request\r\n\r\nheader too long"); + return 0; + } + return 0; + } + $req = new Request($connection, $buffer); + $res = new Response($connection); + $connection->consumeRecvBuffer(strlen($buffer)); + return self::dealHandshake($connection, $req, $res); + } + + /** + * 处理websocket握手 + * + * @param TcpConnection $connection + * @param $req + * @param $res + * @return int + */ + public static function dealHandshake($connection, $req, $res) + { + if (isset($req->headers['sec-websocket-key1'])) { + $res->writeHead(400); + $res->end("Not support"); + return 0; + } + $connection->protocol = 'PHPSocketIO\Engine\Protocols\WebSocket\RFC6455'; + return RFC6455::dealHandshake($connection, $req, $res); + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket/RFC6455.php b/vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket/RFC6455.php new file mode 100644 index 0000000..f28bead --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket/RFC6455.php @@ -0,0 +1,300 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace PHPSocketIO\Engine\Protocols\WebSocket; + +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\ProtocolInterface; + +/** + * WebSocket 协议服务端解包和打包 + */ +class RFC6455 implements ProtocolInterface +{ + /** + * websocket头部最小长度 + * + * @var int + */ + const MIN_HEAD_LEN = 6; + + /** + * websocket blob类型 + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * websocket arraybuffer类型 + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * 检查包的完整性 + * + * @param string $buffer + */ + public static function input($buffer, ConnectionInterface $connection) + { + // 数据长度 + $recv_len = strlen($buffer); + // 长度不够 + if ($recv_len < self::MIN_HEAD_LEN) { + return 0; + } + + // $connection->websocketCurrentFrameLength有值说明当前fin为0,则缓冲websocket帧数据 + if ($connection->websocketCurrentFrameLength) { + // 如果当前帧数据未收全,则继续收 + if ($connection->websocketCurrentFrameLength > $recv_len) { + // 返回0,因为不清楚完整的数据包长度,需要等待fin=1的帧 + return 0; + } + } else { + $data_len = ord($buffer[1]) & 127; + $firstbyte = ord($buffer[0]); + $is_fin_frame = $firstbyte >> 7; + $opcode = $firstbyte & 0xf; + switch ($opcode) { + // 附加数据帧 @todo 实现附加数据帧 + case 0x1: + case 0x2: + case 0x0: + break; + // 文本数据帧 + // 二进制数据帧 + // 关闭的包 + case 0x8: + // 如果有设置onWebSocketClose回调,尝试执行 + if (isset($connection->onWebSocketClose)) { + call_user_func($connection->onWebSocketClose, $connection); + } // 默认行为是关闭连接 + else { + $connection->close(); + } + return 0; + // ping的包 + case 0x9: + // 如果有设置onWebSocketPing回调,尝试执行 + if (isset($connection->onWebSocketPing)) { + call_user_func($connection->onWebSocketPing, $connection); + } // 默认发送pong + else { + $connection->send(pack('H*', '8a00'), true); + } + // 从接受缓冲区中消费掉该数据包 + if (! $data_len) { + $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); + return 0; + } + break; + // pong的包 + case 0xa: + // 如果有设置onWebSocketPong回调,尝试执行 + if (isset($connection->onWebSocketPong)) { + call_user_func($connection->onWebSocketPong, $connection); + } + // 从接受缓冲区中消费掉该数据包 + if (! $data_len) { + $connection->consumeRecvBuffer(self::MIN_HEAD_LEN); + return 0; + } + break; + // 错误的opcode + default: + echo "error opcode $opcode and close websocket connection\n"; + $connection->close(); + return 0; + } + + // websocket二进制数据 + $head_len = self::MIN_HEAD_LEN; + if ($data_len === 126) { + $head_len = 8; + if ($head_len > $recv_len) { + return 0; + } + $pack = unpack('ntotal_len', substr($buffer, 2, 2)); + $data_len = $pack['total_len']; + } elseif ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = unpack('N2', substr($buffer, 2, 8)); + $data_len = $arr[1] * 4294967296 + $arr[2]; + } + $current_frame_length = $head_len + $data_len; + if ($is_fin_frame) { + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + + // 收到的数据刚好是一个frame + if ($connection->websocketCurrentFrameLength == $recv_len) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // 收到的数据大于一个frame + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // 继续读取下一个frame + return self::input(substr($buffer, $current_frame_length), $connection); + } // 收到的数据不足一个frame + else { + return 0; + } + } + + /** + * 打包 + * + * @param string $buffer + * @return string + */ + public static function encode($buffer, ConnectionInterface $connection) + { + $len = strlen($buffer); + if (empty($connection->websocketHandshake)) { + // 默认是utf8文本格式 + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + + $first_byte = $connection->websocketType; + + if ($len <= 125) { + $encode_buffer = $first_byte . chr($len) . $buffer; + } elseif ($len <= 65535) { + $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; + } + + // 还没握手不能发数据,先将数据缓冲起来,等握手完毕后发送 + if (empty($connection->websocketHandshake)) { + if (empty($connection->websocketTmpData)) { + // 临时数据缓冲 + $connection->websocketTmpData = ''; + } + $connection->websocketTmpData .= $encode_buffer; + // 返回空,阻止发送 + return ''; + } + + return $encode_buffer; + } + + /** + * 解包 + * + * @param string $buffer + * @return string + */ + public static function decode($buffer, ConnectionInterface $connection) + { + $masks = $data = $decoded = null; + $len = ord($buffer[1]) & 127; + if ($len === 126) { + $masks = substr($buffer, 4, 4); + $data = substr($buffer, 8); + } elseif ($len === 127) { + $masks = substr($buffer, 10, 4); + $data = substr($buffer, 14); + } else { + $masks = substr($buffer, 2, 4); + $data = substr($buffer, 6); + } + for ($index = 0; $index < strlen($data); $index++) { + $decoded .= $data[$index] ^ $masks[$index % 4]; + } + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded; + return $connection->websocketDataBuffer; + } else { + $decoded = $connection->websocketDataBuffer . $decoded; + $connection->websocketDataBuffer = ''; + return $decoded; + } + } + + /** + * 处理websocket握手 + * + * @param TcpConnection $connection + * @param $req + * @param $res + * @return int + */ + public static function dealHandshake($connection, $req, $res) + { + $headers = []; + if (isset($connection->onWebSocketConnect)) { + try { + call_user_func_array($connection->onWebSocketConnect, [$connection, $req, $res]); + } catch (\Exception $e) { + echo $e; + } + if (! $res->writable) { + return false; + } + } + + if (isset($req->headers['sec-websocket-key'])) { + $sec_websocket_key = $req->headers['sec-websocket-key']; + } else { + $res->writeHead(400); + $res->end('400 Bad Request
Upgrade to websocket but Sec-WebSocket-Key not found.'); + return 0; + } + + // 标记已经握手 + $connection->websocketHandshake = true; + // 缓冲fin为0的包,直到fin为1 + $connection->websocketDataBuffer = ''; + // 当前数据帧的长度,可能是fin为0的帧,也可能是fin为1的帧 + $connection->websocketCurrentFrameLength = 0; + // 当前帧的数据缓冲 + $connection->websocketCurrentFrameBuffer = ''; + // blob or arraybuffer + $connection->websocketType = self::BINARY_TYPE_BLOB; + + $sec_websocket_accept = base64_encode(sha1($sec_websocket_key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); + $headers['Content-Length'] = 0; + $headers['Upgrade'] = 'websocket'; + $headers['Sec-WebSocket-Version'] = 13; + $headers['Connection'] = 'Upgrade'; + $headers['Sec-WebSocket-Accept'] = $sec_websocket_accept; + $res->writeHead(101, '', $headers); + $res->end(); + + // 握手后有数据要发送 + if (! empty($connection->websocketTmpData)) { + $connection->send($connection->websocketTmpData, true); + $connection->websocketTmpData = ''; + } + + return 0; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Socket.php b/vendor/workerman/phpsocket.io/src/Engine/Socket.php new file mode 100644 index 0000000..760d8b1 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Socket.php @@ -0,0 +1,360 @@ +id = $id; + $this->server = $server; + $this->request = $req; + $this->remoteAddress = $req->connection->getRemoteIp() . ':' . $req->connection->getRemotePort(); + $this->setTransport($transport); + $this->onOpen(); + Debug::debug('Engine/Socket __construct'); + } + + public function __destruct() + { + Debug::debug('Engine/Socket __destruct'); + } + + public function maybeUpgrade(object $transport): void + { + $this->upgrading = true; + $this->upgradeTimeoutTimer = Timer::add( + $this->server->upgradeTimeout, + [$this, 'upgradeTimeoutCallback'], + [$transport], + false + ); + $this->upgradeTransport = $transport; + $transport->on('packet', [$this, 'onUpgradePacket']); + $transport->once('close', [$this, 'onUpgradeTransportClose']); + $transport->once('error', [$this, 'onUpgradeTransportError']); + $this->once('close', [$this, 'onUpgradeTransportClose']); + } + + public function onUpgradePacket(array $packet): void + { + if (empty($this->upgradeTransport)) { + $this->onError('upgradeTransport empty'); + return; + } + if ('ping' === $packet['type'] && (isset($packet['data']) && 'probe' === $packet['data'])) { + $this->upgradeTransport->send([['type' => 'pong', 'data' => 'probe']]); + if ($this->checkIntervalTimer) { + Timer::del($this->checkIntervalTimer); + } + $this->checkIntervalTimer = Timer::add(0.5, [$this, 'check']); + } elseif ('upgrade' === $packet['type'] && $this->readyState !== 'closed') { + $this->upgradeCleanup(); + $this->upgraded = true; + $this->clearTransport(); + $this->transport->destroy(); + $this->setTransport($this->upgradeTransport); + $this->emit('upgrade', $this->upgradeTransport); + $this->upgradeTransport = null; + $this->setPingTimeout(); + $this->flush(); + if ($this->readyState === 'closing') { + $this->transport->close([$this, 'onClose']); + } + } else { + $this->upgradeCleanup(); + $this->upgradeTransport->close(); + $this->upgradeTransport = null; + } + } + + public function upgradeCleanup(): void + { + $this->upgrading = false; + Timer::del($this->checkIntervalTimer); + Timer::del($this->upgradeTimeoutTimer); + if (! empty($this->upgradeTransport)) { + $this->upgradeTransport->removeListener('packet', [$this, 'onUpgradePacket']); + $this->upgradeTransport->removeListener('close', [$this, 'onUpgradeTransportClose']); + $this->upgradeTransport->removeListener('error', [$this, 'onUpgradeTransportError']); + } + $this->removeListener('close', [$this, 'onUpgradeTransportClose']); + } + + public function onUpgradeTransportClose(): void + { + $this->onUpgradeTransportError('transport closed'); + } + + public function onUpgradeTransportError($err): void + { + $this->upgradeCleanup(); + if ($this->upgradeTransport) { + $this->upgradeTransport->close(); + $this->upgradeTransport = null; + } + } + + public function upgradeTimeoutCallback(object $transport): void + { + $this->upgradeCleanup(); + if ('open' === $transport->readyState) { + $transport->close(); + } + } + + public function setTransport(object $transport) + { + $this->transport = $transport; + $this->transport->once('error', [$this, 'onError']); + $this->transport->on('packet', [$this, 'onPacket']); + $this->transport->on('drain', [$this, 'flush']); + $this->transport->once('close', [$this, 'onClose']); + //this function will manage packet events (also message callbacks) + $this->setupSendCallback(); + } + + public function onOpen(): void + { + $this->readyState = 'open'; + + $this->transport->sid = $this->id; + $this->sendPacket( + 'open', + json_encode( + [ + 'sid' => $this->id, + 'upgrades' => $this->getAvailableUpgrades(), + 'pingInterval' => $this->server->pingInterval * 1000, + 'pingTimeout' => $this->server->pingTimeout * 1000 + ] + ) + ); + + $this->emit('open'); + $this->setPingTimeout(); + } + + public function onPacket(array $packet) + { + if ('open' === $this->readyState) { + // export packet event + $this->emit('packet', $packet); + + // Reset ping timeout on any packet, incoming data is a good sign of + // other side's liveness + $this->setPingTimeout(); + switch ($packet['type']) { + case 'ping': + $this->sendPacket('pong'); + $this->emit('heartbeat'); + break; + case 'error': + $this->onClose('parse error'); + break; + case 'message': + $this->emit('data', $packet['data']); + $this->emit('message', $packet['data']); + break; + } + } else { + echo('packet received with closed socket'); + } + } + + public function check(): void + { + if ('polling' == $this->transport->name && $this->transport->writable) { + $this->transport->send([['type' => 'noop']]); + } + } + + public function onError($err): void + { + $this->onClose('transport error', $err); + } + + public function setPingTimeout(): void + { + if ($this->pingTimeoutTimer) { + Timer::del($this->pingTimeoutTimer); + } + $this->pingTimeoutTimer = Timer::add( + $this->server->pingInterval + $this->server->pingTimeout, + [$this, 'pingTimeoutCallback'], + null, + false + ); + } + + public function pingTimeoutCallback(): void + { + $this->transport->close(); + $this->onClose('ping timeout'); + } + + public function clearTransport(): void + { + $this->transport->close(); + Timer::del($this->pingTimeoutTimer); + } + + public function onClose(string $reason = '', ?string $description = null): void + { + if ('closed' !== $this->readyState) { + Timer::del($this->pingTimeoutTimer); + + if (! empty($this->checkIntervalTimer)) { + Timer::del($this->checkIntervalTimer); + } + + $this->checkIntervalTimer = null; + + if (! empty($this->checkIntervalTimer)) { + Timer::del($this->upgradeTimeoutTimer); + } + + // clean writeBuffer in next tick, so developers can still + // grab the writeBuffer on 'close' event + $this->writeBuffer = []; + $this->packetsFn = []; + $this->sentCallbackFn = []; + $this->clearTransport(); + $this->readyState = 'closed'; + $this->emit('close', $this->id, $reason, $description); + $this->server = null; + $this->request = null; + $this->upgradeTransport = null; + $this->removeAllListeners(); + if (! empty($this->transport)) { + $this->transport->removeAllListeners(); + $this->transport = null; + } + } + } + + public function send($data, $options, ?callable $callback): Socket + { + $this->sendPacket('message', $data, $callback); + return $this; + } + + public function write($data, ?array $options = [], ?callable $callback = null): Socket + { + return $this->send($data, $options, $callback); + } + + public function sendPacket(string $type, $data = null, $callback = null): void + { + if ('closing' !== $this->readyState) { + $packet = [ + 'type' => $type + ]; + if ($data !== null) { + $packet['data'] = $data; + } + // exports packetCreate event + $this->emit('packetCreate', $packet); + $this->writeBuffer[] = $packet; + //add send callback to object + if ($callback) { + $this->packetsFn[] = $callback; + } + $this->flush(); + } + } + + public function flush(): void + { + if ('closed' !== $this->readyState && $this->transport->writable + && $this->writeBuffer + ) { + $this->emit('flush', $this->writeBuffer); + $this->server->emit('flush', $this, $this->writeBuffer); + $wbuf = $this->writeBuffer; + $this->writeBuffer = []; + if ($this->packetsFn) { + if (! empty($this->transport->supportsFraming)) { + $this->sentCallbackFn[] = $this->packetsFn; + } else { + // @todo check + $this->sentCallbackFn[] = $this->packetsFn; + } + } + $this->packetsFn = []; + $this->transport->send($wbuf); + $this->emit('drain'); + if ($this->server) { + $this->server->emit('drain', $this); + } + } + } + + public function getAvailableUpgrades(): array + { + return ['websocket']; + } + + public function close(): void + { + if ('open' !== $this->readyState) { + return; + } + + $this->readyState = 'closing'; + + if ($this->writeBuffer) { + $this->once('drain', [$this, 'closeTransport']); + return; + } + + $this->closeTransport(); + } + + public function closeTransport(): void + { + $this->transport->close([$this, 'onClose']); + } + + public function setupSendCallback(): void + { + //the message was sent successfully, execute the callback + $this->transport->on('drain', [$this, 'onDrainCallback']); + } + + public function onDrainCallback(): void + { + if ($this->sentCallbackFn) { + $seqFn = array_shift($this->sentCallbackFn); + if (is_callable($seqFn)) { + echo('executing send callback'); + call_user_func($seqFn, $this->transport); + } elseif (is_array($seqFn)) { + echo('executing batch send callback'); + foreach ($seqFn as $fn) { + call_user_func($fn, $this->transport); + } + } + } + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Transport.php b/vendor/workerman/phpsocket.io/src/Engine/Transport.php new file mode 100644 index 0000000..09f07a5 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Transport.php @@ -0,0 +1,80 @@ +req = $req; + } + + public function close(?callable $fn = null): void + { + $this->readyState = 'closing'; + $fn = $fn ?: [$this, 'noop']; + $this->doClose($fn); + } + + public function onError(string $msg, string $desc = '') + { + if ($this->listeners('error')) { + $err = [ + 'type' => 'TransportError', + 'description' => $desc, + ]; + $this->emit('error', $err); + } else { + echo("ignored transport error $msg $desc\n"); + } + } + + public function onPacket($packet): void + { + $this->emit('packet', $packet); + } + + public function onData($data) + { + $this->onPacket(Parser::decodePacket($data)); + } + + public function onClose() + { + $this->req = $this->res = null; + $this->readyState = 'closed'; + $this->emit('close'); + $this->removeAllListeners(); + } + + public function destroy(): void + { + $this->req = null; + $this->res = null; + $this->readyState = 'closed'; + $this->removeAllListeners(); + $this->shouldClose = null; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Transports/Polling.php b/vendor/workerman/phpsocket.io/src/Engine/Transports/Polling.php new file mode 100644 index 0000000..bb8a50b --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Transports/Polling.php @@ -0,0 +1,176 @@ +res; + + if ('GET' === $req->method) { + $this->onPollRequest($req, $res); + } elseif ('POST' === $req->method) { + $this->onDataRequest($req, $res); + } else { + $res->writeHead(500); + $res->end(); + } + } + + public function onPollRequest(object $req, object $res): void + { + if ($this->req) { + $this->onError('overlap from client'); + $res->writeHead(500); + return; + } + + $this->req = $req; + $this->res = $res; + + $req->onClose = [$this, 'pollRequestOnClose']; + $req->cleanup = [$this, 'pollRequestClean']; + + $this->writable = true; + $this->emit('drain'); + + if ($this->writable && $this->shouldClose) { + echo('triggering empty send to append close packet'); + $this->send([['type' => 'noop']]); + } + } + + public function pollRequestOnClose(): void + { + $this->onError('poll connection closed prematurely'); + $this->pollRequestClean(); + } + + public function pollRequestClean(): void + { + if (isset($this->req)) { + $this->req = null; + $this->res = null; + } + } + + public function onDataRequest($req, $res): void + { + if (isset($this->dataReq)) { + $this->onError('data request overlap from client'); + $res->writeHead(500); + return; + } + + $this->dataReq = $req; + $this->dataRes = $res; + $req->onClose = [$this, 'dataRequestOnClose']; + $req->onData = [$this, 'dataRequestOnData']; + $req->onEnd = [$this, 'dataRequestOnEnd']; + } + + public function dataRequestCleanup(): void + { + $this->chunks = ''; + $this->dataReq = null; + $this->dataRes = null; + } + + public function dataRequestOnClose(): void + { + $this->dataRequestCleanup(); + $this->onError('data request connection closed prematurely'); + } + + public function dataRequestOnData($req, $data): void + { + $this->chunks .= $data; + } + + public function dataRequestOnEnd(): void + { + $this->onData($this->chunks); + + $headers = [ + 'Content-Type' => 'text/html', + 'Content-Length' => 2, + 'X-XSS-Protection' => '0', + ]; + + $this->dataRes->writeHead(200, '', $this->headers($this->dataReq, $headers)); + $this->dataRes->end('ok'); + $this->dataRequestCleanup(); + } + + public function onData($data) + { + $packets = Parser::decodePayload($data); + if (isset($packets['type'])) { + if ('close' === $packets['type']) { + $this->onClose(); + return false; + } else { + $packets = [$packets]; + } + } + + foreach ($packets as $packet) { + $this->onPacket($packet); + } + } + + public function onClose() + { + if ($this->writable) { + $this->send([['type' => 'noop']]); + } + parent::onClose(); + } + + public function send($packets): void + { + $this->writable = false; + if ($this->shouldClose) { + echo('appending close packet to payload'); + $packets[] = ['type' => 'close']; + call_user_func($this->shouldClose); + $this->shouldClose = null; + } + $data = Parser::encodePayload($packets, $this->supportsBinary); + $this->write($data); + } + + public function write($data): void + { + $this->doWrite($data); + if (! empty($this->req->cleanup)) { + call_user_func($this->req->cleanup); + } + } + + public function doClose(callable $fn): void + { + if (! empty($this->dataReq)) { + $this->dataReq->destroy(); + } + + if ($this->writable) { + $this->send([['type' => 'close']]); + call_user_func($fn); + } else { + $this->shouldClose = $fn; + } + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Transports/PollingJsonp.php b/vendor/workerman/phpsocket.io/src/Engine/Transports/PollingJsonp.php new file mode 100644 index 0000000..86df54d --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Transports/PollingJsonp.php @@ -0,0 +1,60 @@ +head = '___eio[' . (isset($req['_query']['j']) ? preg_replace('/[^0-9]/', '', $req['_query']['j']) : '') . ']('; + Debug::debug('PollingJsonp __construct'); + } + + public function __destruct() + { + Debug::debug('PollingJsonp __destruct'); + } + + public function onData($data) + { + $parsed_data = null; + parse_str($data, $parsed_data); + $data = $parsed_data['d']; + call_user_func(array(get_parent_class($this), 'onData'), preg_replace('/\\\\n/', '\\n', $data)); + } + + public function doWrite($data): void + { + $js = json_encode($data); + + $data = $this->head . $js . $this->foot; + + // explicit UTF-8 is required for pages not served under utf + $headers = [ + 'Content-Type' => 'text/javascript; charset=UTF-8', + 'Content-Length' => strlen($data), + 'X-XSS-Protection' => '0' + ]; + if (empty($this->res)) { + echo new Exception('empty $this->res'); + return; + } + $this->res->writeHead(200, '', $this->headers($headers)); + $this->res->end($data); + } + + public function headers(array $headers = []): array + { + $listeners = $this->listeners('headers'); + foreach ($listeners as $listener) { + $listener($headers); + } + return $headers; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Transports/PollingXHR.php b/vendor/workerman/phpsocket.io/src/Engine/Transports/PollingXHR.php new file mode 100644 index 0000000..c43f90f --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Transports/PollingXHR.php @@ -0,0 +1,66 @@ +method) { + $res = $req->res; + $headers = $this->headers($req); + $headers['Access-Control-Allow-Headers'] = 'Content-Type'; + $res->writeHead(200, '', $headers); + $res->end(); + } else { + parent::onRequest($req); + } + } + + public function doWrite($data) + { + // explicit UTF-8 is required for pages not served under utf todo + $content_type = preg_match('/^\d+:/', $data) ? 'text/plain; charset=UTF-8' : 'application/octet-stream'; + $content_length = strlen($data); + $headers = [ + 'Content-Type' => $content_type, + 'Content-Length' => $content_length, + 'X-XSS-Protection' => '0', + ]; + if (empty($this->res)) { + echo new \Exception('empty this->res'); + return; + } + $this->res->writeHead(200, '', $this->headers($this->req, $headers)); + $this->res->end($data); + } + + public function headers(object $req, array $headers = []): array + { + if (isset($req->headers['origin'])) { + $headers['Access-Control-Allow-Credentials'] = 'true'; + $headers['Access-Control-Allow-Origin'] = $req->headers['origin']; + } else { + $headers['Access-Control-Allow-Origin'] = '*'; + } + $listeners = $this->listeners('headers'); + foreach ($listeners as $listener) { + $listener($headers); + } + return $headers; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Engine/Transports/WebSocket.php b/vendor/workerman/phpsocket.io/src/Engine/Transports/WebSocket.php new file mode 100644 index 0000000..97b9a0b --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Engine/Transports/WebSocket.php @@ -0,0 +1,64 @@ +socket = $req->connection; + $this->socket->onMessage = [$this, 'onData2']; + $this->socket->onClose = [$this, 'onClose']; + $this->socket->onError = [$this, 'onError2']; + Debug::debug('WebSocket __construct'); + } + + public function __destruct() + { + Debug::debug('WebSocket __destruct'); + } + + public function onData2($connection, $data): void + { + call_user_func(array(get_parent_class($this), 'onData'), $data); + + } + + public function onError2($conection, $code, $msg): void + { + call_user_func(array(get_parent_class($this), 'onData'), $code, $msg); + } + + public function send(array $packets): void + { + foreach ($packets as $packet) { + $data = Parser::encodePacket($packet); + if ($this->socket) { + $this->socket->send($data); + $this->emit('drain'); + } + } + } + + public function doClose(callable $fn = null): void + { + if ($this->socket) { + $this->socket->close(); + $this->socket = null; + if (! empty($fn)) { + call_user_func($fn); + } + } + } +} diff --git a/vendor/workerman/phpsocket.io/src/Event/Emitter.php b/vendor/workerman/phpsocket.io/src/Event/Emitter.php new file mode 100644 index 0000000..15c77ff --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Event/Emitter.php @@ -0,0 +1,96 @@ +[[listener1, once?], [listener2,once?], ..], ..] + */ + protected $_eventListenerMap = []; + + public function on($event_name, $listener): Emitter + { + $this->emit('newListener', $event_name, $listener); + $this->_eventListenerMap[$event_name][] = [$listener, 0]; + return $this; + } + + public function once($event_name, $listener): Emitter + { + $this->_eventListenerMap[$event_name][] = [$listener, 1]; + return $this; + } + + public function removeListener($event_name, $listener): Emitter + { + if (! isset($this->_eventListenerMap[$event_name])) { + return $this; + } + foreach ($this->_eventListenerMap[$event_name] as $key => $item) { + if ($item[0] === $listener) { + $this->emit('removeListener', $event_name, $listener); + unset($this->_eventListenerMap[$event_name][$key]); + } + } + if (empty($this->_eventListenerMap[$event_name])) { + unset($this->_eventListenerMap[$event_name]); + } + return $this; + } + + public function removeAllListeners($event_name = null): Emitter + { + $this->emit('removeListener', $event_name); + if (null === $event_name) { + $this->_eventListenerMap = []; + return $this; + } + unset($this->_eventListenerMap[$event_name]); + return $this; + } + + public function listeners($event_name): array + { + if (empty($this->_eventListenerMap[$event_name])) { + return []; + } + $listeners = []; + foreach ($this->_eventListenerMap[$event_name] as $item) { + $listeners[] = $item[0]; + } + return $listeners; + } + + public function emit($event_name = null) + { + if (empty($event_name) || empty($this->_eventListenerMap[$event_name])) { + return false; + } + foreach ($this->_eventListenerMap[$event_name] as $key => $item) { + $args = func_get_args(); + unset($args[0]); + call_user_func_array($item[0], $args); + // once ? + if ($item[1]) { + unset($this->_eventListenerMap[$event_name][$key]); + if (empty($this->_eventListenerMap[$event_name])) { + unset($this->_eventListenerMap[$event_name]); + } + } + } + return true; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Nsp.php b/vendor/workerman/phpsocket.io/src/Nsp.php new file mode 100644 index 0000000..bfd2e42 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Nsp.php @@ -0,0 +1,158 @@ + 'connect', // for symmetry with client + 'connection' => 'connection', + 'newListener' => 'newListener' + ]; + + public function __construct($server, $name) + { + $this->name = $name; + $this->server = $server; + $this->initAdapter(); + Debug::debug('Nsp __construct'); + } + + public function __destruct() + { + Debug::debug('Nsp __destruct'); + } + + public function initAdapter() + { + $adapter_name = $this->server->adapter(); + $this->adapter = new $adapter_name($this); + } + + public function to($name): Nsp + { + if (! isset($this->rooms[$name])) { + $this->rooms[$name] = $name; + } + return $this; + } + + public function in($name): Nsp + { + return $this->to($name); + } + + public function add($client, $nsp, $fn) + { + $socket_name = $this->server->socket(); + $socket = new $socket_name($this, $client); + if ('open' === $client->conn->readyState) { + $this->sockets[$socket->id] = $socket; + $socket->onconnect(); + if (! empty($fn)) { + call_user_func($fn, $socket, $nsp); + } + $this->emit('connect', $socket); + $this->emit('connection', $socket); + } else { + echo('next called after client was closed - ignoring socket'); + } + } + + /** + * Removes a client. Called by each `Socket`. + * + * @api private + */ + public function remove($socket) + { + // todo $socket->id + unset($this->sockets[$socket->id]); + } + + + /** + * Emits to all clients. + * + * @param null $ev + * @return Nsp|void {Namespace} self + * @api public + */ + public function emit($ev = null) + { + $args = func_get_args(); + if (isset(self::$events[$ev])) { + call_user_func_array([get_parent_class(__CLASS__), 'emit'], $args); + } else { + // set up packet object + + $parserType = Parser::EVENT; // default + //if (self::hasBin($args)) { $parserType = Parser::BINARY_EVENT; } // binary + + $packet = ['type' => $parserType, 'data' => $args]; + + if (is_callable(end($args))) { + echo('Callbacks are not supported when broadcasting'); + return; + } + + $this->adapter->broadcast( + $packet, + [ + 'rooms' => $this->rooms, + 'flags' => $this->flags + ] + ); + + $this->rooms = []; + $this->flags = []; + } + return $this; + } + + public function send(): Nsp + { + $args = func_get_args(); + array_unshift($args, 'message'); + $this->emit($args); + return $this; + } + + public function write() + { + $args = func_get_args(); + return call_user_func_array([$this, 'send'], $args); + } + + public function clients($fn): Nsp + { + $this->adapter->clients($this->rooms, $fn); + return $this; + } + + /** + * Sets the compress flag. + * + * @param {Boolean} if `true`, compresses the sending data + * @return Nsp {Socket} self + * @api public + */ + public function compress($compress): Nsp + { + $this->flags['compress'] = $compress; + return $this; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Parser/Decoder.php b/vendor/workerman/phpsocket.io/src/Parser/Decoder.php new file mode 100644 index 0000000..5084fcf --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Parser/Decoder.php @@ -0,0 +1,113 @@ +emit('decoded', $packet); + } + } + + /** + * @throws Exception + */ + public function decodeString($str): array + { + $p = []; + $i = 0; + + // look up type + $p['type'] = $str[0]; + if (! isset(Parser::$types[$p['type']])) { + return self::error(); + } + + // look up attachments if type binary + if (Parser::BINARY_EVENT == $p['type'] || Parser::BINARY_ACK == $p['type']) { + $buf = ''; + while ($str[++$i] != '-') { + $buf .= $str[$i]; + if ($i == strlen($str)) { + break; + } + } + if ($buf != intval($buf) || $str[$i] != '-') { + throw new Exception('Illegal attachments'); + } + $p['attachments'] = intval($buf); + } + + // look up namespace (if any) + if (isset($str[$i + 1]) && '/' === $str[$i + 1]) { + $p['nsp'] = ''; + while (++$i) { + if ($i === strlen($str)) { + break; + } + $c = $str[$i]; + if (',' === $c) { + break; + } + $p['nsp'] .= $c; + } + } else { + $p['nsp'] = '/'; + } + + // look up id + if (isset($str[$i + 1])) { + $next = $str[$i + 1]; + if ('' !== $next && strval((int)$next) === strval($next)) { + $p['id'] = ''; + while (++$i) { + $c = $str[$i]; + if (null == $c || strval((int)$c) != strval($c)) { + --$i; + break; + } + $p['id'] .= $str[$i]; + if ($i == strlen($str)) { + break; + } + } + $p['id'] = (int)$p['id']; + } + } + + // look up json data + if (isset($str[++$i])) { + $p['data'] = json_decode(substr($str, $i), true); + } + + return $p; + } + + public static function error(): array + { + return [ + 'type' => Parser::ERROR, + 'data' => 'parser error' + ]; + } +} diff --git a/vendor/workerman/phpsocket.io/src/Parser/Encoder.php b/vendor/workerman/phpsocket.io/src/Parser/Encoder.php new file mode 100644 index 0000000..eb5ec5f --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/Parser/Encoder.php @@ -0,0 +1,72 @@ + 'error', + 'connect' => 'connect', + 'disconnect' => 'disconnect', + 'newListener' => 'newListener', + 'removeListener' => 'removeListener' + ]; + + public static $flagsMap = [ + 'json' => 'json', + 'volatile' => 'volatile', + 'broadcast' => 'broadcast' + ]; + + public function __construct($nsp, $client) + { + $this->nsp = $nsp; + $this->server = $nsp->server; + $this->adapter = $this->nsp->adapter; + $this->id = ($nsp->name !== '/') ? $nsp->name . '#' . $client->id : $client->id; + $this->request = $client->request; + $this->client = $client; + $this->conn = $client->conn; + $this->handshake = $this->buildHandshake(); + Debug::debug('IO Socket __construct'); + } + + public function __destruct() + { + Debug::debug('IO Socket __destruct'); + } + + public function buildHandshake(): array + { + //todo check this->request->_query + $info = ! empty($this->request->url) ? parse_url($this->request->url) : []; + $query = []; + if (isset($info['query'])) { + parse_str($info['query'], $query); + } + return [ + 'headers' => $this->request->headers ?? [], + 'time' => date('D M d Y H:i:s') . ' GMT', + 'address' => $this->conn->remoteAddress, + 'xdomain' => isset($this->request->headers['origin']), + 'secure' => ! empty($this->request->connection->encrypted), + 'issued' => time(), + 'url' => $this->request->url ?? '', + 'query' => $query, + ]; + } + + public function __get($name) + { + if ($name === 'broadcast') { + $this->flags['broadcast'] = true; + return $this; + } + return null; + } + + /** + * @throws Exception + */ + public function emit($ev = null) + { + $args = func_get_args(); + if (isset(self::$events[$ev])) { + call_user_func_array(array(get_parent_class(__CLASS__), 'emit'), $args); + } else { + $packet = []; + $packet['type'] = Parser::EVENT; + $packet['data'] = $args; + $flags = $this->flags; + // access last argument to see if it's an ACK callback + if (is_callable(end($args))) { + if ($this->_rooms || isset($flags['broadcast'])) { + throw new Exception('Callbacks are not supported when broadcasting'); + } + echo('emitting packet with ack id ' . $this->nsp->ids); + $this->acks[$this->nsp->ids] = array_pop($args); + $packet['id'] = $this->nsp->ids++; + } + + if ($this->_rooms || ! empty($flags['broadcast'])) { + $this->adapter->broadcast( + $packet, + [ + 'except' => [$this->id => $this->id], + 'rooms' => $this->_rooms, + 'flags' => $flags + ] + ); + } else { + // dispatch packet + $this->packet($packet); + } + + // reset flags + $this->_rooms = []; + $this->flags = []; + } + return $this; + } + + + /** + * Targets a room when broadcasting. + * + * @param {String} name + * @return Socket {Socket} self + * @api public + */ + public function to($name): Socket + { + if (! isset($this->_rooms[$name])) { + $this->_rooms[$name] = $name; + } + return $this; + } + + public function in($name): Socket + { + return $this->to($name); + } + + /** + * Sends a `message` event. + * + * @return Socket {Socket} self + * @api public + */ + public function send(): Socket + { + $args = func_get_args(); + array_unshift($args, 'message'); + call_user_func_array([$this, 'emit'], $args); + return $this; + } + + public function write(): Socket + { + $args = func_get_args(); + array_unshift($args, 'message'); + call_user_func_array([$this, 'emit'], $args); + return $this; + } + + /** + * Writes a packet. + * + * @param {Object} packet object + * @param {Object} options + * @api private + */ + public function packet($packet, $preEncoded = false) + { + if (! $this->nsp || ! $this->client) { + return; + } + $packet['nsp'] = $this->nsp->name; + $this->client->packet($packet, $preEncoded, false); + } + + /** + * Joins a room. + * + * @param {String} room + * @return Socket {Socket} self + * @api private + */ + public function join($room): Socket + { + if (! $this->connected) { + return $this; + } + if (isset($this->rooms[$room])) { + return $this; + } + $this->adapter->add($this->id, $room); + $this->rooms[$room] = $room; + return $this; + } + + /** + * Leaves a room. + * + * @param {String} room + * @return Socket {Socket} self + * @api private + */ + public function leave($room): Socket + { + $this->adapter->del($this->id, $room); + unset($this->rooms[$room]); + return $this; + } + + /** + * Leave all rooms. + * + * @api private + */ + + public function leaveAll() + { + $this->adapter->delAll($this->id); + $this->rooms = []; + } + + /** + * Called by `Namespace` upon succesful + * middleware execution (ie: authorization). + * + * @api private + */ + public function onconnect() + { + $this->nsp->connected[$this->id] = $this; + $this->join($this->id); + $this->packet( + [ + 'type' => Parser::CONNECT + ] + ); + } + + /** + * Called with each packet. Called by `Client`. + * + * @param {Object} packet + * @throws Exception + * @api private + */ + public function onpacket($packet) + { + switch ($packet['type']) { + case Parser::BINARY_EVENT: + case Parser::EVENT: + $this->onevent($packet); + break; + case Parser::BINARY_ACK: + case Parser::ACK: + $this->onack($packet); + break; + case Parser::DISCONNECT: + $this->ondisconnect(); + break; + case Parser::ERROR: + $this->emit('error', $packet['data']); + } + } + + /** + * Called upon event packet. + * + * @param {Object} packet object + * @api private + */ + public function onevent($packet) + { + $args = $packet['data'] ?? []; + if (! empty($packet['id']) || (isset($packet['id']) && $packet['id'] === 0)) { + $args[] = $this->ack($packet['id']); + } + call_user_func_array(array(get_parent_class(__CLASS__), 'emit'), $args); + } + + /** + * Produces an ack callback to emit with an event. + * + * @param {Number} packet id + * @api private + */ + public function ack($id): Closure + { + $sent = false; + return function () use (&$sent, $id) { + $self = $this; + // prevent double callbacks + if ($sent) { + return; + } + $args = func_get_args(); + $type = $this->hasBin($args) ? Parser::BINARY_ACK : Parser::ACK; + $self->packet( + [ + 'id' => $id, + 'type' => $type, + 'data' => $args + ] + ); + }; + } + + /** + * Called upon ack packet. + * + * @api private + */ + public function onack($packet) + { + $ack = $this->acks[$packet['id']]; + if (is_callable($ack)) { + call_user_func($ack, $packet['data']); + unset($this->acks[$packet['id']]); + } else { + echo('bad ack ' . $packet['id']); + } + } + + /** + * Called upon client disconnect packet. + * + * @throws Exception + * @api private + */ + public function ondisconnect() + { + $this->onclose('client namespace disconnect'); + } + + /** + * Handles a client error. + * + * @throws Exception + * @api private + */ + public function onerror($err) + { + if ($this->listeners('error')) { + $this->emit('error', $err); + } + } + + /** + * Called upon closing. Called by `Client`. + * + * @param {String} reason + * @param {Error} optional error object + * @throws Exception + * @api private + */ + public function onclose($reason) + { + if (! $this->connected) { + return $this; + } + $this->emit('disconnect', $reason); + $this->leaveAll(); + $this->nsp->remove($this); + $this->client->remove($this); + $this->connected = false; + $this->disconnected = true; + unset($this->nsp->connected[$this->id]); + // .... + $this->nsp = null; + $this->server = null; + $this->adapter = null; + $this->request = null; + $this->client = null; + $this->conn = null; + $this->removeAllListeners(); + } + + /** + * Produces an `error` packet. + * + * @param {Object} error object + * @api private + */ + + public function error($err) + { + $this->packet( + [ + 'type' => Parser::ERROR, 'data' => $err + ] + ); + } + + /** + * Disconnects this client. + * + * @param bool $close + * @return Socket {Socket} self + * @throws Exception + * @api public + */ + public function disconnect(bool $close = false): Socket + { + if (! $this->connected) { + return $this; + } + if ($close) { + $this->client->disconnect(); + } else { + $this->packet( + [ + 'type' => Parser::DISCONNECT + ] + ); + $this->onclose('server namespace disconnect'); + } + return $this; + } + + /** + * Sets the compress flag. + * + * @param {Boolean} if `true`, compresses the sending data + * @return Socket {Socket} self + * @api public + */ + public function compress($compress): Socket + { + $this->flags['compress'] = $compress; + return $this; + } + + protected function hasBin($args): bool + { + $hasBin = false; + + array_walk_recursive( + $args, + function ($item, $key) use ($hasBin) { + if (! ctype_print($item)) { + $hasBin = true; + } + } + ); + + return $hasBin; + } +} diff --git a/vendor/workerman/phpsocket.io/src/SocketIO.php b/vendor/workerman/phpsocket.io/src/SocketIO.php new file mode 100644 index 0000000..94c6687 --- /dev/null +++ b/vendor/workerman/phpsocket.io/src/SocketIO.php @@ -0,0 +1,177 @@ +nsp($nsp); + + $socket = $opts['socket'] ?? '\PHPSocketIO\Socket'; + $this->socket($socket); + + $adapter = $opts['adapter'] ?? '\PHPSocketIO\DefaultAdapter'; + $this->adapter($adapter); + if (isset($opts['origins'])) { + $this->origins($opts['origins']); + } + + unset($opts['nsp'], $opts['socket'], $opts['adapter'], $opts['origins']); + + $this->sockets = $this->of('/'); + + if (! class_exists('Protocols\SocketIO')) { + class_alias('PHPSocketIO\Engine\Protocols\SocketIO', 'Protocols\SocketIO'); + } + if ($port) { + $host = filter_var(trim($opts['host'] ?? '0.0.0.0', '[]'), FILTER_VALIDATE_IP) ?: '0.0.0.0'; + $worker = new Worker('SocketIO://' . $host . ':' . $port, $opts); + $worker->name = 'PHPSocketIO'; + + if (isset($opts['ssl'])) { + $worker->transport = 'ssl'; + } + + $this->attach($worker); + } + } + + public function nsp($v = null) + { + if (empty($v)) { + return $this->_nsp; + } + $this->_nsp = $v; + return $this; + } + + public function socket($v = null) + { + if (empty($v)) { + return $this->_socket; + } + $this->_socket = $v; + return $this; + } + + public function adapter($v = null) + { + if (empty($v)) { + return $this->_adapter; + } + $this->_adapter = $v; + foreach ($this->nsps as $nsp) { + $nsp->initAdapter(); + } + return $this; + } + + public function origins($v = null) + { + if ($v === null) { + return $this->_origins; + } + $this->_origins = $v; + if (isset($this->engine)) { + $this->engine->origins = $this->_origins; + } + return $this; + } + + public function attach($srv, $opts = []): SocketIO + { + $engine = new Engine(); + $engine->attach($srv, $opts); + + // Export http server + $this->worker = $srv; + + // bind to engine events + $this->bind($engine); + + return $this; + } + + public function bind($engine): SocketIO + { + $this->engine = $engine; + $this->engine->on('connection', [$this, 'onConnection']); + $this->engine->origins = $this->_origins; + return $this; + } + + public function of($name, $fn = null) + { + if ($name[0] !== '/') { + $name = "/$name"; + } + if (empty($this->nsps[$name])) { + $nsp_name = $this->nsp(); + $this->nsps[$name] = new $nsp_name($this, $name); + } + if ($fn) { + $this->nsps[$name]->on('connect', $fn); + } + return $this->nsps[$name]; + } + + public function onConnection($engine_socket): SocketIO + { + $client = new Client($this, $engine_socket); + $client->connect('/'); + return $this; + } + + public function on() + { + $args = array_pad(func_get_args(), 2, null); + + if ($args[0] === 'workerStart') { + $this->worker->onWorkerStart = $args[1]; + } elseif ($args[0] === 'workerStop') { + $this->worker->onWorkerStop = $args[1]; + } elseif ($args[0] !== null) { + return call_user_func_array([$this->sockets, 'on'], $args); + } + } + + public function in() + { + return call_user_func_array([$this->sockets, 'in'], func_get_args()); + } + + public function to() + { + return call_user_func_array([$this->sockets, 'to'], func_get_args()); + } + + public function emit() + { + return call_user_func_array([$this->sockets, 'emit'], func_get_args()); + } + + public function send() + { + return call_user_func_array([$this->sockets, 'send'], func_get_args()); + } + + public function write() + { + return call_user_func_array([$this->sockets, 'write'], func_get_args()); + } +} diff --git a/vendor/workerman/workerman-584743.status b/vendor/workerman/workerman-584743.status new file mode 100755 index 0000000..3cd2332 --- /dev/null +++ b/vendor/workerman/workerman-584743.status @@ -0,0 +1,11 @@ +a:1:{i:584744;a:2:{s:4:"name";s:16:"ShoproChatWorker";s:6:"listen";s:23:"socketIO://0.0.0.0:2222";}} +----------------------------------------------GLOBAL STATUS---------------------------------------------------- +Workerman version:4.2.1 PHP version:8.0.26 +start time:2025-10-28 19:02:32 run 0 days 0 hours +load average: 0.06, 0.08, 0.07 event-loop:\Workerman\Events\Select +1 workers 1 processes +worker_name exit_status exit_count +ShoproChatWorker 0 0 +----------------------------------------------PROCESS STATUS--------------------------------------------------- +pid memory listening worker_name connections send_fail timers total_request qps status +584744 4.78M socketIO://0.0.0.0:2222 ShoproChatWorker 0 0 2 0 diff --git a/vendor/workerman/workerman/.github/FUNDING.yml b/vendor/workerman/workerman/.github/FUNDING.yml new file mode 100644 index 0000000..beae44f --- /dev/null +++ b/vendor/workerman/workerman/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +open_collective: workerman +patreon: walkor diff --git a/vendor/workerman/workerman/.gitignore b/vendor/workerman/workerman/.gitignore new file mode 100644 index 0000000..f3f9e18 --- /dev/null +++ b/vendor/workerman/workerman/.gitignore @@ -0,0 +1,6 @@ +logs +.buildpath +.project +.settings +.idea +.DS_Store diff --git a/vendor/workerman/workerman/Autoloader.php b/vendor/workerman/workerman/Autoloader.php new file mode 100644 index 0000000..7d760e9 --- /dev/null +++ b/vendor/workerman/workerman/Autoloader.php @@ -0,0 +1,69 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +/** + * Autoload. + */ +class Autoloader +{ + /** + * Autoload root path. + * + * @var string + */ + protected static $_autoloadRootPath = ''; + + /** + * Set autoload root path. + * + * @param string $root_path + * @return void + */ + public static function setRootPath($root_path) + { + self::$_autoloadRootPath = $root_path; + } + + /** + * Load files by namespace. + * + * @param string $name + * @return boolean + */ + public static function loadByNamespace($name) + { + $class_path = \str_replace('\\', \DIRECTORY_SEPARATOR, $name); + if (\strpos($name, 'Workerman\\') === 0) { + $class_file = __DIR__ . \substr($class_path, \strlen('Workerman')) . '.php'; + } else { + if (self::$_autoloadRootPath) { + $class_file = self::$_autoloadRootPath . \DIRECTORY_SEPARATOR . $class_path . '.php'; + } + if (empty($class_file) || !\is_file($class_file)) { + $class_file = __DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . "$class_path.php"; + } + } + + if (\is_file($class_file)) { + require_once($class_file); + if (\class_exists($name, false)) { + return true; + } + } + return false; + } +} + +\spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); \ No newline at end of file diff --git a/vendor/workerman/workerman/Connection/AsyncTcpConnection.php b/vendor/workerman/workerman/Connection/AsyncTcpConnection.php new file mode 100644 index 0000000..30b1638 --- /dev/null +++ b/vendor/workerman/workerman/Connection/AsyncTcpConnection.php @@ -0,0 +1,382 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use StdClass; +use Workerman\Events\EventInterface; +use Workerman\Lib\Timer; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncTcpConnection extends TcpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callable|null + */ + public $onConnect = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Status. + * + * @var int + */ + protected $_status = self::STATUS_INITIAL; + + /** + * Remote host. + * + * @var string + */ + protected $_remoteHost = ''; + + /** + * Remote port. + * + * @var int + */ + protected $_remotePort = 80; + + /** + * Connect start time. + * + * @var float + */ + protected $_connectStartTime = 0; + + /** + * Remote URI. + * + * @var string + */ + protected $_remoteURI = ''; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Reconnect timer. + * + * @var int + */ + protected $_reconnectTimer = null; + + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'ssl', + 'sslv2' => 'sslv2', + 'sslv3' => 'sslv3', + 'tls' => 'tls' + ); + + /** + * Construct. + * + * @param string $remote_address + * @param array $context_option + * @throws Exception + */ + public function __construct($remote_address, array $context_option = array()) + { + $address_info = \parse_url($remote_address); + if (!$address_info) { + list($scheme, $this->_remoteAddress) = \explode(':', $remote_address, 2); + if('unix' === strtolower($scheme)) { + $this->_remoteAddress = substr($remote_address, strpos($remote_address, '/') + 2); + } + if (!$this->_remoteAddress) { + Worker::safeEcho(new \Exception('bad remote_address')); + } + } else { + if (!isset($address_info['port'])) { + $address_info['port'] = 0; + } + if (!isset($address_info['path'])) { + $address_info['path'] = '/'; + } + if (!isset($address_info['query'])) { + $address_info['query'] = ''; + } else { + $address_info['query'] = '?' . $address_info['query']; + } + $this->_remoteHost = $address_info['host']; + $this->_remotePort = $address_info['port']; + $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; + $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; + $this->_remoteAddress = 'unix' === strtolower($scheme) + ? substr($remote_address, strpos($remote_address, '/') + 2) + : $this->_remoteHost . ':' . $this->_remotePort; + } + + $this->id = $this->_id = self::$_idRecorder++; + if(\PHP_INT_MAX === self::$_idRecorder){ + self::$_idRecorder = 0; + } + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = \ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } else { + $this->transport = self::$_builtinTransports[$scheme]; + } + + // For statistics. + ++self::$statistics['connection_count']; + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->maxPackageSize = self::$defaultMaxPackageSize; + $this->_contextOption = $context_option; + $this->context = new StdClass; + static::$connections[$this->_id] = $this; + } + + /** + * Do connect. + * + * @return void + */ + public function connect() + { + if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && + $this->_status !== self::STATUS_CLOSED) { + return; + } + $this->_status = self::STATUS_CONNECTING; + $this->_connectStartTime = \microtime(true); + set_error_handler(function() { + return false; + }); + if ($this->transport !== 'unix') { + if (!$this->_remotePort) { + $this->_remotePort = $this->transport === 'ssl' ? 443 : 80; + $this->_remoteAddress = $this->_remoteHost.':'.$this->_remotePort; + } + // Open socket connection asynchronously. + if ($this->_contextOption) { + $context = \stream_context_create($this->_contextOption); + $this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT, $context); + } else { + $this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT); + } + } else { + $this->_socket = \stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + \STREAM_CLIENT_ASYNC_CONNECT); + } + restore_error_handler(); + // If failed attempt to emit onError callback. + if (!$this->_socket || !\is_resource($this->_socket)) { + $this->emitError(\WORKERMAN_CONNECT_FAIL, $errstr); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + return; + } + // Add socket to global event loop waiting connection is successfully established or faild. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); + // For windows. + if(\DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); + } + } + + /** + * Reconnect. + * + * @param int $after + * @return void + */ + public function reconnect($after = 0) + { + $this->_status = self::STATUS_INITIAL; + static::$connections[$this->_id] = $this; + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + if ($after > 0) { + $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); + return; + } + $this->connect(); + } + + /** + * CancelReconnect. + */ + public function cancelReconnect() + { + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteHost() + { + return $this->_remoteHost; + } + + /** + * Get remote URI. + * + * @return string + */ + public function getRemoteURI() + { + return $this->_remoteURI; + } + + /** + * Try to emit onError callback. + * + * @param int $code + * @param string $msg + * @return void + */ + protected function emitError($code, $msg) + { + $this->_status = self::STATUS_CLOSING; + if ($this->onError) { + try { + \call_user_func($this->onError, $this, $code, $msg); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + + /** + * Check connection is successfully established or faild. + * + * @param resource $socket + * @return void + */ + public function checkConnection() + { + // Remove EV_EXPECT for windows. + if(\DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT); + } + + // Remove write listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + if ($this->_status !== self::STATUS_CONNECTING) { + return; + } + + // Check socket state. + if ($address = \stream_socket_get_name($this->_socket, true)) { + // Nonblocking. + \stream_set_blocking($this->_socket, false); + // Compatible with hhvm + if (\function_exists('stream_set_read_buffer')) { + \stream_set_read_buffer($this->_socket, 0); + } + // Try to open keepalive for tcp and disable Nagle algorithm. + if (\function_exists('socket_import_stream') && $this->transport === 'tcp') { + $raw_socket = \socket_import_stream($this->_socket); + \socket_set_option($raw_socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); + \socket_set_option($raw_socket, \SOL_TCP, \TCP_NODELAY, 1); + } + + // SSL handshake. + if ($this->transport === 'ssl') { + $this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket); + if ($this->_sslHandshakeCompleted === false) { + return; + } + } else { + // There are some data waiting to send. + if ($this->_sendBuffer) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } + + // Register a listener waiting read event. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + + $this->_status = self::STATUS_ESTABLISHED; + $this->_remoteAddress = $address; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + // Try to emit protocol::onConnect + if ($this->protocol && \method_exists($this->protocol, 'onConnect')) { + try { + \call_user_func(array($this->protocol, 'onConnect'), $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } else { + // Connection failed. + $this->emitError(\WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(\microtime(true) - $this->_connectStartTime, 4) . ' seconds'); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + } + } +} diff --git a/vendor/workerman/workerman/Connection/AsyncUdpConnection.php b/vendor/workerman/workerman/Connection/AsyncUdpConnection.php new file mode 100644 index 0000000..745f060 --- /dev/null +++ b/vendor/workerman/workerman/Connection/AsyncUdpConnection.php @@ -0,0 +1,203 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * AsyncUdpConnection. + */ +class AsyncUdpConnection extends UdpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callable + */ + public $onConnect = null; + + /** + * Emitted when socket connection closed. + * + * @var callable + */ + public $onClose = null; + + /** + * Connected or not. + * + * @var bool + */ + protected $connected = false; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Construct. + * + * @param string $remote_address + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = \explode(':', $remote_address, 2); + // Check application layer protocol class. + if ($scheme !== 'udp') { + $scheme = \ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } + + $this->_remoteAddress = \substr($address, 2); + $this->_contextOption = $context_option; + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function baseRead($socket) + { + $recv_buffer = \stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $this); + } + ++ConnectionInterface::$statistics['total_request']; + try { + \call_user_func($this->onMessage, $this, $recv_buffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + if ($this->connected === false) { + $this->connect(); + } + return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0); + } + + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + \fclose($this->_socket); + $this->connected = false; + // Try to emit onClose callback. + if ($this->onClose) { + try { + \call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->onConnect = $this->onMessage = $this->onClose = null; + return true; + } + + /** + * Connect. + * + * @return void + */ + public function connect() + { + if ($this->connected === true) { + return; + } + if ($this->_contextOption) { + $context = \stream_context_create($this->_contextOption); + $this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg, + 30, \STREAM_CLIENT_CONNECT, $context); + } else { + $this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg); + } + + if (!$this->_socket) { + Worker::safeEcho(new \Exception($errmsg)); + return; + } + + \stream_set_blocking($this->_socket, false); + + if ($this->onMessage) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + } + $this->connected = true; + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + +} diff --git a/vendor/workerman/workerman/Connection/ConnectionInterface.php b/vendor/workerman/workerman/Connection/ConnectionInterface.php new file mode 100644 index 0000000..5d815d8 --- /dev/null +++ b/vendor/workerman/workerman/Connection/ConnectionInterface.php @@ -0,0 +1,126 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * ConnectionInterface. + */ +#[\AllowDynamicProperties] +abstract class ConnectionInterface +{ + /** + * Statistics for status command. + * + * @var array + */ + public static $statistics = array( + 'connection_count' => 0, + 'total_request' => 0, + 'throw_exception' => 0, + 'send_fail' => 0, + ); + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @return void|boolean + */ + abstract public function send($send_buffer); + + /** + * Get remote IP. + * + * @return string + */ + abstract public function getRemoteIp(); + + /** + * Get remote port. + * + * @return int + */ + abstract public function getRemotePort(); + + /** + * Get remote address. + * + * @return string + */ + abstract public function getRemoteAddress(); + + /** + * Get local IP. + * + * @return string + */ + abstract public function getLocalIp(); + + /** + * Get local port. + * + * @return int + */ + abstract public function getLocalPort(); + + /** + * Get local address. + * + * @return string + */ + abstract public function getLocalAddress(); + + /** + * Is ipv4. + * + * @return bool + */ + abstract public function isIPv4(); + + /** + * Is ipv6. + * + * @return bool + */ + abstract public function isIPv6(); + + /** + * Close connection. + * + * @param string|null $data + * @return void + */ + abstract public function close($data = null); +} diff --git a/vendor/workerman/workerman/Connection/TcpConnection.php b/vendor/workerman/workerman/Connection/TcpConnection.php new file mode 100644 index 0000000..a4995f9 --- /dev/null +++ b/vendor/workerman/workerman/Connection/TcpConnection.php @@ -0,0 +1,1004 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * TcpConnection. + */ +class TcpConnection extends ConnectionInterface +{ + /** + * Read buffer size. + * + * @var int + */ + const READ_BUFFER_SIZE = 65535; + + /** + * Status initial. + * + * @var int + */ + const STATUS_INITIAL = 0; + + /** + * Status connecting. + * + * @var int + */ + const STATUS_CONNECTING = 1; + + /** + * Status connection established. + * + * @var int + */ + const STATUS_ESTABLISHED = 2; + + /** + * Status closing. + * + * @var int + */ + const STATUS_CLOSING = 4; + + /** + * Status closed. + * + * @var int + */ + const STATUS_CLOSED = 8; + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callable + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callable + */ + public $onBufferDrain = null; + + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport (tcp/udp/unix/ssl). + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Which worker belong to. + * + * @var Worker + */ + public $worker = null; + + /** + * Bytes read. + * + * @var int + */ + public $bytesRead = 0; + + /** + * Bytes written. + * + * @var int + */ + public $bytesWritten = 0; + + /** + * Connection->id. + * + * @var int + */ + public $id = 0; + + /** + * A copy of $worker->id which used to clean up the connection in worker->connections + * + * @var int + */ + protected $_id = 0; + + /** + * Sets the maximum send buffer size for the current connection. + * OnBufferFull callback will be emited When the send buffer is full. + * + * @var int + */ + public $maxSendBufferSize = 1048576; + + /** + * Context. + * + * @var object|null + */ + public $context = null; + + /** + * Default send buffer size. + * + * @var int + */ + public static $defaultMaxSendBufferSize = 1048576; + + /** + * Sets the maximum acceptable packet size for the current connection. + * + * @var int + */ + public $maxPackageSize = 1048576; + + /** + * Default maximum acceptable packet size. + * + * @var int + */ + public static $defaultMaxPackageSize = 10485760; + + /** + * Id recorder. + * + * @var int + */ + protected static $_idRecorder = 1; + + /** + * Socket + * + * @var resource + */ + protected $_socket = null; + + /** + * Send buffer. + * + * @var string + */ + protected $_sendBuffer = ''; + + /** + * Receive buffer. + * + * @var string + */ + protected $_recvBuffer = ''; + + /** + * Current package length. + * + * @var int + */ + protected $_currentPackageLength = 0; + + /** + * Connection status. + * + * @var int + */ + protected $_status = self::STATUS_ESTABLISHED; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Is paused. + * + * @var bool + */ + protected $_isPaused = false; + + /** + * SSL handshake completed or not. + * + * @var bool + */ + protected $_sslHandshakeCompleted = false; + + /** + * All connection instances. + * + * @var array + */ + public static $connections = array(); + + /** + * Is safe. + * + * @var bool + */ + protected $_isSafe = true; + + /** + * Status to string. + * + * @var array + */ + public static $_statusToString = array( + self::STATUS_INITIAL => 'INITIAL', + self::STATUS_CONNECTING => 'CONNECTING', + self::STATUS_ESTABLISHED => 'ESTABLISHED', + self::STATUS_CLOSING => 'CLOSING', + self::STATUS_CLOSED => 'CLOSED', + ); + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address = '') + { + ++self::$statistics['connection_count']; + $this->id = $this->_id = self::$_idRecorder++; + if(self::$_idRecorder === \PHP_INT_MAX){ + self::$_idRecorder = 0; + } + $this->_socket = $socket; + \stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (\function_exists('stream_set_read_buffer')) { + \stream_set_read_buffer($this->_socket, 0); + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->maxPackageSize = self::$defaultMaxPackageSize; + $this->_remoteAddress = $remote_address; + static::$connections[$this->id] = $this; + $this->context = new \stdClass; + } + + /** + * Get status. + * + * @param bool $raw_output + * + * @return int|string + */ + public function getStatus($raw_output = true) + { + if ($raw_output) { + return $this->_status; + } + return self::$_statusToString[$this->_status]; + } + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @param bool $raw + * @return bool|null + */ + public function send($send_buffer, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return false; + } + + // Try to call protocol::encode($send_buffer) before sending. + if (false === $raw && $this->protocol !== null) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + + if ($this->_status !== self::STATUS_ESTABLISHED || + ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) + ) { + if ($this->_sendBuffer && $this->bufferIsFull()) { + ++self::$statistics['send_fail']; + return false; + } + $this->_sendBuffer .= $send_buffer; + $this->checkBufferWillFull(); + return; + } + + // Attempt to send data directly. + if ($this->_sendBuffer === '') { + if ($this->transport === 'ssl') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + $this->_sendBuffer = $send_buffer; + $this->checkBufferWillFull(); + return; + } + $len = 0; + try { + $len = @\fwrite($this->_socket, $send_buffer); + } catch (\Exception $e) { + Worker::log($e); + } catch (\Error $e) { + Worker::log($e); + } + // send successful. + if ($len === \strlen($send_buffer)) { + $this->bytesWritten += $len; + return true; + } + // Send only part of the data. + if ($len > 0) { + $this->_sendBuffer = \substr($send_buffer, $len); + $this->bytesWritten += $len; + } else { + // Connection closed? + if (!\is_resource($this->_socket) || \feof($this->_socket)) { + ++self::$statistics['send_fail']; + if ($this->onError) { + try { + \call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'client closed'); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->destroy(); + return false; + } + $this->_sendBuffer = $send_buffer; + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + // Check if the send buffer will be full. + $this->checkBufferWillFull(); + return; + } + + if ($this->bufferIsFull()) { + ++self::$statistics['send_fail']; + return false; + } + + $this->_sendBuffer .= $send_buffer; + // Check if the send buffer is full. + $this->checkBufferWillFull(); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = \strrpos($this->_remoteAddress, ':'); + if ($pos) { + return (string) \substr($this->_remoteAddress, 0, $pos); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int) \substr(\strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return ''; + } + return \substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)\substr(\strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + if (!\is_resource($this->_socket)) { + return ''; + } + return (string)@\stream_socket_get_name($this->_socket, false); + } + + /** + * Get send buffer queue size. + * + * @return integer + */ + public function getSendBufferQueueSize() + { + return \strlen($this->_sendBuffer); + } + + /** + * Get recv buffer queue size. + * + * @return integer + */ + public function getRecvBufferQueueSize() + { + return \strlen($this->_recvBuffer); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. + * + * @return void + */ + public function pauseRecv() + { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + $this->_isPaused = true; + } + + /** + * Resumes reading after a call to pauseRecv. + * + * @return void + */ + public function resumeRecv() + { + if ($this->_isPaused === true) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->_isPaused = false; + $this->baseRead($this->_socket, false); + } + } + + + + /** + * Base read handler. + * + * @param resource $socket + * @param bool $check_eof + * @return void + */ + public function baseRead($socket, $check_eof = true) + { + // SSL handshake. + if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) { + if ($this->doSslHandshake($socket)) { + $this->_sslHandshakeCompleted = true; + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } else { + return; + } + } + + $buffer = ''; + try { + $buffer = @\fread($socket, self::READ_BUFFER_SIZE); + } catch (\Exception $e) {} catch (\Error $e) {} + + // Check connection closed. + if ($buffer === '' || $buffer === false) { + if ($check_eof && (\feof($socket) || !\is_resource($socket) || $buffer === false)) { + $this->destroy(); + return; + } + } else { + $this->bytesRead += \strlen($buffer); + $this->_recvBuffer .= $buffer; + } + + // If the application layer protocol has been set up. + if ($this->protocol !== null) { + $parser = $this->protocol; + while ($this->_recvBuffer !== '' && !$this->_isPaused) { + // The current packet length is known. + if ($this->_currentPackageLength) { + // Data is not enough for a package. + if ($this->_currentPackageLength > \strlen($this->_recvBuffer)) { + break; + } + } else { + // Get current package length. + try { + $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); + } catch (\Exception $e) {} catch (\Error $e) {} + // The packet length is unknown. + if ($this->_currentPackageLength === 0) { + break; + } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= $this->maxPackageSize) { + // Data is not enough for a package. + if ($this->_currentPackageLength > \strlen($this->_recvBuffer)) { + break; + } + } // Wrong package. + else { + Worker::safeEcho('Error package. package_length=' . \var_export($this->_currentPackageLength, true)); + $this->destroy(); + return; + } + } + + // The data is enough for a packet. + ++self::$statistics['total_request']; + // The current packet length is equal to the length of the buffer. + if (\strlen($this->_recvBuffer) === $this->_currentPackageLength) { + $one_request_buffer = $this->_recvBuffer; + $this->_recvBuffer = ''; + } else { + // Get a full package from the buffer. + $one_request_buffer = \substr($this->_recvBuffer, 0, $this->_currentPackageLength); + // Remove the current package from the receive buffer. + $this->_recvBuffer = \substr($this->_recvBuffer, $this->_currentPackageLength); + } + // Reset the current packet length to 0. + $this->_currentPackageLength = 0; + if (!$this->onMessage) { + continue; + } + try { + // Decode request buffer before Emitting onMessage callback. + \call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return; + } + + if ($this->_recvBuffer === '' || $this->_isPaused) { + return; + } + + // Applications protocol is not set. + ++self::$statistics['total_request']; + if (!$this->onMessage) { + $this->_recvBuffer = ''; + return; + } + try { + \call_user_func($this->onMessage, $this, $this->_recvBuffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + // Clean receive buffer. + $this->_recvBuffer = ''; + } + + /** + * Base write handler. + * + * @return void|bool + */ + public function baseWrite() + { + \set_error_handler(function(){}); + if ($this->transport === 'ssl') { + $len = @\fwrite($this->_socket, $this->_sendBuffer, 8192); + } else { + $len = @\fwrite($this->_socket, $this->_sendBuffer); + } + \restore_error_handler(); + if ($len === \strlen($this->_sendBuffer)) { + $this->bytesWritten += $len; + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + $this->_sendBuffer = ''; + // Try to emit onBufferDrain callback when the send buffer becomes empty. + if ($this->onBufferDrain) { + try { + \call_user_func($this->onBufferDrain, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + return true; + } + if ($len > 0) { + $this->bytesWritten += $len; + $this->_sendBuffer = \substr($this->_sendBuffer, $len); + } else { + ++self::$statistics['send_fail']; + $this->destroy(); + } + } + + /** + * SSL handshake. + * + * @param resource $socket + * @return bool + */ + public function doSslHandshake($socket){ + if (\feof($socket)) { + $this->destroy(); + return false; + } + $async = $this instanceof AsyncTcpConnection; + + /** + * We disabled ssl3 because https://blog.qualys.com/ssllabs/2014/10/15/ssl-3-is-dead-killed-by-the-poodle-attack. + * You can enable ssl3 by the codes below. + */ + /*if($async){ + $type = STREAM_CRYPTO_METHOD_SSLv2_CLIENT | STREAM_CRYPTO_METHOD_SSLv23_CLIENT | STREAM_CRYPTO_METHOD_SSLv3_CLIENT; + }else{ + $type = STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER | STREAM_CRYPTO_METHOD_SSLv3_SERVER; + }*/ + + if($async){ + $type = \STREAM_CRYPTO_METHOD_SSLv2_CLIENT | \STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + }else{ + $type = \STREAM_CRYPTO_METHOD_SSLv2_SERVER | \STREAM_CRYPTO_METHOD_SSLv23_SERVER; + } + + // Hidden error. + \set_error_handler(function($errno, $errstr, $file){ + if (!Worker::$daemonize) { + Worker::safeEcho("SSL handshake error: $errstr \n"); + } + }); + $ret = \stream_socket_enable_crypto($socket, true, $type); + \restore_error_handler(); + // Negotiation has failed. + if (false === $ret) { + $this->destroy(); + return false; + } elseif (0 === $ret) { + // There isn't enough data and should try again. + return 0; + } + if (isset($this->onSslHandshake)) { + try { + \call_user_func($this->onSslHandshake, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + + /** + * This method pulls all the data out of a readable stream, and writes it to the supplied destination. + * + * @param self $dest + * @param bool $raw + * @return void + */ + public function pipe(self $dest, $raw = false) + { + $source = $this; + $this->onMessage = function ($source, $data) use ($dest, $raw) { + $dest->send($data, $raw); + }; + $this->onClose = function ($source) use ($dest) { + $dest->close(); + }; + $dest->onBufferFull = function ($dest) use ($source) { + $source->pauseRecv(); + }; + $dest->onBufferDrain = function ($dest) use ($source) { + $source->resumeRecv(); + }; + } + + /** + * Remove $length of data from receive buffer. + * + * @param int $length + * @return void + */ + public function consumeRecvBuffer($length) + { + $this->_recvBuffer = \substr($this->_recvBuffer, $length); + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return void + */ + public function close($data = null, $raw = false) + { + if($this->_status === self::STATUS_CONNECTING){ + $this->destroy(); + return; + } + + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return; + } + + if ($data !== null) { + $this->send($data, $raw); + } + + $this->_status = self::STATUS_CLOSING; + + if ($this->_sendBuffer === '') { + $this->destroy(); + } else { + $this->pauseRecv(); + } + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } + + /** + * Check whether the send buffer will be full. + * + * @return void + */ + protected function checkBufferWillFull() + { + if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) { + if ($this->onBufferFull) { + try { + \call_user_func($this->onBufferFull, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + } + + /** + * Whether send buffer is full. + * + * @return bool + */ + protected function bufferIsFull() + { + // Buffer has been marked as full but still has data to send then the packet is discarded. + if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) { + if ($this->onError) { + try { + \call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + return false; + } + + /** + * Whether send buffer is Empty. + * + * @return bool + */ + public function bufferIsEmpty() + { + return empty($this->_sendBuffer); + } + + /** + * Destroy connection. + * + * @return void + */ + public function destroy() + { + // Avoid repeated calls. + if ($this->_status === self::STATUS_CLOSED) { + return; + } + // Remove event listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + // Close socket. + try { + @\fclose($this->_socket); + } catch (\Exception $e) {} catch (\Error $e) {} + + $this->_status = self::STATUS_CLOSED; + // Try to emit onClose callback. + if ($this->onClose) { + try { + \call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + // Try to emit protocol::onClose + if ($this->protocol && \method_exists($this->protocol, 'onClose')) { + try { + \call_user_func(array($this->protocol, 'onClose'), $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->_sendBuffer = $this->_recvBuffer = ''; + $this->_currentPackageLength = 0; + $this->_isPaused = $this->_sslHandshakeCompleted = false; + if ($this->_status === self::STATUS_CLOSED) { + // Cleaning up the callback to avoid memory leaks. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; + // Remove from worker->connections. + if ($this->worker) { + unset($this->worker->connections[$this->_id]); + } + unset(static::$connections[$this->_id]); + } + } + + + /** + * __wakeup. + * + * @return void + */ + public function __wakeup() + { + $this->_isSafe = false; + } + + /** + * Destruct. + * + * @return void + */ + public function __destruct() + { + static $mod; + if (!$this->_isSafe) { + return; + } + self::$statistics['connection_count']--; + if (Worker::getGracefulStop()) { + if (!isset($mod)) { + $mod = \ceil((self::$statistics['connection_count'] + 1) / 3); + } + + if (0 === self::$statistics['connection_count'] % $mod) { + Worker::log('worker[' . \posix_getpid() . '] remains ' . self::$statistics['connection_count'] . ' connection(s)'); + } + + if(0 === self::$statistics['connection_count']) { + Worker::stopAll(); + } + } + } +} diff --git a/vendor/workerman/workerman/Connection/UdpConnection.php b/vendor/workerman/workerman/Connection/UdpConnection.php new file mode 100644 index 0000000..9cd95ba --- /dev/null +++ b/vendor/workerman/workerman/Connection/UdpConnection.php @@ -0,0 +1,208 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * UdpConnection. + */ +class UdpConnection extends ConnectionInterface +{ + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'udp'; + + /** + * Udp socket. + * + * @var resource + */ + protected $_socket = null; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address) + { + $this->_socket = $socket; + $this->_remoteAddress = $remote_address; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0, $this->isIpV6() ? '[' . $this->getRemoteIp() . ']:' . $this->getRemotePort() : $this->_remoteAddress); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = \strrpos($this->_remoteAddress, ':'); + if ($pos) { + return \trim(\substr($this->_remoteAddress, 0, $pos), '[]'); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)\substr(\strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return ''; + } + return \substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)\substr(\strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@\stream_socket_get_name($this->_socket, false); + } + + /** + * Is ipv4. + * + * @return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * @return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + return true; + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } +} diff --git a/vendor/workerman/workerman/Events/Ev.php b/vendor/workerman/workerman/Events/Ev.php new file mode 100644 index 0000000..8e21bc3 --- /dev/null +++ b/vendor/workerman/workerman/Events/Ev.php @@ -0,0 +1,189 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; +use \EvWatcher; + +/** + * ev eventloop + */ +class Ev implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * Add a timer. + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = null) + { + $callback = function ($event, $socket) use ($fd, $func) { + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + switch ($flag) { + case self::EV_SIGNAL: + $event = new \EvSignal($fd, $callback); + $this->_eventSignal[$fd] = $event; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag === self::EV_TIMER_ONCE ? 0 : $fd; + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; + $event = new \EvIo($fd, $real_flag, $callback); + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + + } + + /** + * Remove a timer. + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->stop(); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->stop(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param EvWatcher $event + */ + public function timerCallback(EvWatcher $event) + { + $param = $event->data; + $timer_id = $param[4]; + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->stop(); + unset($this->_eventTimer[$timer_id]); + } + try { + \call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->stop(); + } + $this->_eventTimer = array(); + } + + /** + * Main loop. + * + * @see EventInterface::loop() + */ + public function loop() + { + \Ev::run(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + \Ev::stop(\Ev::BREAK_ALL); + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/Event.php b/vendor/workerman/workerman/Events/Event.php new file mode 100644 index 0000000..9e25521 --- /dev/null +++ b/vendor/workerman/workerman/Events/Event.php @@ -0,0 +1,215 @@ + + * @copyright 有个鬼<42765633@qq.com> + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Event implements EventInterface +{ + /** + * Event base. + * @var object + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * @var int + */ + protected static $_timerId = 1; + + /** + * construct + * @return void + */ + public function __construct() + { + if (\class_exists('\\\\EventBase', false)) { + $class_name = '\\\\EventBase'; + } else { + $class_name = '\EventBase'; + } + $this->_eventBase = new $class_name(); + } + + /** + * @see EventInterface::add() + */ + public function add($fd, $flag, $func, $args=array()) + { + if (\class_exists('\\\\Event', false)) { + $class_name = '\\\\Event'; + } else { + $class_name = '\Event'; + } + switch ($flag) { + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + $event = $class_name::signal($this->_eventBase, $fd, $func); + if (!$event||!$event->add()) { + return false; + } + $this->_eventSignal[$fd_key] = $event; + return true; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param); + if (!$event||!$event->addTimer($fd)) { + return false; + } + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST; + $event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd); + if (!$event||!$event->add()) { + return false; + } + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + } + + /** + * @see Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + + case self::EV_READ: + case self::EV_WRITE: + + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->del(); + unset($this->_eventSignal[$fd_key]); + } + break; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->del(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * @param int|null $fd + * @param int $what + * @param int $timer_id + */ + public function timerCallback($fd, $what, $param) + { + $timer_id = $param[4]; + + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->del(); + unset($this->_eventTimer[$timer_id]); + } + + try { + \call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * @see Events\EventInterface::clearAllTimer() + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->del(); + } + $this->_eventTimer = array(); + } + + + /** + * @see EventInterface::loop() + */ + public function loop() + { + $this->_eventBase->loop(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + $this->_eventBase->exit(); + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/EventInterface.php b/vendor/workerman/workerman/Events/EventInterface.php new file mode 100644 index 0000000..e6f59c6 --- /dev/null +++ b/vendor/workerman/workerman/Events/EventInterface.php @@ -0,0 +1,107 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +interface EventInterface +{ + /** + * Read event. + * + * @var int + */ + const EV_READ = 1; + + /** + * Write event. + * + * @var int + */ + const EV_WRITE = 2; + + /** + * Except event + * + * @var int + */ + const EV_EXCEPT = 3; + + /** + * Signal event. + * + * @var int + */ + const EV_SIGNAL = 4; + + /** + * Timer event. + * + * @var int + */ + const EV_TIMER = 8; + + /** + * Timer once event. + * + * @var int + */ + const EV_TIMER_ONCE = 16; + + /** + * Add event listener to event loop. + * + * @param mixed $fd + * @param int $flag + * @param callable $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, $args = array()); + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag); + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer(); + + /** + * Main loop. + * + * @return void + */ + public function loop(); + + /** + * Destroy loop. + * + * @return mixed + */ + public function destroy(); + + /** + * Get Timer count. + * + * @return mixed + */ + public function getTimerCount(); +} diff --git a/vendor/workerman/workerman/Events/Libevent.php b/vendor/workerman/workerman/Events/Libevent.php new file mode 100644 index 0000000..5f61e9c --- /dev/null +++ b/vendor/workerman/workerman/Events/Libevent.php @@ -0,0 +1,225 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Libevent implements EventInterface +{ + /** + * Event base. + * + * @var resource + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * construct + */ + public function __construct() + { + $this->_eventBase = \event_base_new(); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $fd_key = (int)$fd; + $real_flag = \EV_SIGNAL | \EV_PERSIST; + $this->_eventSignal[$fd_key] = \event_new(); + if (!\event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { + return false; + } + if (!\event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { + return false; + } + if (!\event_add($this->_eventSignal[$fd_key])) { + return false; + } + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $event = \event_new(); + $timer_id = (int)$event; + if (!\event_set($event, 0, \EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { + return false; + } + + if (!\event_base_set($event, $this->_eventBase)) { + return false; + } + + $time_interval = $fd * 1000000; + if (!\event_add($event, $time_interval)) { + return false; + } + $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); + return $timer_id; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \EV_READ | \EV_PERSIST : \EV_WRITE | \EV_PERSIST; + + $event = \event_new(); + + if (!\event_set($event, $fd, $real_flag, $func, null)) { + return false; + } + + if (!\event_base_set($event, $this->_eventBase)) { + return false; + } + + if (!\event_add($event)) { + return false; + } + + $this->_allEvents[$fd_key][$flag] = $event; + + return true; + } + + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + \event_del($this->_allEvents[$fd_key][$flag]); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + \event_del($this->_eventSignal[$fd_key]); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // 这里 fd 为timerid + if (isset($this->_eventTimer[$fd])) { + \event_del($this->_eventTimer[$fd][2]); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param mixed $_null1 + * @param int $_null2 + * @param mixed $timer_id + */ + protected function timerCallback($_null1, $_null2, $timer_id) + { + if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { + \event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); + } + try { + \call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $task_data) { + \event_del($task_data[2]); + } + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + \event_base_loop($this->_eventBase); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + \event_del($event); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} + diff --git a/vendor/workerman/workerman/Events/React/Base.php b/vendor/workerman/workerman/Events/React/Base.php new file mode 100644 index 0000000..bce4f73 --- /dev/null +++ b/vendor/workerman/workerman/Events/React/Base.php @@ -0,0 +1,264 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +use Workerman\Events\EventInterface; +use React\EventLoop\TimerInterface; +use React\EventLoop\LoopInterface; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class Base implements LoopInterface +{ + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * @var array + */ + protected $_signalHandlerMap = array(); + + /** + * @var LoopInterface + */ + protected $_eventLoop = null; + + /** + * Base constructor. + */ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } + + /** + * Add event listener to event loop. + * + * @param int $fd + * @param int $flag + * @param callable $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, array $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + if (isset($this->_signalHandlerMap[$fd])) { + $this->removeSignal($fd, $this->_signalHandlerMap[$fd]); + } + $this->_signalHandlerMap[$fd] = $func; + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + \call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $index = ++$this->_timerIdIndex; + $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) { + $this->del($index,EventInterface::EV_TIMER_ONCE); + \call_user_func_array($func, $args); + }); + $this->_timerIdMap[$index] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + if (!isset($this->_eventLoop[$fd])) { + return false; + } + $func = $this->_eventLoop[$fd]; + unset($this->_eventLoop[$fd]); + return $this->removeSignal($fd, $func); + + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE: + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_timerIdMap); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addReadStream($stream, $listener) + { + return $this->_eventLoop->addReadStream($stream, $listener); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addWriteStream($stream, $listener) + { + return $this->_eventLoop->addWriteStream($stream, $listener); + } + + /** + * @param resource $stream + */ + public function removeReadStream($stream) + { + return $this->_eventLoop->removeReadStream($stream); + } + + /** + * @param resource $stream + */ + public function removeWriteStream($stream) + { + return $this->_eventLoop->removeWriteStream($stream); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addTimer($interval, $callback) + { + return $this->_eventLoop->addTimer($interval, $callback); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addPeriodicTimer($interval, $callback) + { + return $this->_eventLoop->addPeriodicTimer($interval, $callback); + } + + /** + * @param TimerInterface $timer + */ + public function cancelTimer(TimerInterface $timer) + { + return $this->_eventLoop->cancelTimer($timer); + } + + /** + * @param callable $listener + */ + public function futureTick($listener) + { + return $this->_eventLoop->futureTick($listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function addSignal($signal, $listener) + { + return $this->_eventLoop->addSignal($signal, $listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function removeSignal($signal, $listener) + { + return $this->_eventLoop->removeSignal($signal, $listener); + } + + /** + * Run. + */ + public function run() + { + return $this->_eventLoop->run(); + } + + /** + * Stop. + */ + public function stop() + { + return $this->_eventLoop->stop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/ExtEventLoop.php b/vendor/workerman/workerman/Events/React/ExtEventLoop.php new file mode 100644 index 0000000..3dab25b --- /dev/null +++ b/vendor/workerman/workerman/Events/React/ExtEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class ExtEventLoop + * @package Workerman\Events\React + */ +class ExtEventLoop extends Base +{ + + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtEventLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php b/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php new file mode 100644 index 0000000..eb02b35 --- /dev/null +++ b/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class ExtLibEventLoop + * @package Workerman\Events\React + */ +class ExtLibEventLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtLibeventLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/StreamSelectLoop.php b/vendor/workerman/workerman/Events/React/StreamSelectLoop.php new file mode 100644 index 0000000..7f5f94b --- /dev/null +++ b/vendor/workerman/workerman/Events/React/StreamSelectLoop.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class StreamSelectLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/Select.php b/vendor/workerman/workerman/Events/Select.php new file mode 100644 index 0000000..ebc263e --- /dev/null +++ b/vendor/workerman/workerman/Events/Select.php @@ -0,0 +1,357 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Throwable; +use Workerman\Worker; + +/** + * select eventloop + */ +class Select implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + public $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + public $_signalEvents = array(); + + /** + * Fds waiting for read event. + * + * @var array + */ + protected $_readFds = array(); + + /** + * Fds waiting for write event. + * + * @var array + */ + protected $_writeFds = array(); + + /** + * Fds waiting for except event. + * + * @var array + */ + protected $_exceptFds = array(); + + /** + * Timer scheduler. + * {['data':timer_id, 'priority':run_timestamp], ..} + * + * @var \SplPriorityQueue + */ + protected $_scheduler = null; + + /** + * All timer event listeners. + * [[func, args, flag, timer_interval], ..] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected $_timerId = 1; + + /** + * Select timeout. + * + * @var int + */ + protected $_selectTimeout = 100000000; + + /** + * Paired socket channels + * + * @var array + */ + protected $channel = array(); + + /** + * Construct. + */ + public function __construct() + { + // Init SplPriorityQueue. + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $count = $flag === self::EV_READ ? \count($this->_readFds) : \count($this->_writeFds); + if ($count >= 1024) { + echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n"; + } else if (\DIRECTORY_SEPARATOR !== '/' && $count >= 256) { + echo "Warning: system call select exceeded the maximum number of connections 256.\n"; + } + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + if ($flag === self::EV_READ) { + $this->_readFds[$fd_key] = $fd; + } else { + $this->_writeFds[$fd_key] = $fd; + } + break; + case self::EV_EXCEPT: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_exceptFds[$fd_key] = $fd; + break; + case self::EV_SIGNAL: + // Windows not support signal. + if(\DIRECTORY_SEPARATOR !== '/') { + return false; + } + $fd_key = (int)$fd; + $this->_signalEvents[$fd_key][$flag] = array($func, $fd); + \pcntl_signal($fd, array($this, 'signalHandler')); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $timer_id = $this->_timerId++; + $run_time = \microtime(true) + $fd; + $this->_scheduler->insert($timer_id, -$run_time); + $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); + $select_timeout = ($run_time - \microtime(true)) * 1000000; + $select_timeout = $select_timeout <= 0 ? 1 : $select_timeout; + if( $this->_selectTimeout > $select_timeout ){ + $this->_selectTimeout = (int) $select_timeout; + } + return $timer_id; + } + + return true; + } + + /** + * Signal handler. + * + * @param int $signal + */ + public function signalHandler($signal) + { + \call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + $fd_key = (int)$fd; + switch ($flag) { + case self::EV_READ: + unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_WRITE: + unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_EXCEPT: + unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); + if(empty($this->_allEvents[$fd_key])) + { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_SIGNAL: + if(\DIRECTORY_SEPARATOR !== '/') { + return false; + } + unset($this->_signalEvents[$fd_key]); + \pcntl_signal($fd, SIG_IGN); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE; + unset($this->_eventTimer[$fd_key]); + return true; + } + return false; + } + + /** + * Tick for timer. + * + * @return void + */ + protected function tick() + { + $tasks_to_insert = []; + while (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $timer_id = $scheduler_data['data']; + $next_run_time = -$scheduler_data['priority']; + $time_now = \microtime(true); + $this->_selectTimeout = (int) (($next_run_time - $time_now) * 1000000); + if ($this->_selectTimeout <= 0) { + $this->_scheduler->extract(); + + if (!isset($this->_eventTimer[$timer_id])) { + continue; + } + + // [func, args, flag, timer_interval] + $task_data = $this->_eventTimer[$timer_id]; + if ($task_data[2] === self::EV_TIMER) { + $next_run_time = $time_now + $task_data[3]; + $tasks_to_insert[] = [$timer_id, -$next_run_time]; + } + try { + \call_user_func_array($task_data[0], $task_data[1]); + } catch (Throwable $e) { + Worker::stopAll(250, $e); + } + if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } else { + break; + } + } + foreach ($tasks_to_insert as $item) { + $this->_scheduler->insert($item[0], $item[1]); + } + if (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $next_run_time = -$scheduler_data['priority']; + $time_now = \microtime(true); + $this->_selectTimeout = \max((int) (($next_run_time - $time_now) * 1000000), 0); + return; + } + $this->_selectTimeout = 100000000; + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + while (1) { + if(\DIRECTORY_SEPARATOR === '/') { + // Calls signal handlers for pending signals + \pcntl_signal_dispatch(); + } + + $read = $this->_readFds; + $write = $this->_writeFds; + $except = $this->_exceptFds; + $ret = false; + + if ($read || $write || $except) { + // Waiting read/write/signal/timeout events. + try { + $ret = @stream_select($read, $write, $except, 0, $this->_selectTimeout); + } catch (\Exception $e) {} catch (\Error $e) {} + + } else { + $this->_selectTimeout >= 1 && usleep($this->_selectTimeout); + } + + if (!$this->_scheduler->isEmpty()) { + $this->tick(); + } + + if (!$ret) { + continue; + } + + if ($read) { + foreach ($read as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_READ])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], + array($this->_allEvents[$fd_key][self::EV_READ][1])); + } + } + } + + if ($write) { + foreach ($write as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], + array($this->_allEvents[$fd_key][self::EV_WRITE][1])); + } + } + } + + if($except) { + foreach($except as $fd) { + $fd_key = (int) $fd; + if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], + array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); + } + } + } + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/Swoole.php b/vendor/workerman/workerman/Events/Swoole.php new file mode 100644 index 0000000..dc11526 --- /dev/null +++ b/vendor/workerman/workerman/Events/Swoole.php @@ -0,0 +1,285 @@ + + * @link http://www.workerman.net/ + * @link https://github.com/ares333/Workerman + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; +use Swoole\Event; +use Swoole\Timer; +use Swoole\Coroutine; + +class Swoole implements EventInterface +{ + + protected $_timer = array(); + + protected $_timerOnceMap = array(); + + protected $mapId = 0; + + protected $_fd = array(); + + // milisecond + public static $signalDispatchInterval = 500; + + protected $_hasSignal = false; + + protected $_readEvents = array(); + + protected $_writeEvents = array(); + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::add() + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $res = \pcntl_signal($fd, $func, false); + if (! $this->_hasSignal && $res) { + Timer::tick(static::$signalDispatchInterval, + function () { + \pcntl_signal_dispatch(); + }); + $this->_hasSignal = true; + } + return $res; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $method = self::EV_TIMER === $flag ? 'tick' : 'after'; + if ($this->mapId > \PHP_INT_MAX) { + $this->mapId = 0; + } + $mapId = $this->mapId++; + $t = (int)($fd * 1000); + if ($t < 1) { + $t = 1; + } + $timer_id = Timer::$method($t, + function ($timer_id = null) use ($func, $args, $mapId) { + try { + \call_user_func_array($func, (array)$args); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + // EV_TIMER_ONCE + if (! isset($timer_id)) { + // may be deleted in $func + if (\array_key_exists($mapId, $this->_timerOnceMap)) { + $timer_id = $this->_timerOnceMap[$mapId]; + unset($this->_timer[$timer_id], + $this->_timerOnceMap[$mapId]); + } + } + }); + if ($flag === self::EV_TIMER_ONCE) { + $this->_timerOnceMap[$mapId] = $timer_id; + $this->_timer[$timer_id] = $mapId; + } else { + $this->_timer[$timer_id] = null; + } + return $timer_id; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if ($flag === self::EV_READ) { + $this->_readEvents[$fd_key] = $func; + } else { + $this->_writeEvents[$fd_key] = $func; + } + if (!isset($this->_fd[$fd_key])) { + if ($flag === self::EV_READ) { + $res = Event::add($fd, [$this, 'callRead'], null, SWOOLE_EVENT_READ); + $fd_type = SWOOLE_EVENT_READ; + } else { + $res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE); + $fd_type = SWOOLE_EVENT_WRITE; + } + if ($res) { + $this->_fd[$fd_key] = $fd_type; + } + } else { + $fd_val = $this->_fd[$fd_key]; + $res = true; + if ($flag === self::EV_READ) { + if (($fd_val & SWOOLE_EVENT_READ) !== SWOOLE_EVENT_READ) { + $res = Event::set($fd, $func, null, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_READ; + } + } else { + if (($fd_val & SWOOLE_EVENT_WRITE) !== SWOOLE_EVENT_WRITE) { + $res = Event::set($fd, null, $func, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE; + } + } + } + return $res; + } + } + + /** + * @param $fd + * @return void + */ + protected function callRead($stream) + { + $fd = (int) $stream; + if (isset($this->_readEvents[$fd])) { + try { + \call_user_func($this->_readEvents[$fd], $stream); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + + /** + * @param $fd + * @return void + */ + protected function callWrite($stream) + { + $fd = (int) $stream; + if (isset($this->_writeEvents[$fd])) { + try { + \call_user_func($this->_writeEvents[$fd], $stream); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_SIGNAL: + return \pcntl_signal($fd, SIG_IGN, false); + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // already remove in EV_TIMER_ONCE callback. + if (! \array_key_exists($fd, $this->_timer)) { + return true; + } + $res = Timer::clear($fd); + if ($res) { + $mapId = $this->_timer[$fd]; + if (isset($mapId)) { + unset($this->_timerOnceMap[$mapId]); + } + unset($this->_timer[$fd]); + } + return $res; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if ($flag === self::EV_READ) { + unset($this->_readEvents[$fd_key]); + } elseif ($flag === self::EV_WRITE) { + unset($this->_writeEvents[$fd_key]); + } + if (isset($this->_fd[$fd_key])) { + $fd_val = $this->_fd[$fd_key]; + if ($flag === self::EV_READ) { + $flag_remove = ~ SWOOLE_EVENT_READ; + } else { + $flag_remove = ~ SWOOLE_EVENT_WRITE; + } + $fd_val &= $flag_remove; + if (0 === $fd_val) { + $res = Event::del($fd); + if ($res) { + unset($this->_fd[$fd_key]); + } + } else { + $res = Event::set($fd, null, null, $fd_val); + if ($res) { + $this->_fd[$fd_key] = $fd_val; + } + } + } else { + $res = true; + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::clearAllTimer() + */ + public function clearAllTimer() + { + foreach (array_keys($this->_timer) as $v) { + Timer::clear($v); + } + $this->_timer = array(); + $this->_timerOnceMap = array(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::loop() + */ + public function loop() + { + Event::wait(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::destroy() + */ + public function destroy() + { + foreach (Coroutine::listCoroutines() as $coroutine) { + Coroutine::cancel($coroutine); + } + // Wait for coroutines to exit + usleep(100000); + Event::exit(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::getTimerCount() + */ + public function getTimerCount() + { + return \count($this->_timer); + } +} diff --git a/vendor/workerman/workerman/Events/Uv.php b/vendor/workerman/workerman/Events/Uv.php new file mode 100644 index 0000000..49f0ddd --- /dev/null +++ b/vendor/workerman/workerman/Events/Uv.php @@ -0,0 +1,260 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libuv eventloop + */ +class Uv implements EventInterface +{ + /** + * Event Loop. + * @var object + */ + protected $_eventLoop = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * @brief Constructor + * + * @param object $loop + * + * @return void + */ + public function __construct(\UVLoop $loop = null) + { + if(!extension_loaded('uv')) + { + throw new \Exception(__CLASS__ . ' requires the UV extension, but detected it has NOT been installed yet.'); + } + + if(empty($loop) || !$loop instanceof \UVLoop) + { + $this->_eventLoop = \uv_default_loop(); + return; + } + + $this->_eventLoop = $loop; + } + + /** + * @brief Add a timer + * + * @param resource $fd + * @param int $flag + * @param callback $func + * @param mixed $args + * + * @return mixed + */ + public function add($fd, $flag, $func, $args = null) + { + switch ($flag) + { + case self::EV_SIGNAL: + $signalCallback = function($watcher, $socket)use($func, $fd){ + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + $signalWatcher = \uv_signal_init(); + \uv_signal_start($signalWatcher, $signalCallback, $fd); + $this->_eventSignal[$fd] = $signalWatcher; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag === self::EV_TIMER_ONCE ? 0 : (int)($fd * 1000); + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $timerWatcher = \uv_timer_init(); + \uv_timer_start($timerWatcher, ($flag === self::EV_TIMER_ONCE ? (int)($fd * 1000) :1), $repeat, function($watcher)use($param){ + call_user_func_array([$this, 'timerCallback'], [$param]); + }); + $this->_eventTimer[self::$_timerId] = $timerWatcher; + return self::$_timerId++; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + $ioCallback = function($watcher, $status, $events, $fd)use($func){ + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + $ioWatcher = \uv_poll_init($this->_eventLoop, $fd); + $real_flag = $flag === self::EV_READ ? \Uv::READABLE : \Uv::WRITABLE; + \uv_poll_start($ioWatcher, $real_flag, $ioCallback); + $this->_allEvents[$fd_key][$flag] = $ioWatcher; + return true; + default: + break; + } + } + + /** + * @brief Remove a timer + * + * @param resource $fd + * @param int $flag + * + * @return boolean + */ + public function del($fd, $flag) + { + switch ($flag) + { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $watcher = $this->_allEvents[$fd_key][$flag]; + \uv_is_active($watcher) && \uv_poll_stop($watcher); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $watcher = $this->_eventSignal[$fd_key]; + \uv_is_active($watcher) && \uv_signal_stop($watcher); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $watcher = $this->_eventTimer[$fd]; + \uv_is_active($watcher) && \uv_timer_stop($watcher); + unset($this->_eventTimer[$fd]); + } + break; + } + + return true; + } + + /** + * @brief Timer callback + * + * @param array $input + * + * @return void + */ + public function timerCallback($input) + { + if(!is_array($input)) return; + + $timer_id = $input[4]; + + if ($input[2] === self::EV_TIMER_ONCE) + { + $watcher = $this->_eventTimer[$timer_id]; + \uv_is_active($watcher) && \uv_timer_stop($watcher); + unset($this->_eventTimer[$timer_id]); + } + + try { + \call_user_func_array($input[0], $input[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * @brief Remove all timers + * + * @return void + */ + public function clearAllTimer() + { + if(!is_array($this->_eventTimer)) return; + + foreach($this->_eventTimer as $watcher) + { + \uv_is_active($watcher) && \uv_timer_stop($watcher); + } + + $this->_eventTimer = array(); + } + + /** + * @brief Start loop + * + * @return void + */ + public function loop() + { + \Uv_run(); + } + + /** + * @brief Destroy loop + * + * @return void + */ + public function destroy() + { + !empty($this->_eventLoop) && \uv_loop_delete($this->_eventLoop); + $this->_allEvents = []; + } + + /** + * @brief Get timer count + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Lib/Constants.php b/vendor/workerman/workerman/Lib/Constants.php new file mode 100644 index 0000000..f5e2424 --- /dev/null +++ b/vendor/workerman/workerman/Lib/Constants.php @@ -0,0 +1,44 @@ + + * @copyright walkor + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * + * @link http://www.workerman.net/ + */ + +// Pcre.jit is not stable, temporarily disabled. +ini_set('pcre.jit', 0); + +// For onError callback. +const WORKERMAN_CONNECT_FAIL = 1; +// For onError callback. +const WORKERMAN_SEND_FAIL = 2; + +// Define OS Type +const OS_TYPE_LINUX = 'linux'; +const OS_TYPE_WINDOWS = 'windows'; + +// Compatible with php7 +if (!class_exists('Error')) { + class Error extends Exception + { + } +} + +if (!interface_exists('SessionHandlerInterface')) { + interface SessionHandlerInterface { + public function close(); + public function destroy($session_id); + public function gc($maxlifetime); + public function open($save_path ,$session_name); + public function read($session_id); + public function write($session_id , $session_data); + } +} diff --git a/vendor/workerman/workerman/Lib/Timer.php b/vendor/workerman/workerman/Lib/Timer.php new file mode 100644 index 0000000..b110051 --- /dev/null +++ b/vendor/workerman/workerman/Lib/Timer.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Lib; + +/** + * Do not use Workerman\Lib\Timer. + * Please use Workerman\Timer. + * This class is only used for compatibility with workerman 3.* + * @package Workerman\Lib + */ +class Timer extends \Workerman\Timer {} \ No newline at end of file diff --git a/vendor/workerman/workerman/MIT-LICENSE.txt b/vendor/workerman/workerman/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/vendor/workerman/workerman/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/workerman/workerman/Protocols/Frame.php b/vendor/workerman/workerman/Protocols/Frame.php new file mode 100644 index 0000000..26b04de --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Frame.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Frame Protocol. + */ +class Frame +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + if (\strlen($buffer) < 4) { + return 0; + } + $unpack_data = \unpack('Ntotal_length', $buffer); + return $unpack_data['total_length']; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + return \substr($buffer, 4); + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + $total_length = 4 + \strlen($buffer); + return \pack('N', $total_length) . $buffer; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http.php b/vendor/workerman/workerman/Protocols/Http.php new file mode 100644 index 0000000..9e5d928 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http.php @@ -0,0 +1,323 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Request; +use Workerman\Protocols\Http\Response; +use Workerman\Protocols\Http\Session; +use Workerman\Protocols\Websocket; +use Workerman\Worker; + +/** + * Class Http. + * @package Workerman\Protocols + */ +class Http +{ + /** + * Request class name. + * + * @var string + */ + protected static $_requestClass = 'Workerman\Protocols\Http\Request'; + + /** + * Upload tmp dir. + * + * @var string + */ + protected static $_uploadTmpDir = ''; + + /** + * Open cache. + * + * @var bool. + */ + protected static $_enableCache = true; + + /** + * Get or set session name. + * + * @param string|null $name + * @return string + */ + public static function sessionName($name = null) + { + if ($name !== null && $name !== '') { + Session::$name = (string)$name; + } + return Session::$name; + } + + /** + * Get or set the request class name. + * + * @param string|null $class_name + * @return string + */ + public static function requestClass($class_name = null) + { + if ($class_name) { + static::$_requestClass = $class_name; + } + return static::$_requestClass; + } + + /** + * Enable or disable Cache. + * + * @param mixed $value + */ + public static function enableCache($value) + { + static::$_enableCache = (bool)$value; + } + + /** + * Check the integrity of the package. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($recv_buffer, TcpConnection $connection) + { + static $input = []; + if (!isset($recv_buffer[512]) && isset($input[$recv_buffer])) { + return $input[$recv_buffer]; + } + $crlf_pos = \strpos($recv_buffer, "\r\n\r\n"); + if (false === $crlf_pos) { + // Judge whether the package length exceeds the limit. + if (\strlen($recv_buffer) >= 16384) { + $connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true); + return 0; + } + return 0; + } + + $length = $crlf_pos + 4; + $method = \strstr($recv_buffer, ' ', true); + + if (!\in_array($method, ['GET', 'POST', 'OPTIONS', 'HEAD', 'DELETE', 'PUT', 'PATCH'])) { + $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + + $header = \substr($recv_buffer, 0, $crlf_pos); + if ($pos = \strpos($header, "\r\nContent-Length: ")) { + $length = $length + (int)\substr($header, $pos + 18, 10); + $has_content_length = true; + } else if (\preg_match("/\r\ncontent-length: ?(\d+)/i", $header, $match)) { + $length = $length + $match[1]; + $has_content_length = true; + } else { + $has_content_length = false; + if (false !== stripos($header, "\r\nTransfer-Encoding:")) { + $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + } + + if ($has_content_length) { + if ($length > $connection->maxPackageSize) { + $connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true); + return 0; + } + } + + if (!isset($recv_buffer[512])) { + $input[$recv_buffer] = $length; + if (\count($input) > 512) { + unset($input[key($input)]); + } + } + + return $length; + } + + /** + * Http decode. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return \Workerman\Protocols\Http\Request + */ + public static function decode($recv_buffer, TcpConnection $connection) + { + static $requests = array(); + $cacheable = static::$_enableCache && !isset($recv_buffer[512]); + if (true === $cacheable && isset($requests[$recv_buffer])) { + $request = $requests[$recv_buffer]; + $request->connection = $connection; + $connection->__request = $request; + $request->properties = array(); + return $request; + } + $request = new static::$_requestClass($recv_buffer); + $request->connection = $connection; + $connection->__request = $request; + if (true === $cacheable) { + $requests[$recv_buffer] = $request; + if (\count($requests) > 512) { + unset($requests[key($requests)]); + } + } + return $request; + } + + /** + * Http encode. + * + * @param string|Response $response + * @param TcpConnection $connection + * @return string + */ + public static function encode($response, TcpConnection $connection) + { + if (isset($connection->__request)) { + $connection->__request->session = null; + $connection->__request->connection = null; + $connection->__request = null; + } + if (!\is_object($response)) { + $ext_header = ''; + if (isset($connection->__header)) { + foreach ($connection->__header as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $ext_header = "$name: $item\r\n"; + } + } else { + $ext_header = "$name: $value\r\n"; + } + } + unset($connection->__header); + } + $body_len = \strlen((string)$response); + return "HTTP/1.1 200 OK\r\nServer: workerman\r\n{$ext_header}Connection: keep-alive\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\n\r\n$response"; + } + + if (isset($connection->__header)) { + $response->withHeaders($connection->__header); + unset($connection->__header); + } + + if (isset($response->file)) { + $file = $response->file['file']; + $offset = $response->file['offset']; + $length = $response->file['length']; + clearstatcache(); + $file_size = (int)\filesize($file); + $body_len = $length > 0 ? $length : $file_size - $offset; + $response->withHeaders(array( + 'Content-Length' => $body_len, + 'Accept-Ranges' => 'bytes', + )); + if ($offset || $length) { + $offset_end = $offset + $body_len - 1; + $response->header('Content-Range', "bytes $offset-$offset_end/$file_size"); + } + if ($body_len < 2 * 1024 * 1024) { + $connection->send((string)$response . file_get_contents($file, false, null, $offset, $body_len), true); + return ''; + } + $handler = \fopen($file, 'r'); + if (false === $handler) { + $connection->close(new Response(403, null, '403 Forbidden')); + return ''; + } + $connection->send((string)$response, true); + static::sendStream($connection, $handler, $offset, $length); + return ''; + } + + return (string)$response; + } + + /** + * Send remainder of a stream to client. + * + * @param TcpConnection $connection + * @param resource $handler + * @param int $offset + * @param int $length + */ + protected static function sendStream(TcpConnection $connection, $handler, $offset = 0, $length = 0) + { + $connection->bufferFull = false; + if ($offset !== 0) { + \fseek($handler, $offset); + } + $offset_end = $offset + $length; + // Read file content from disk piece by piece and send to client. + $do_write = function () use ($connection, $handler, $length, $offset_end) { + // Send buffer not full. + while ($connection->bufferFull === false) { + // Read from disk. + $size = 1024 * 1024; + if ($length !== 0) { + $tell = \ftell($handler); + $remain_size = $offset_end - $tell; + if ($remain_size <= 0) { + fclose($handler); + $connection->onBufferDrain = null; + return; + } + $size = $remain_size > $size ? $size : $remain_size; + } + + $buffer = \fread($handler, $size); + // Read eof. + if ($buffer === '' || $buffer === false) { + fclose($handler); + $connection->onBufferDrain = null; + return; + } + $connection->send($buffer, true); + } + }; + // Send buffer full. + $connection->onBufferFull = function ($connection) { + $connection->bufferFull = true; + }; + // Send buffer drain. + $connection->onBufferDrain = function ($connection) use ($do_write) { + $connection->bufferFull = false; + $do_write(); + }; + $do_write(); + } + + /** + * Set or get uploadTmpDir. + * + * @return bool|string + */ + public static function uploadTmpDir($dir = null) + { + if (null !== $dir) { + static::$_uploadTmpDir = $dir; + } + if (static::$_uploadTmpDir === '') { + if ($upload_tmp_dir = \ini_get('upload_tmp_dir')) { + static::$_uploadTmpDir = $upload_tmp_dir; + } else if ($upload_tmp_dir = \sys_get_temp_dir()) { + static::$_uploadTmpDir = $upload_tmp_dir; + } + } + return static::$_uploadTmpDir; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Chunk.php b/vendor/workerman/workerman/Protocols/Http/Chunk.php new file mode 100644 index 0000000..ab06a9c --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Chunk.php @@ -0,0 +1,48 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + + +/** + * Class Chunk + * @package Workerman\Protocols\Http + */ +class Chunk +{ + /** + * Chunk buffer. + * + * @var string + */ + protected $_buffer = null; + + /** + * Chunk constructor. + * @param string $buffer + */ + public function __construct($buffer) + { + $this->_buffer = $buffer; + } + + /** + * __toString + * + * @return string + */ + public function __toString() + { + return \dechex(\strlen($this->_buffer))."\r\n$this->_buffer\r\n"; + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Request.php b/vendor/workerman/workerman/Protocols/Http/Request.php new file mode 100644 index 0000000..6eca3e4 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Request.php @@ -0,0 +1,694 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Session; +use Workerman\Protocols\Http; +use Workerman\Worker; + +/** + * Class Request + * @package Workerman\Protocols\Http + */ +class Request +{ + /** + * Connection. + * + * @var TcpConnection + */ + public $connection = null; + + /** + * Session instance. + * + * @var Session + */ + public $session = null; + + /** + * Properties. + * + * @var array + */ + public $properties = array(); + + /** + * @var int + */ + public static $maxFileUploads = 1024; + + /** + * Http buffer. + * + * @var string + */ + protected $_buffer = null; + + /** + * Request data. + * + * @var array + */ + protected $_data = null; + + /** + * Enable cache. + * + * @var bool + */ + protected static $_enableCache = true; + + /** + * Is safe. + * + * @var bool + */ + protected $_isSafe = true; + + + /** + * Request constructor. + * + * @param string $buffer + */ + public function __construct($buffer) + { + $this->_buffer = $buffer; + } + + /** + * $_GET. + * + * @param string|null $name + * @param mixed|null $default + * @return mixed|null + */ + public function get($name = null, $default = null) + { + if (!isset($this->_data['get'])) { + $this->parseGet(); + } + if (null === $name) { + return $this->_data['get']; + } + return isset($this->_data['get'][$name]) ? $this->_data['get'][$name] : $default; + } + + /** + * $_POST. + * + * @param string|null $name + * @param mixed|null $default + * @return mixed|null + */ + public function post($name = null, $default = null) + { + if (!isset($this->_data['post'])) { + $this->parsePost(); + } + if (null === $name) { + return $this->_data['post']; + } + return isset($this->_data['post'][$name]) ? $this->_data['post'][$name] : $default; + } + + /** + * Get header item by name. + * + * @param string|null $name + * @param mixed|null $default + * @return array|string|null + */ + public function header($name = null, $default = null) + { + if (!isset($this->_data['headers'])) { + $this->parseHeaders(); + } + if (null === $name) { + return $this->_data['headers']; + } + $name = \strtolower($name); + return isset($this->_data['headers'][$name]) ? $this->_data['headers'][$name] : $default; + } + + /** + * Get cookie item by name. + * + * @param string|null $name + * @param mixed|null $default + * @return array|string|null + */ + public function cookie($name = null, $default = null) + { + if (!isset($this->_data['cookie'])) { + $this->_data['cookie'] = array(); + \parse_str(\preg_replace('/; ?/', '&', $this->header('cookie', '')), $this->_data['cookie']); + } + if ($name === null) { + return $this->_data['cookie']; + } + return isset($this->_data['cookie'][$name]) ? $this->_data['cookie'][$name] : $default; + } + + /** + * Get upload files. + * + * @param string|null $name + * @return array|null + */ + public function file(?string $name = null): mixed + { + if (!isset($this->_data['files'])) { + $this->parsePost(); + } + if (null === $name) { + return $this->_data['files']; + } + return isset($this->_data['files'][$name]) ? $this->_data['files'][$name] : null; + } + + /** + * Get method. + * + * @return string + */ + public function method() + { + if (!isset($this->_data['method'])) { + $this->parseHeadFirstLine(); + } + return $this->_data['method']; + } + + /** + * Get http protocol version. + * + * @return string + */ + public function protocolVersion() + { + if (!isset($this->_data['protocolVersion'])) { + $this->parseProtocolVersion(); + } + return $this->_data['protocolVersion']; + } + + /** + * Get host. + * + * @param bool $without_port + * @return string + */ + public function host($without_port = false) + { + $host = $this->header('host'); + if ($host && $without_port) { + return preg_replace('/:\d{1,5}$/', '', $host); + } + return $host; + } + + /** + * Get uri. + * + * @return mixed + */ + public function uri() + { + if (!isset($this->_data['uri'])) { + $this->parseHeadFirstLine(); + } + return $this->_data['uri']; + } + + /** + * Get path. + * + * @return mixed + */ + public function path() + { + if (!isset($this->_data['path'])) { + $this->_data['path'] = (string)\parse_url($this->uri(), PHP_URL_PATH); + } + return $this->_data['path']; + } + + /** + * Get query string. + * + * @return mixed + */ + public function queryString() + { + if (!isset($this->_data['query_string'])) { + $this->_data['query_string'] = (string)\parse_url($this->uri(), PHP_URL_QUERY); + } + return $this->_data['query_string']; + } + + /** + * Get session. + * + * @return bool|\Workerman\Protocols\Http\Session + */ + public function session() + { + if ($this->session === null) { + $session_id = $this->sessionId(); + if ($session_id === false) { + return false; + } + $this->session = new Session($session_id); + } + return $this->session; + } + + /** + * Get/Set session id. + * + * @param $session_id + * @return string + */ + public function sessionId($session_id = null) + { + if ($session_id) { + unset($this->sid); + } + if (!isset($this->sid)) { + $session_name = Session::$name; + $sid = $session_id ? '' : $this->cookie($session_name); + if ($sid === '' || $sid === null) { + if ($this->connection === null) { + Worker::safeEcho('Request->session() fail, header already send'); + return false; + } + $sid = $session_id ? $session_id : static::createSessionId(); + $cookie_params = Session::getCookieParams(); + $this->connection->__header['Set-Cookie'] = array($session_name . '=' . $sid + . (empty($cookie_params['domain']) ? '' : '; Domain=' . $cookie_params['domain']) + . (empty($cookie_params['lifetime']) ? '' : '; Max-Age=' . $cookie_params['lifetime']) + . (empty($cookie_params['path']) ? '' : '; Path=' . $cookie_params['path']) + . (empty($cookie_params['samesite']) ? '' : '; SameSite=' . $cookie_params['samesite']) + . (!$cookie_params['secure'] ? '' : '; Secure') + . (!$cookie_params['httponly'] ? '' : '; HttpOnly')); + } + $this->sid = $sid; + } + return $this->sid; + } + + /** + * Get http raw head. + * + * @return string + */ + public function rawHead() + { + if (!isset($this->_data['head'])) { + $this->_data['head'] = \strstr($this->_buffer, "\r\n\r\n", true); + } + return $this->_data['head']; + } + + /** + * Get http raw body. + * + * @return string + */ + public function rawBody() + { + return \substr($this->_buffer, \strpos($this->_buffer, "\r\n\r\n") + 4); + } + + /** + * Get raw buffer. + * + * @return string + */ + public function rawBuffer() + { + return $this->_buffer; + } + + /** + * Enable or disable cache. + * + * @param mixed $value + */ + public static function enableCache($value) + { + static::$_enableCache = (bool)$value; + } + + /** + * Parse first line of http header buffer. + * + * @return void + */ + protected function parseHeadFirstLine() + { + $first_line = \strstr($this->_buffer, "\r\n", true); + $tmp = \explode(' ', $first_line, 3); + $this->_data['method'] = $tmp[0]; + $this->_data['uri'] = isset($tmp[1]) ? $tmp[1] : '/'; + } + + /** + * Parse protocol version. + * + * @return void + */ + protected function parseProtocolVersion() + { + $first_line = \strstr($this->_buffer, "\r\n", true); + $protoco_version = substr(\strstr($first_line, 'HTTP/'), 5); + $this->_data['protocolVersion'] = $protoco_version ? $protoco_version : '1.0'; + } + + /** + * Parse headers. + * + * @return void + */ + protected function parseHeaders() + { + static $cache = []; + $this->_data['headers'] = array(); + $raw_head = $this->rawHead(); + $end_line_position = \strpos($raw_head, "\r\n"); + if ($end_line_position === false) { + return; + } + $head_buffer = \substr($raw_head, $end_line_position + 2); + $cacheable = static::$_enableCache && !isset($head_buffer[2048]); + if ($cacheable && isset($cache[$head_buffer])) { + $this->_data['headers'] = $cache[$head_buffer]; + return; + } + $head_data = \explode("\r\n", $head_buffer); + foreach ($head_data as $content) { + if (false !== \strpos($content, ':')) { + list($key, $value) = \explode(':', $content, 2); + $key = \strtolower($key); + $value = \ltrim($value); + } else { + $key = \strtolower($content); + $value = ''; + } + if (isset($this->_data['headers'][$key])) { + $this->_data['headers'][$key] = "{$this->_data['headers'][$key]},$value"; + } else { + $this->_data['headers'][$key] = $value; + } + } + if ($cacheable) { + $cache[$head_buffer] = $this->_data['headers']; + if (\count($cache) > 128) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse head. + * + * @return void + */ + protected function parseGet() + { + static $cache = []; + $query_string = $this->queryString(); + $this->_data['get'] = array(); + if ($query_string === '') { + return; + } + $cacheable = static::$_enableCache && !isset($query_string[1024]); + if ($cacheable && isset($cache[$query_string])) { + $this->_data['get'] = $cache[$query_string]; + return; + } + \parse_str($query_string, $this->_data['get']); + if ($cacheable) { + $cache[$query_string] = $this->_data['get']; + if (\count($cache) > 256) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse post. + * + * @return void + */ + protected function parsePost() + { + static $cache = []; + $this->_data['post'] = $this->_data['files'] = array(); + $content_type = $this->header('content-type', ''); + if (\preg_match('/boundary="?(\S+)"?/', $content_type, $match)) { + $http_post_boundary = '--' . $match[1]; + $this->parseUploadFiles($http_post_boundary); + return; + } + $body_buffer = $this->rawBody(); + if ($body_buffer === '') { + return; + } + $cacheable = static::$_enableCache && !isset($body_buffer[1024]); + if ($cacheable && isset($cache[$body_buffer])) { + $this->_data['post'] = $cache[$body_buffer]; + return; + } + if (\preg_match('/\bjson\b/i', $content_type)) { + $this->_data['post'] = (array) json_decode($body_buffer, true); + } else { + \parse_str($body_buffer, $this->_data['post']); + } + if ($cacheable) { + $cache[$body_buffer] = $this->_data['post']; + if (\count($cache) > 256) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse upload files. + * + * @param string $http_post_boundary + * @return void + */ + protected function parseUploadFiles($http_post_boundary) + { + $http_post_boundary = \trim($http_post_boundary, '"'); + $buffer = $this->_buffer; + $post_encode_string = ''; + $files_encode_string = ''; + $files = []; + $boday_position = strpos($buffer, "\r\n\r\n") + 4; + $offset = $boday_position + strlen($http_post_boundary) + 2; + $max_count = static::$maxFileUploads; + while ($max_count-- > 0 && $offset) { + $offset = $this->parseUploadFile($http_post_boundary, $offset, $post_encode_string, $files_encode_string, $files); + } + if ($post_encode_string) { + parse_str($post_encode_string, $this->_data['post']); + } + + if ($files_encode_string) { + parse_str($files_encode_string, $this->_data['files']); + \array_walk_recursive($this->_data['files'], function (&$value) use ($files) { + $value = $files[$value]; + }); + } + } + + /** + * @param $boundary + * @param $section_start_offset + * @return int + */ + protected function parseUploadFile($boundary, $section_start_offset, &$post_encode_string, &$files_encode_str, &$files) + { + $file = []; + $boundary = "\r\n$boundary"; + if (\strlen($this->_buffer) < $section_start_offset) { + return 0; + } + $section_end_offset = \strpos($this->_buffer, $boundary, $section_start_offset); + if (!$section_end_offset) { + return 0; + } + $content_lines_end_offset = \strpos($this->_buffer, "\r\n\r\n", $section_start_offset); + if (!$content_lines_end_offset || $content_lines_end_offset + 4 > $section_end_offset) { + return 0; + } + $content_lines_str = \substr($this->_buffer, $section_start_offset, $content_lines_end_offset - $section_start_offset); + $content_lines = \explode("\r\n", trim($content_lines_str . "\r\n")); + $boundary_value = \substr($this->_buffer, $content_lines_end_offset + 4, $section_end_offset - $content_lines_end_offset - 4); + $upload_key = false; + foreach ($content_lines as $content_line) { + if (!\strpos($content_line, ': ')) { + return 0; + } + list($key, $value) = \explode(': ', $content_line); + switch (strtolower($key)) { + case "content-disposition": + // Is file data. + if (\preg_match('/name="(.*?)"; filename="(.*?)"/i', $value, $match)) { + $error = 0; + $tmp_file = ''; + $file_name = $match[2]; + $size = \strlen($boundary_value); + $tmp_upload_dir = HTTP::uploadTmpDir(); + if (!$tmp_upload_dir) { + $error = UPLOAD_ERR_NO_TMP_DIR; + } else if ($boundary_value === '' && $file_name === '') { + $error = UPLOAD_ERR_NO_FILE; + } else { + $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.'); + if ($tmp_file === false || false === \file_put_contents($tmp_file, $boundary_value)) { + $error = UPLOAD_ERR_CANT_WRITE; + } + } + $upload_key = $match[1]; + // Parse upload files. + $file = [ + 'name' => $file_name, + 'tmp_name' => $tmp_file, + 'size' => $size, + 'error' => $error, + 'type' => '', + ]; + break; + } // Is post field. + else { + // Parse $_POST. + if (\preg_match('/name="(.*?)"$/', $value, $match)) { + $k = $match[1]; + $post_encode_string .= \urlencode($k) . "=" . \urlencode($boundary_value) . '&'; + } + return $section_end_offset + \strlen($boundary) + 2; + } + break; + case "content-type": + $file['type'] = \trim($value); + break; + } + } + if ($upload_key === false) { + return 0; + } + $files_encode_str .= \urlencode($upload_key) . '=' . \count($files) . '&'; + $files[] = $file; + + return $section_end_offset + \strlen($boundary) + 2; + } + + /** + * Create session id. + * + * @return string + */ + protected static function createSessionId() + { + return \bin2hex(\pack('d', \microtime(true)) . random_bytes(8)); + } + + /** + * Setter. + * + * @param string $name + * @param mixed $value + * @return void + */ + public function __set($name, $value) + { + $this->properties[$name] = $value; + } + + /** + * Getter. + * + * @param string $name + * @return mixed|null + */ + public function __get($name) + { + return isset($this->properties[$name]) ? $this->properties[$name] : null; + } + + /** + * Isset. + * + * @param string $name + * @return bool + */ + public function __isset($name) + { + return isset($this->properties[$name]); + } + + /** + * Unset. + * + * @param string $name + * @return void + */ + public function __unset($name) + { + unset($this->properties[$name]); + } + + /** + * __toString. + */ + public function __toString() + { + return $this->_buffer; + } + + /** + * __wakeup. + * + * @return void + */ + public function __wakeup() + { + $this->_isSafe = false; + } + + /** + * __destruct. + * + * @return void + */ + public function __destruct() + { + if (isset($this->_data['files']) && $this->_isSafe) { + \clearstatcache(); + \array_walk_recursive($this->_data['files'], function($value, $key){ + if ($key === 'tmp_name') { + if (\is_file($value)) { + \unlink($value); + } + } + }); + } + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Response.php b/vendor/workerman/workerman/Protocols/Http/Response.php new file mode 100644 index 0000000..e423727 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Response.php @@ -0,0 +1,458 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +/** + * Class Response + * @package Workerman\Protocols\Http + */ +class Response +{ + /** + * Header data. + * + * @var array + */ + protected $_header = null; + + /** + * Http status. + * + * @var int + */ + protected $_status = null; + + /** + * Http reason. + * + * @var string + */ + protected $_reason = null; + + /** + * Http version. + * + * @var string + */ + protected $_version = '1.1'; + + /** + * Http body. + * + * @var string + */ + protected $_body = null; + + /** + * Send file info + * + * @var array + */ + public $file = null; + + /** + * Mine type map. + * @var array + */ + protected static $_mimeTypeMap = null; + + /** + * Phrases. + * + * @var array + */ + protected static $_phrases = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ); + + /** + * Init. + * + * @return void + */ + public static function init() { + static::initMimeTypeMap(); + } + + /** + * Response constructor. + * + * @param int $status + * @param array $headers + * @param string $body + */ + public function __construct( + $status = 200, + $headers = array(), + $body = '' + ) { + $this->_status = $status; + $this->_header = $headers; + $this->_body = (string)$body; + } + + /** + * Set header. + * + * @param string $name + * @param string $value + * @return $this + */ + public function header($name, $value) { + $this->_header[$name] = $value; + return $this; + } + + /** + * Set header. + * + * @param string $name + * @param string $value + * @return Response + */ + public function withHeader($name, $value) { + return $this->header($name, $value); + } + + /** + * Set headers. + * + * @param array $headers + * @return $this + */ + public function withHeaders($headers) { + $this->_header = \array_merge_recursive($this->_header, $headers); + return $this; + } + + /** + * Remove header. + * + * @param string $name + * @return $this + */ + public function withoutHeader($name) { + unset($this->_header[$name]); + return $this; + } + + /** + * Get header. + * + * @param string $name + * @return null|array|string + */ + public function getHeader($name) { + if (!isset($this->_header[$name])) { + return null; + } + return $this->_header[$name]; + } + + /** + * Get headers. + * + * @return array + */ + public function getHeaders() { + return $this->_header; + } + + /** + * Set status. + * + * @param int $code + * @param string|null $reason_phrase + * @return $this + */ + public function withStatus($code, $reason_phrase = null) { + $this->_status = $code; + $this->_reason = $reason_phrase; + return $this; + } + + /** + * Get status code. + * + * @return int + */ + public function getStatusCode() { + return $this->_status; + } + + /** + * Get reason phrase. + * + * @return string + */ + public function getReasonPhrase() { + return $this->_reason; + } + + /** + * Set protocol version. + * + * @param int $version + * @return $this + */ + public function withProtocolVersion($version) { + $this->_version = $version; + return $this; + } + + /** + * Set http body. + * + * @param string $body + * @return $this + */ + public function withBody($body) { + $this->_body = $body; + return $this; + } + + /** + * Get http raw body. + * + * @return string + */ + public function rawBody() { + return $this->_body; + } + + /** + * Send file. + * + * @param string $file + * @param int $offset + * @param int $length + * @return $this + */ + public function withFile($file, $offset = 0, $length = 0) { + if (!\is_file($file)) { + return $this->withStatus(404)->withBody('

404 Not Found

'); + } + $this->file = array('file' => $file, 'offset' => $offset, 'length' => $length); + return $this; + } + + /** + * Set cookie. + * + * @param $name + * @param string $value + * @param int $max_age + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $http_only + * @param string $same_site + * @return $this + */ + public function cookie($name, $value = '', $max_age = null, $path = '', $domain = '', $secure = false, $http_only = false, $same_site = '') + { + $this->_header['Set-Cookie'][] = $name . '=' . \rawurlencode($value) + . (empty($domain) ? '' : '; Domain=' . $domain) + . ($max_age === null ? '' : '; Max-Age=' . $max_age) + . (empty($path) ? '' : '; Path=' . $path) + . (!$secure ? '' : '; Secure') + . (!$http_only ? '' : '; HttpOnly') + . (empty($same_site) ? '' : '; SameSite=' . $same_site); + return $this; + } + + /** + * Create header for file. + * + * @param array $file_info + * @return string + */ + protected function createHeadForFile($file_info) + { + $file = $file_info['file']; + $reason = $this->_reason ? $this->_reason : static::$_phrases[$this->_status]; + $head = "HTTP/{$this->_version} {$this->_status} $reason\r\n"; + $headers = $this->_header; + if (!isset($headers['Server'])) { + $head .= "Server: workerman\r\n"; + } + foreach ($headers as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $head .= "$name: $item\r\n"; + } + continue; + } + $head .= "$name: $value\r\n"; + } + + if (!isset($headers['Connection'])) { + $head .= "Connection: keep-alive\r\n"; + } + + $file_info = \pathinfo($file); + $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; + $base_name = isset($file_info['basename']) ? $file_info['basename'] : 'unknown'; + if (!isset($headers['Content-Type'])) { + if (isset(self::$_mimeTypeMap[$extension])) { + $head .= "Content-Type: " . self::$_mimeTypeMap[$extension] . "\r\n"; + } else { + $head .= "Content-Type: application/octet-stream\r\n"; + } + } + + if (!isset($headers['Content-Disposition']) && !isset(self::$_mimeTypeMap[$extension])) { + $head .= "Content-Disposition: attachment; filename=\"$base_name\"\r\n"; + } + + if (!isset($headers['Last-Modified'])) { + if ($mtime = \filemtime($file)) { + $head .= 'Last-Modified: '. \gmdate('D, d M Y H:i:s', $mtime) . ' GMT' . "\r\n"; + } + } + + return "{$head}\r\n"; + } + + /** + * __toString. + * + * @return string + */ + public function __toString() + { + if (isset($this->file)) { + return $this->createHeadForFile($this->file); + } + + $reason = $this->_reason ? $this->_reason : static::$_phrases[$this->_status]; + $body_len = \strlen($this->_body); + if (empty($this->_header)) { + return "HTTP/{$this->_version} {$this->_status} $reason\r\nServer: workerman\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\nConnection: keep-alive\r\n\r\n{$this->_body}"; + } + + $head = "HTTP/{$this->_version} {$this->_status} $reason\r\n"; + $headers = $this->_header; + if (!isset($headers['Server'])) { + $head .= "Server: workerman\r\n"; + } + foreach ($headers as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $head .= "$name: $item\r\n"; + } + continue; + } + $head .= "$name: $value\r\n"; + } + + if (!isset($headers['Connection'])) { + $head .= "Connection: keep-alive\r\n"; + } + + if (!isset($headers['Content-Type'])) { + $head .= "Content-Type: text/html;charset=utf-8\r\n"; + } else if ($headers['Content-Type'] === 'text/event-stream') { + return $head . $this->_body; + } + + if (!isset($headers['Transfer-Encoding'])) { + $head .= "Content-Length: $body_len\r\n\r\n"; + } else { + return $body_len ? "$head\r\n" . dechex($body_len) . "\r\n{$this->_body}\r\n" : "$head\r\n"; + } + + // The whole http package + return $head . $this->_body; + } + + /** + * Init mime map. + * + * @return void + */ + public static function initMimeTypeMap() + { + $mime_file = __DIR__ . '/mime.types'; + $items = \file($mime_file, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES); + foreach ($items as $content) { + if (\preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { + $mime_type = $match[1]; + $extension_var = $match[2]; + $extension_array = \explode(' ', \substr($extension_var, 0, -1)); + foreach ($extension_array as $file_extension) { + static::$_mimeTypeMap[$file_extension] = $mime_type; + } + } + } + } +} +Response::init(); diff --git a/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php b/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php new file mode 100644 index 0000000..a6e9e0d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php @@ -0,0 +1,64 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +/** + * Class ServerSentEvents + * @package Workerman\Protocols\Http + */ +class ServerSentEvents +{ + /** + * Data. + * @var array + */ + protected $_data = null; + + /** + * ServerSentEvents constructor. + * $data for example ['event'=>'ping', 'data' => 'some thing', 'id' => 1000, 'retry' => 5000] + * @param array $data + */ + public function __construct(array $data) + { + $this->_data = $data; + } + + /** + * __toString. + * + * @return string + */ + public function __toString() + { + $buffer = ''; + $data = $this->_data; + if (isset($data[''])) { + $buffer = ": {$data['']}\n"; + } + if (isset($data['event'])) { + $buffer .= "event: {$data['event']}\n"; + } + if (isset($data['id'])) { + $buffer .= "id: {$data['id']}\n"; + } + if (isset($data['retry'])) { + $buffer .= "retry: {$data['retry']}\n"; + } + if (isset($data['data'])) { + $buffer .= 'data: ' . str_replace("\n", "\ndata: ", $data['data']) . "\n"; + } + return $buffer . "\n"; + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Session.php b/vendor/workerman/workerman/Protocols/Http/Session.php new file mode 100644 index 0000000..a0c2417 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session.php @@ -0,0 +1,461 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols\Http; + +use Workerman\Protocols\Http\Session\SessionHandlerInterface; + +/** + * Class Session + * @package Workerman\Protocols\Http + */ +class Session +{ + /** + * Session andler class which implements SessionHandlerInterface. + * + * @var string + */ + protected static $_handlerClass = 'Workerman\Protocols\Http\Session\FileSessionHandler'; + + /** + * Parameters of __constructor for session handler class. + * + * @var null + */ + protected static $_handlerConfig = null; + + /** + * Session name. + * + * @var string + */ + public static $name = 'PHPSID'; + + /** + * Auto update timestamp. + * + * @var bool + */ + public static $autoUpdateTimestamp = false; + + /** + * Session lifetime. + * + * @var int + */ + public static $lifetime = 1440; + + /** + * Cookie lifetime. + * + * @var int + */ + public static $cookieLifetime = 1440; + + /** + * Session cookie path. + * + * @var string + */ + public static $cookiePath = '/'; + + /** + * Session cookie domain. + * + * @var string + */ + public static $domain = ''; + + /** + * HTTPS only cookies. + * + * @var bool + */ + public static $secure = false; + + /** + * HTTP access only. + * + * @var bool + */ + public static $httpOnly = true; + + /** + * Same-site cookies. + * + * @var string + */ + public static $sameSite = ''; + + /** + * Gc probability. + * + * @var int[] + */ + public static $gcProbability = [1, 1000]; + + /** + * Session handler instance. + * + * @var SessionHandlerInterface + */ + protected static $_handler = null; + + /** + * Session data. + * + * @var array + */ + protected $_data = []; + + /** + * Session changed and need to save. + * + * @var bool + */ + protected $_needSave = false; + + /** + * Session id. + * + * @var null + */ + protected $_sessionId = null; + + /** + * Is safe. + * + * @var bool + */ + protected $_isSafe = true; + + /** + * Session constructor. + * + * @param string $session_id + */ + public function __construct($session_id) + { + static::checkSessionId($session_id); + if (static::$_handler === null) { + static::initHandler(); + } + $this->_sessionId = $session_id; + if ($data = static::$_handler->read($session_id)) { + $this->_data = \unserialize($data); + } + } + + /** + * Get session id. + * + * @return string + */ + public function getId() + { + return $this->_sessionId; + } + + /** + * Get session. + * + * @param string $name + * @param mixed|null $default + * @return mixed|null + */ + public function get($name, $default = null) + { + return isset($this->_data[$name]) ? $this->_data[$name] : $default; + } + + /** + * Store data in the session. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $this->_data[$name] = $value; + $this->_needSave = true; + } + + /** + * Delete an item from the session. + * + * @param string $name + */ + public function delete($name) + { + unset($this->_data[$name]); + $this->_needSave = true; + } + + /** + * Retrieve and delete an item from the session. + * + * @param string $name + * @param mixed|null $default + * @return mixed|null + */ + public function pull($name, $default = null) + { + $value = $this->get($name, $default); + $this->delete($name); + return $value; + } + + /** + * Store data in the session. + * + * @param string|array $key + * @param mixed|null $value + */ + public function put($key, $value = null) + { + if (!\is_array($key)) { + $this->set($key, $value); + return; + } + + foreach ($key as $k => $v) { + $this->_data[$k] = $v; + } + $this->_needSave = true; + } + + /** + * Remove a piece of data from the session. + * + * @param string $name + */ + public function forget($name) + { + if (\is_scalar($name)) { + $this->delete($name); + return; + } + if (\is_array($name)) { + foreach ($name as $key) { + unset($this->_data[$key]); + } + } + $this->_needSave = true; + } + + /** + * Retrieve all the data in the session. + * + * @return array + */ + public function all() + { + return $this->_data; + } + + /** + * Remove all data from the session. + * + * @return void + */ + public function flush() + { + $this->_needSave = true; + $this->_data = []; + } + + /** + * Determining If An Item Exists In The Session. + * + * @param string $name + * @return bool + */ + public function has($name) + { + return isset($this->_data[$name]); + } + + /** + * To determine if an item is present in the session, even if its value is null. + * + * @param string $name + * @return bool + */ + public function exists($name) + { + return \array_key_exists($name, $this->_data); + } + + /** + * Save session to store. + * + * @return void + */ + public function save() + { + if ($this->_needSave) { + if (empty($this->_data)) { + static::$_handler->destroy($this->_sessionId); + } else { + static::$_handler->write($this->_sessionId, \serialize($this->_data)); + } + } elseif (static::$autoUpdateTimestamp) { + static::refresh(); + } + $this->_needSave = false; + } + + /** + * Refresh session expire time. + * + * @return bool + */ + public function refresh() + { + static::$_handler->updateTimestamp($this->getId()); + } + + /** + * Init. + * + * @return void + */ + public static function init() + { + if (($gc_probability = (int)\ini_get('session.gc_probability')) && ($gc_divisor = (int)\ini_get('session.gc_divisor'))) { + static::$gcProbability = [$gc_probability, $gc_divisor]; + } + + if ($gc_max_life_time = \ini_get('session.gc_maxlifetime')) { + self::$lifetime = (int)$gc_max_life_time; + } + + $session_cookie_params = \session_get_cookie_params(); + static::$cookieLifetime = $session_cookie_params['lifetime']; + static::$cookiePath = $session_cookie_params['path']; + static::$domain = $session_cookie_params['domain']; + static::$secure = $session_cookie_params['secure']; + static::$httpOnly = $session_cookie_params['httponly']; + } + + /** + * Set session handler class. + * + * @param mixed|null $class_name + * @param mixed|null $config + * @return string + */ + public static function handlerClass($class_name = null, $config = null) + { + if ($class_name) { + static::$_handlerClass = $class_name; + } + if ($config) { + static::$_handlerConfig = $config; + } + return static::$_handlerClass; + } + + /** + * Get cookie params. + * + * @return array + */ + public static function getCookieParams() + { + return [ + 'lifetime' => static::$cookieLifetime, + 'path' => static::$cookiePath, + 'domain' => static::$domain, + 'secure' => static::$secure, + 'httponly' => static::$httpOnly, + 'samesite' => static::$sameSite, + ]; + } + + /** + * Init handler. + * + * @return void + */ + protected static function initHandler() + { + if (static::$_handlerConfig === null) { + static::$_handler = new static::$_handlerClass(); + } else { + static::$_handler = new static::$_handlerClass(static::$_handlerConfig); + } + } + + /** + * GC sessions. + * + * @return void + */ + public function gc() + { + static::$_handler->gc(static::$lifetime); + } + + /** + * __wakeup. + * + * @return void + */ + public function __wakeup() + { + $this->_isSafe = false; + } + + /** + * __destruct. + * + * @return void + */ + public function __destruct() + { + if (!$this->_isSafe) { + return; + } + $this->save(); + if (\random_int(1, static::$gcProbability[1]) <= static::$gcProbability[0]) { + $this->gc(); + } + } + + /** + * Check session id. + * + * @param string $session_id + */ + protected static function checkSessionId($session_id) + { + if (!\preg_match('/^[a-zA-Z0-9"]+$/', $session_id)) { + throw new SessionException("session_id $session_id is invalid"); + } + } +} + +/** + * Class SessionException + * @package Workerman\Protocols\Http + */ +class SessionException extends \RuntimeException +{ + +} + +// Init session. +Session::init(); diff --git a/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php new file mode 100644 index 0000000..a7cefbd --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php @@ -0,0 +1,183 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; + +/** + * Class FileSessionHandler + * @package Workerman\Protocols\Http\Session + */ +class FileSessionHandler implements SessionHandlerInterface +{ + /** + * Session save path. + * + * @var string + */ + protected static $_sessionSavePath = null; + + /** + * Session file prefix. + * + * @var string + */ + protected static $_sessionFilePrefix = 'session_'; + + /** + * Init. + */ + public static function init() { + $save_path = @\session_save_path(); + if (!$save_path || \strpos($save_path, 'tcp://') === 0) { + $save_path = \sys_get_temp_dir(); + } + static::sessionSavePath($save_path); + } + + /** + * FileSessionHandler constructor. + * @param array $config + */ + public function __construct($config = array()) { + if (isset($config['save_path'])) { + static::sessionSavePath($config['save_path']); + } + } + + /** + * {@inheritdoc} + */ + public function open($save_path, $name) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + $session_file = static::sessionFile($session_id); + \clearstatcache(); + if (\is_file($session_file)) { + if (\time() - \filemtime($session_file) > Session::$lifetime) { + \unlink($session_file); + return ''; + } + $data = \file_get_contents($session_file); + return $data ? $data : ''; + } + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($session_id, $session_data) + { + $temp_file = static::$_sessionSavePath . uniqid(bin2hex(random_bytes(8)), true); + if (!\file_put_contents($temp_file, $session_data)) { + return false; + } + return \rename($temp_file, static::sessionFile($session_id)); + } + + /** + * Update sesstion modify time. + * + * @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php + * @see https://www.php.net/manual/zh/function.touch.php + * + * @param string $id Session id. + * @param string $data Session Data. + * + * @return bool + */ + public function updateTimestamp($id, $data = "") + { + $session_file = static::sessionFile($id); + if (!file_exists($session_file)) { + return false; + } + // set file modify time to current time + $set_modify_time = \touch($session_file); + // clear file stat cache + \clearstatcache(); + return $set_modify_time; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($session_id) + { + $session_file = static::sessionFile($session_id); + if (\is_file($session_file)) { + \unlink($session_file); + } + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) { + $time_now = \time(); + foreach (\glob(static::$_sessionSavePath . static::$_sessionFilePrefix . '*') as $file) { + if(\is_file($file) && $time_now - \filemtime($file) > $maxlifetime) { + \unlink($file); + } + } + } + + /** + * Get session file path. + * + * @param string $session_id + * @return string + */ + protected static function sessionFile($session_id) { + return static::$_sessionSavePath.static::$_sessionFilePrefix.$session_id; + } + + /** + * Get or set session file path. + * + * @param string $path + * @return string + */ + public static function sessionSavePath($path) { + if ($path) { + if ($path[\strlen($path)-1] !== DIRECTORY_SEPARATOR) { + $path .= DIRECTORY_SEPARATOR; + } + static::$_sessionSavePath = $path; + if (!\is_dir($path)) { + \mkdir($path, 0777, true); + } + } + return $path; + } +} + +FileSessionHandler::init(); \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php new file mode 100644 index 0000000..281759a --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php @@ -0,0 +1,46 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; + +class RedisClusterSessionHandler extends RedisSessionHandler +{ + public function __construct($config) + { + $timeout = isset($config['timeout']) ? $config['timeout'] : 2; + $read_timeout = isset($config['read_timeout']) ? $config['read_timeout'] : $timeout; + $persistent = isset($config['persistent']) ? $config['persistent'] : false; + $auth = isset($config['auth']) ? $config['auth'] : ''; + if ($auth) { + $this->_redis = new \RedisCluster(null, $config['host'], $timeout, $read_timeout, $persistent, $auth); + } else { + $this->_redis = new \RedisCluster(null, $config['host'], $timeout, $read_timeout, $persistent); + } + if (empty($config['prefix'])) { + $config['prefix'] = 'redis_session_'; + } + $this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']); + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + return $this->_redis->get($session_id); + } + +} diff --git a/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php new file mode 100644 index 0000000..e1b5bd5 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php @@ -0,0 +1,154 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; +use Workerman\Timer; +use RedisException; + +/** + * Class RedisSessionHandler + * @package Workerman\Protocols\Http\Session + */ +class RedisSessionHandler implements SessionHandlerInterface +{ + + /** + * @var \Redis + */ + protected $_redis; + + /** + * @var array + */ + protected $_config; + + /** + * RedisSessionHandler constructor. + * @param array $config = [ + * 'host' => '127.0.0.1', + * 'port' => 6379, + * 'timeout' => 2, + * 'auth' => '******', + * 'database' => 2, + * 'prefix' => 'redis_session_', + * 'ping' => 55, + * ] + */ + public function __construct($config) + { + if (false === extension_loaded('redis')) { + throw new \RuntimeException('Please install redis extension.'); + } + + if (!isset($config['timeout'])) { + $config['timeout'] = 2; + } + + $this->_config = $config; + + $this->connect(); + + Timer::add(!empty($config['ping']) ? $config['ping'] : 55, function () { + $this->_redis->get('ping'); + }); + } + + public function connect() + { + $config = $this->_config; + + $this->_redis = new \Redis(); + if (false === $this->_redis->connect($config['host'], $config['port'], $config['timeout'])) { + throw new \RuntimeException("Redis connect {$config['host']}:{$config['port']} fail."); + } + if (!empty($config['auth'])) { + $this->_redis->auth($config['auth']); + } + if (!empty($config['database'])) { + $this->_redis->select($config['database']); + } + if (empty($config['prefix'])) { + $config['prefix'] = 'redis_session_'; + } + $this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']); + } + + /** + * {@inheritdoc} + */ + public function open($save_path, $name) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + try { + return $this->_redis->get($session_id); + } catch (RedisException $e) { + $msg = strtolower($e->getMessage()); + if ($msg === 'connection lost' || strpos($msg, 'went away')) { + $this->connect(); + return $this->_redis->get($session_id); + } + throw $e; + } + + } + + /** + * {@inheritdoc} + */ + public function write($session_id, $session_data) + { + return true === $this->_redis->setex($session_id, Session::$lifetime, $session_data); + } + + /** + * {@inheritdoc} + */ + public function updateTimestamp($id, $data = "") + { + return true === $this->_redis->expire($id, Session::$lifetime); + } + + /** + * {@inheritdoc} + */ + public function destroy($session_id) + { + $this->_redis->del($session_id); + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return true; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php b/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php new file mode 100644 index 0000000..23a47f2 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php @@ -0,0 +1,114 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +interface SessionHandlerInterface +{ + /** + * Close the session + * @link http://php.net/manual/en/sessionhandlerinterface.close.php + * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function close(); + + /** + * Destroy a session + * @link http://php.net/manual/en/sessionhandlerinterface.destroy.php + * @param string $session_id The session ID being destroyed. + * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function destroy($session_id); + + /** + * Cleanup old sessions + * @link http://php.net/manual/en/sessionhandlerinterface.gc.php + * @param int $maxlifetime

+ * Sessions that have not updated for + * the last maxlifetime seconds will be removed. + *

+ * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function gc($maxlifetime); + + /** + * Initialize session + * @link http://php.net/manual/en/sessionhandlerinterface.open.php + * @param string $save_path The path where to store/retrieve the session. + * @param string $name The session name. + * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function open($save_path, $name); + + + /** + * Read session data + * @link http://php.net/manual/en/sessionhandlerinterface.read.php + * @param string $session_id The session id to read data for. + * @return string

+ * Returns an encoded string of the read data. + * If nothing was read, it must return an empty string. + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function read($session_id); + + /** + * Write session data + * @link http://php.net/manual/en/sessionhandlerinterface.write.php + * @param string $session_id The session id. + * @param string $session_data

+ * The encoded session data. This data is the + * result of the PHP internally encoding + * the $_SESSION superglobal to a serialized + * string and passing it as this parameter. + * Please note sessions use an alternative serialization method. + *

+ * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function write($session_id, $session_data); + + /** + * Update sesstion modify time. + * + * @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php + * + * @param string $id Session id. + * @param string $data Session Data. + * + * @return bool + */ + public function updateTimestamp($id, $data = ""); + +} diff --git a/vendor/workerman/workerman/Protocols/Http/mime.types b/vendor/workerman/workerman/Protocols/Http/mime.types new file mode 100644 index 0000000..e6ccf0a --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/mime.types @@ -0,0 +1,90 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/font-woff woff; + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; + application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; + font/ttf ttf; +} diff --git a/vendor/workerman/workerman/Protocols/ProtocolInterface.php b/vendor/workerman/workerman/Protocols/ProtocolInterface.php new file mode 100644 index 0000000..4fea87d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/ProtocolInterface.php @@ -0,0 +1,52 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Protocol interface + */ +interface ProtocolInterface +{ + /** + * Check the integrity of the package. + * Please return the length of package. + * If length is unknow please return 0 that mean wating more data. + * If the package has something wrong please return false the connection will be closed. + * + * @param string $recv_buffer + * @param ConnectionInterface $connection + * @return int|false + */ + public static function input($recv_buffer, ConnectionInterface $connection); + + /** + * Decode package and emit onMessage($message) callback, $message is the result that decode returned. + * + * @param string $recv_buffer + * @param ConnectionInterface $connection + * @return mixed + */ + public static function decode($recv_buffer, ConnectionInterface $connection); + + /** + * Encode package brefore sending to client. + * + * @param mixed $data + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($data, ConnectionInterface $connection); +} diff --git a/vendor/workerman/workerman/Protocols/Text.php b/vendor/workerman/workerman/Protocols/Text.php new file mode 100644 index 0000000..407ea2d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Text.php @@ -0,0 +1,70 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Text Protocol. + */ +class Text +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Judge whether the package length exceeds the limit. + if (isset($connection->maxPackageSize) && \strlen($buffer) >= $connection->maxPackageSize) { + $connection->close(); + return 0; + } + // Find the position of "\n". + $pos = \strpos($buffer, "\n"); + // No "\n", packet length is unknown, continue to wait for the data so return 0. + if ($pos === false) { + return 0; + } + // Return the current package length. + return $pos + 1; + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + // Add "\n" + return $buffer . "\n"; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + // Remove "\n" + return \rtrim($buffer, "\r\n"); + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Websocket.php b/vendor/workerman/workerman/Protocols/Websocket.php new file mode 100644 index 0000000..e161228 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Websocket.php @@ -0,0 +1,562 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Request; +use Workerman\Worker; + +/** + * WebSocket protocol. + */ +class Websocket implements \Workerman\Protocols\ProtocolInterface +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB_DEFLATE = "\xc1"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER_DEFLATE = "\xc2"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Receive length. + $recv_len = \strlen($buffer); + // We need more data. + if ($recv_len < 6) { + return 0; + } + + // Has not yet completed the handshake. + if (empty($connection->context->websocketHandshake)) { + return static::dealHandshake($buffer, $connection); + } + + // Buffer websocket frame data. + if ($connection->context->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->context->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + $first_byte = \ord($buffer[0]); + $second_byte = \ord($buffer[1]); + $data_len = $second_byte & 127; + $is_fin_frame = $first_byte >> 7; + $masked = $second_byte >> 7; + + if (!$masked) { + Worker::safeEcho("frame not masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $first_byte & 0xf; + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + $close_cb = $connection->onWebSocketClose ?? $connection->worker->onWebSocketClose ?? false; + if ($close_cb) { + try { + $close_cb($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } // Close connection. + else { + $connection->close("\x88\x02\x03\xe8", true); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"); + $connection->close(); + return 0; + } + + // Calculate packet length. + $head_len = 6; + if ($data_len === 126) { + $head_len = 8; + if ($head_len > $recv_len) { + return 0; + } + $pack = \unpack('nn/ntotal_len', $buffer); + $data_len = $pack['total_len']; + } else { + if ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = \unpack('n/N2c', $buffer); + $data_len = $arr['c1'] * 4294967296 + $arr['c2']; + } + } + $current_frame_length = $head_len + $data_len; + + $total_package_size = \strlen($connection->context->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + $ping_cb = $connection->onWebSocketPing ?? $connection->worker->onWebSocketPing ?? false; + if ($ping_cb) { + try { + $ping_cb($connection, $ping_data); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + $pong_cb = $connection->onWebSocketPong ?? $connection->worker->onWebSocketPong ?? false; + if ($pong_cb) { + try { + $pong_cb($connection, $pong_data); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->context->websocketCurrentFrameLength = $current_frame_length; + } + } + + // Received just a frame length data. + if ($connection->context->websocketCurrentFrameLength === $recv_len) { + static::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $connection->context->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->context->websocketCurrentFrameLength < $recv_len) { + static::decode(\substr($buffer, 0, $connection->context->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $current_frame_length = $connection->context->websocketCurrentFrameLength; + $connection->context->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return static::input(\substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($buffer, ConnectionInterface $connection) + { + if (!is_scalar($buffer)) { + throw new \Exception("You can't send(" . \gettype($buffer) . ") to client, you need to convert it to a string. "); + } + + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + // permessage-deflate + if (\ord($connection->websocketType) & 64) { + $buffer = static::deflate($connection, $buffer); + } + + $first_byte = $connection->websocketType; + $len = \strlen($buffer); + + if ($len <= 125) { + $encode_buffer = $first_byte . \chr($len) . $buffer; + } else { + if ($len <= 65535) { + $encode_buffer = $first_byte . \chr(126) . \pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . \chr(127) . \pack("xxxxN", $len) . $buffer; + } + } + + // Handshake not completed so temporary buffer websocket data waiting for send. + if (empty($connection->context->websocketHandshake)) { + if (empty($connection->context->tmpWebsocketData)) { + $connection->context->tmpWebsocketData = ''; + } + // If buffer has already full then discard the current package. + if (\strlen($connection->context->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + ($connection->onError)($connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + return ''; + } + $connection->context->tmpWebsocketData .= $encode_buffer; + // Check buffer is full. + if ($connection->maxSendBufferSize <= \strlen($connection->context->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + ($connection->onBufferFull)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + } + // Return empty string. + return ''; + } + + return $encode_buffer; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($buffer, ConnectionInterface $connection) + { + $first_byte = \ord($buffer[0]); + $second_byte = \ord($buffer[1]); + $len = $second_byte & 127; + $is_fin_frame = $first_byte >> 7; + $rsv1 = 64 === ($first_byte & 64); + + if ($len === 126) { + $masks = \substr($buffer, 4, 4); + $data = \substr($buffer, 8); + } else { + if ($len === 127) { + $masks = \substr($buffer, 10, 4); + $data = \substr($buffer, 14); + } else { + $masks = \substr($buffer, 2, 4); + $data = \substr($buffer, 6); + } + } + $dataLength = \strlen($data); + $masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4); + $decoded = $data ^ $masks; + if ($connection->context->websocketCurrentFrameLength) { + $connection->context->websocketDataBuffer .= $decoded; + if ($rsv1) { + return static::inflate($connection, $connection->context->websocketDataBuffer, $is_fin_frame); + } + return $connection->context->websocketDataBuffer; + } else { + if ($connection->context->websocketDataBuffer !== '') { + $decoded = $connection->context->websocketDataBuffer . $decoded; + $connection->context->websocketDataBuffer = ''; + } + if ($rsv1) { + return static::inflate($connection, $decoded, $is_fin_frame); + } + return $decoded; + } + } + + /** + * Inflate. + * + * @param $connection + * @param $buffer + * @param $is_fin_frame + * @return false|string + */ + protected static function inflate($connection, $buffer, $is_fin_frame) + { + if (!isset($connection->context->inflator)) { + $connection->context->inflator = \inflate_init( + \ZLIB_ENCODING_RAW, + [ + 'level' => -1, + 'memory' => 8, + 'window' => 15, + 'strategy' => \ZLIB_DEFAULT_STRATEGY + ] + ); + } + if ($is_fin_frame) { + $buffer .= "\x00\x00\xff\xff"; + } + return \inflate_add($connection->context->inflator, $buffer); + } + + /** + * Deflate. + * + * @param $connection + * @param $buffer + * @return false|string + */ + protected static function deflate($connection, $buffer) + { + if (!isset($connection->context->deflator)) { + $connection->context->deflator = \deflate_init( + \ZLIB_ENCODING_RAW, + [ + 'level' => -1, + 'memory' => 8, + 'window' => 15, + 'strategy' => \ZLIB_DEFAULT_STRATEGY + ] + ); + } + return \substr(\deflate_add($connection->context->deflator, $buffer), 0, -4); + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, $connection) + { + // HTTP protocol. + if (0 === \strpos($buffer, 'GET')) { + // Find \r\n\r\n. + $header_end_pos = \strpos($buffer, "\r\n\r\n"); + if (!$header_end_pos) { + return 0; + } + $header_length = $header_end_pos + 4; + + // Get Sec-WebSocket-Key. + $Sec_WebSocket_Key = ''; + if (\preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { + $Sec_WebSocket_Key = $match[1]; + } else { + $connection->close("HTTP/1.0 400 Bad Request\r\nServer: workerman\r\n\r\n

WebSocket


workerman
", true); + return 0; + } + // Calculation websocket key. + $new_key = \base64_encode(\sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); + // Handshake response data. + $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n" + . "Upgrade: websocket\r\n" + . "Sec-WebSocket-Version: 13\r\n" + . "Connection: Upgrade\r\n" + . "Sec-WebSocket-Accept: " . $new_key . "\r\n"; + + // Websocket data buffer. + $connection->context->websocketDataBuffer = ''; + // Current websocket frame length. + $connection->context->websocketCurrentFrameLength = 0; + // Current websocket frame data. + $connection->context->websocketCurrentFrameBuffer = ''; + // Consume handshake data. + $connection->consumeRecvBuffer($header_length); + + // Try to emit onWebSocketConnect callback. + $on_websocket_connect = $connection->onWebSocketConnect ?? $connection->worker->onWebSocketConnect ?? false; + if ($on_websocket_connect) { + static::parseHttpHeader($buffer); + try { + \call_user_func($on_websocket_connect, $connection, $buffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + if (!empty($_SESSION) && \class_exists('\GatewayWorker\Lib\Context')) { + $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); + } + $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); + } + + // blob or arraybuffer + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $has_server_header = false; + + if (isset($connection->headers)) { + if (\is_array($connection->headers)) { + foreach ($connection->headers as $header) { + if (\stripos($header, 'Server:') === 0) { + $has_server_header = true; + } + $handshake_message .= "$header\r\n"; + } + } else { + if (\stripos($connection->headers, 'Server:') !== false) { + $has_server_header = true; + } + $handshake_message .= "$connection->headers\r\n"; + } + } + if (!$has_server_header) { + $handshake_message .= "Server: workerman/" . Worker::VERSION . "\r\n"; + } + $handshake_message .= "\r\n"; + // Send handshake response. + $connection->send($handshake_message, true); + // Mark handshake complete.. + $connection->context->websocketHandshake = true; + + // There are data waiting to be sent. + if (!empty($connection->context->tmpWebsocketData)) { + $connection->send($connection->context->tmpWebsocketData, true); + $connection->context->tmpWebsocketData = ''; + } + if (\strlen($buffer) > $header_length) { + return static::input(\substr($buffer, $header_length), $connection); + } + return 0; + } + // Bad websocket handshake request. + $connection->close("HTTP/1.0 400 Bad Request\r\nServer: workerman\r\n\r\n

400 Bad Request


workerman
", true); + return 0; + } + + /** + * Parse http header. + * + * @param string $buffer + * @return void + */ + protected static function parseHttpHeader($buffer) + { + // Parse headers. + list($http_header, ) = \explode("\r\n\r\n", $buffer, 2); + $header_data = \explode("\r\n", $http_header); + + if ($_SERVER) { + $_SERVER = array(); + } + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = \explode(' ', + $header_data[0]); + + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = \explode(':', $content, 2); + $key = \str_replace('-', '_', \strtoupper($key)); + $value = \trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = \explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + \parse_str(\str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + } + } + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = \parse_url($_SERVER['REQUEST_URI'], \PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + \parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + } + +} diff --git a/vendor/workerman/workerman/Protocols/Ws.php b/vendor/workerman/workerman/Protocols/Ws.php new file mode 100644 index 0000000..dd1bd1d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Ws.php @@ -0,0 +1,432 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols; + +use Workerman\Worker; +use Workerman\Timer; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\ConnectionInterface; + +/** + * Websocket protocol for client. + */ +class Ws +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + if (empty($connection->context->handshakeStep)) { + Worker::safeEcho("recv data before handshake. Buffer:" . \bin2hex($buffer) . "\n"); + return false; + } + // Recv handshake response + if ($connection->context->handshakeStep === 1) { + return self::dealHandshake($buffer, $connection); + } + $recvLen = \strlen($buffer); + if ($recvLen < 2) { + return 0; + } + // Buffer websocket frame data. + if ($connection->context->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->context->websocketCurrentFrameLength > $recvLen) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + + $firstbyte = \ord($buffer[0]); + $secondbyte = \ord($buffer[1]); + $dataLen = $secondbyte & 127; + $isFinFrame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if ($masked) { + Worker::safeEcho("frame masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + + switch ($opcode) { + case 0x0: + // Blob type. + case 0x1: + // Arraybuffer type. + case 0x2: + // Ping package. + case 0x9: + // Pong package. + case 0xa: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + ($connection->onWebSocketClose)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"); + $connection->close(); + return 0; + } + // Calculate packet length. + if ($dataLen === 126) { + if (\strlen($buffer) < 4) { + return 0; + } + $pack = \unpack('nn/ntotal_len', $buffer); + $currentFrameLength = $pack['total_len'] + 4; + } else if ($dataLen === 127) { + if (\strlen($buffer) < 10) { + return 0; + } + $arr = \unpack('n/N2c', $buffer); + $currentFrameLength = $arr['c1'] * 4294967296 + $arr['c2'] + 10; + } else { + $currentFrameLength = $dataLen + 2; + } + + $totalPackageSize = \strlen($connection->context->websocketDataBuffer) + $currentFrameLength; + if ($totalPackageSize > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$totalPackageSize\n"); + $connection->close(); + return 0; + } + + if ($isFinFrame) { + if ($opcode === 0x9) { + if ($recvLen >= $currentFrameLength) { + $pingData = static::decode(\substr($buffer, 0, $currentFrameLength), $connection); + $connection->consumeRecvBuffer($currentFrameLength); + $tmpConnectionType = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing)) { + try { + ($connection->onWebSocketPing)($connection, $pingData); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } else { + $connection->send($pingData); + } + $connection->websocketType = $tmpConnectionType; + if ($recvLen > $currentFrameLength) { + return static::input(\substr($buffer, $currentFrameLength), $connection); + } + } + return 0; + + } else if ($opcode === 0xa) { + if ($recvLen >= $currentFrameLength) { + $pongData = static::decode(\substr($buffer, 0, $currentFrameLength), $connection); + $connection->consumeRecvBuffer($currentFrameLength); + $tmpConnectionType = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + ($connection->onWebSocketPong)($connection, $pongData); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + $connection->websocketType = $tmpConnectionType; + if ($recvLen > $currentFrameLength) { + return static::input(\substr($buffer, $currentFrameLength), $connection); + } + } + return 0; + } + return $currentFrameLength; + } else { + $connection->context->websocketCurrentFrameLength = $currentFrameLength; + } + } + // Received just a frame length data. + if ($connection->context->websocketCurrentFrameLength === $recvLen) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $connection->context->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->context->websocketCurrentFrameLength < $recvLen) { + self::decode(\substr($buffer, 0, $connection->context->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $currentFrameLength = $connection->context->websocketCurrentFrameLength; + $connection->context->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return self::input(\substr($buffer, $currentFrameLength), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($payload, ConnectionInterface $connection) + { + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + $payload = (string)$payload; + if (empty($connection->context->handshakeStep)) { + static::sendHandshake($connection); + } + + $maskKey = "\x00\x00\x00\x00"; + $length = \strlen($payload); + + if (strlen($payload) < 126) { + $head = chr(0x80 | $length); + } elseif ($length < 0xFFFF) { + $head = chr(0x80 | 126) . pack("n", $length); + } else { + $head = chr(0x80 | 127) . pack("N", 0) . pack("N", $length); + } + + $frame = $connection->websocketType . $head . $maskKey; + // append payload to frame: + $maskKey = \str_repeat($maskKey, \floor($length / 4)) . \substr($maskKey, 0, $length % 4); + $frame .= $payload ^ $maskKey; + if ($connection->context->handshakeStep === 1) { + // If buffer has already full then discard the current package. + if (\strlen($connection->context->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + ($connection->onError)($connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + return ''; + } + $connection->context->tmpWebsocketData = $connection->context->tmpWebsocketData . $frame; + // Check buffer is full. + if ($connection->maxSendBufferSize <= \strlen($connection->context->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + ($connection->onBufferFull)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + } + return ''; + } + return $frame; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($bytes, ConnectionInterface $connection) + { + $dataLength = \ord($bytes[1]); + + if ($dataLength === 126) { + $decodedData = \substr($bytes, 4); + } else if ($dataLength === 127) { + $decodedData = \substr($bytes, 10); + } else { + $decodedData = \substr($bytes, 2); + } + if ($connection->context->websocketCurrentFrameLength) { + $connection->context->websocketDataBuffer .= $decodedData; + return $connection->context->websocketDataBuffer; + } else { + if ($connection->context->websocketDataBuffer !== '') { + $decodedData = $connection->context->websocketDataBuffer . $decodedData; + $connection->context->websocketDataBuffer = ''; + } + return $decodedData; + } + } + + /** + * Send websocket handshake data. + * + * @return void + */ + public static function onConnect($connection) + { + static::sendHandshake($connection); + } + + /** + * Clean + * + * @param TcpConnection $connection + */ + public static function onClose($connection) + { + $connection->context->handshakeStep = null; + $connection->context->websocketCurrentFrameLength = 0; + $connection->context->tmpWebsocketData = ''; + $connection->context->websocketDataBuffer = ''; + if (!empty($connection->context->websocketPingTimer)) { + Timer::del($connection->context->websocketPingTimer); + $connection->context->websocketPingTimer = null; + } + } + + /** + * Send websocket handshake. + * + * @param TcpConnection $connection + * @return void + */ + public static function sendHandshake(ConnectionInterface $connection) + { + if (!empty($connection->context->handshakeStep)) { + return; + } + // Get Host. + $port = $connection->getRemotePort(); + $host = $port === 80 || $port === 443 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; + // Handshake header. + $connection->context->websocketSecKey = \base64_encode(random_bytes(16)); + $userHeader = $connection->headers ?? null; + $userHeaderStr = ''; + if (!empty($userHeader)) { + if (\is_array($userHeader)) { + foreach ($userHeader as $k => $v) { + $userHeaderStr .= "$k: $v\r\n"; + } + } else { + $userHeaderStr .= $userHeader; + } + $userHeaderStr = "\r\n" . \trim($userHeaderStr); + } + $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n" . + (!\preg_match("/\nHost:/i", $userHeaderStr) ? "Host: $host\r\n" : '') . + "Connection: Upgrade\r\n" . + "Upgrade: websocket\r\n" . + (isset($connection->websocketOrigin) ? "Origin: " . $connection->websocketOrigin . "\r\n" : '') . + (isset($connection->websocketClientProtocol) ? "Sec-WebSocket-Protocol: " . $connection->websocketClientProtocol . "\r\n" : '') . + "Sec-WebSocket-Version: 13\r\n" . + "Sec-WebSocket-Key: " . $connection->context->websocketSecKey . $userHeaderStr . "\r\n\r\n"; + $connection->send($header, true); + $connection->context->handshakeStep = 1; + $connection->context->websocketCurrentFrameLength = 0; + $connection->context->websocketDataBuffer = ''; + $connection->context->tmpWebsocketData = ''; + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, ConnectionInterface $connection) + { + $pos = \strpos($buffer, "\r\n\r\n"); + if ($pos) { + //checking Sec-WebSocket-Accept + if (\preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) { + if ($match[1] !== \base64_encode(\sha1($connection->context->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) { + Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . \substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + } else { + Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . \substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + + // handshake complete + + // Get WebSocket subprotocol (if specified by server) + if (\preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) { + $connection->websocketServerProtocol = \trim($match[1]); + } + + $connection->context->handshakeStep = 2; + $handshakeResponseLength = $pos + 4; + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + try { + ($connection->onWebSocketConnect)($connection, \substr($buffer, 0, $handshakeResponseLength)); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + // Headbeat. + if (!empty($connection->websocketPingInterval)) { + $connection->context->websocketPingTimer = Timer::add($connection->websocketPingInterval, function () use ($connection) { + if (false === $connection->send(\pack('H*', '898000000000'), true)) { + Timer::del($connection->context->websocketPingTimer); + $connection->context->websocketPingTimer = null; + } + }); + } + + $connection->consumeRecvBuffer($handshakeResponseLength); + if (!empty($connection->context->tmpWebsocketData)) { + $connection->send($connection->context->tmpWebsocketData, true); + $connection->context->tmpWebsocketData = ''; + } + if (\strlen($buffer) > $handshakeResponseLength) { + return self::input(\substr($buffer, $handshakeResponseLength), $connection); + } + } + return 0; + } + +} diff --git a/vendor/workerman/workerman/README.md b/vendor/workerman/workerman/README.md new file mode 100644 index 0000000..6038c02 --- /dev/null +++ b/vendor/workerman/workerman/README.md @@ -0,0 +1,342 @@ +# Workerman +[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) +[![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman) +[![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman) +[![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman) +[![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman) +[![License](https://poser.pugx.org/workerman/workerman/license)](https://packagist.org/packages/workerman/workerman) + +## What is it +Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications. +Workerman supports HTTP, Websocket, SSL and other custom protocols. +Workerman supports event extension. + +## Requires +PHP 7.0 or Higher +A POSIX compatible operating system (Linux, OSX, BSD) +POSIX and PCNTL extensions required +Event extension recommended for better performance + +## Installation + +``` +composer require workerman/workerman +``` + +## Basic Usage + +### A websocket server +```php +onConnect = function ($connection) { + echo "New connection\n"; +}; + +// Emitted when data received +$ws_worker->onMessage = function ($connection, $data) { + // Send hello $data + $connection->send('Hello ' . $data); +}; + +// Emitted when connection closed +$ws_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +// Run worker +Worker::runAll(); +``` + +### An http server +```php +count = 4; + +// Emitted when data received +$http_worker->onMessage = function ($connection, $request) { + //$request->get(); + //$request->post(); + //$request->header(); + //$request->cookie(); + //$request->session(); + //$request->uri(); + //$request->path(); + //$request->method(); + + // Send data to client + $connection->send("Hello World"); +}; + +// Run all workers +Worker::runAll(); +``` + +### A tcp server +```php +count = 4; + +// Emitted when new connection come +$tcp_worker->onConnect = function ($connection) { + echo "New Connection\n"; +}; + +// Emitted when data received +$tcp_worker->onMessage = function ($connection, $data) { + // Send data to client + $connection->send("Hello $data \n"); +}; + +// Emitted when connection is closed +$tcp_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +Worker::runAll(); +``` + +### A udp server + +```php +count = 4; + +// Emitted when data received +$worker->onMessage = function($connection, $data) +{ + $connection->send($data); +}; + +Worker::runAll(); +``` + +### Enable SSL +```php + array( + 'local_cert' => '/your/path/of/server.pem', + 'local_pk' => '/your/path/of/server.key', + 'verify_peer' => false, + ) +); + +// Create a Websocket server with ssl context. +$ws_worker = new Worker('websocket://0.0.0.0:2346', $context); + +// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://). +// The similar approaches for Https etc. +$ws_worker->transport = 'ssl'; + +$ws_worker->onMessage = function ($connection, $data) { + // Send hello $data + $connection->send('Hello ' . $data); +}; + +Worker::runAll(); +``` + +### Custom protocol +Protocols/MyTextProtocol.php +```php +onConnect = function ($connection) { + echo "New connection\n"; +}; + +$text_worker->onMessage = function ($connection, $data) { + // Send data to client + $connection->send("Hello world\n"); +}; + +$text_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +// Run all workers +Worker::runAll(); +``` + +### Timer +```php +onWorkerStart = function ($task) { + // 2.5 seconds + $time_interval = 2.5; + $timer_id = Timer::add($time_interval, function () { + echo "Timer run\n"; + }); +}; + +// Run all workers +Worker::runAll(); +``` + +### AsyncTcpConnection (tcp/ws/text/frame etc...) +```php +onWorkerStart = function () { + // Websocket protocol for client. + $ws_connection = new AsyncTcpConnection('ws://echo.websocket.org:80'); + $ws_connection->onConnect = function ($connection) { + $connection->send('Hello'); + }; + $ws_connection->onMessage = function ($connection, $data) { + echo "Recv: $data\n"; + }; + $ws_connection->onError = function ($connection, $code, $msg) { + echo "Error: $msg\n"; + }; + $ws_connection->onClose = function ($connection) { + echo "Connection closed\n"; + }; + $ws_connection->connect(); +}; + +Worker::runAll(); +``` + + + +## Available commands +```php start.php start ``` +```php start.php start -d ``` +![workerman start](http://www.workerman.net/img/workerman-start.png) +```php start.php status ``` +![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) +```php start.php connections``` +```php start.php stop ``` +```php start.php restart ``` +```php start.php reload ``` + +## Documentation + +中文主页:[http://www.workerman.net](https://www.workerman.net) + +中文文档: [https://www.workerman.net/doc/workerman](https://www.workerman.net/doc/workerman) + +Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/SUMMARY.md) + +# Benchmarks +https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=db&l=yyku7z-e7&a=2 +![image](https://user-images.githubusercontent.com/6073368/146704320-1559fe97-aa67-4ee3-95d6-61e341b3c93b.png) + +## Sponsors +[opencollective.com/walkor](https://opencollective.com/walkor) + +[patreon.com/walkor](https://patreon.com/walkor) + +## Donate + + + +## Other links with workerman + +[webman](https://github.com/walkor/webman) +[PHPSocket.IO](https://github.com/walkor/phpsocket.io) +[php-socks5](https://github.com/walkor/php-socks5) +[php-http-proxy](https://github.com/walkor/php-http-proxy) + +## LICENSE + +Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). diff --git a/vendor/workerman/workerman/Timer.php b/vendor/workerman/workerman/Timer.php new file mode 100644 index 0000000..9f152f3 --- /dev/null +++ b/vendor/workerman/workerman/Timer.php @@ -0,0 +1,220 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * Timer. + * + * example: + * Workerman\Timer::add($time_interval, callback, array($arg1, $arg2..)); + */ +class Timer +{ + /** + * Tasks that based on ALARM signal. + * [ + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * .. + * ] + * + * @var array + */ + protected static $_tasks = array(); + + /** + * event + * + * @var EventInterface + */ + protected static $_event = null; + + /** + * timer id + * + * @var int + */ + protected static $_timerId = 0; + + /** + * timer status + * [ + * timer_id1 => bool, + * timer_id2 => bool, + * ...................., + * ] + * + * @var array + */ + protected static $_status = array(); + + /** + * Init. + * + * @param EventInterface $event + * @return void + */ + public static function init($event = null) + { + if ($event) { + self::$_event = $event; + return; + } + if (\function_exists('pcntl_signal')) { + \pcntl_signal(\SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); + } + } + + /** + * ALARM signal handler. + * + * @return void + */ + public static function signalHandle() + { + if (!self::$_event) { + \pcntl_alarm(1); + self::tick(); + } + } + + /** + * Add a timer. + * + * @param float $time_interval + * @param callable $func + * @param mixed $args + * @param bool $persistent + * @return int|bool + */ + public static function add($time_interval, $func, $args = array(), $persistent = true) + { + if ($time_interval <= 0) { + Worker::safeEcho(new Exception("bad time_interval")); + return false; + } + + if ($args === null) { + $args = array(); + } + + if (self::$_event) { + return self::$_event->add($time_interval, + $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); + } + + // If not workerman runtime just return. + if (!Worker::getAllWorkers()) { + return; + } + + if (!\is_callable($func)) { + Worker::safeEcho(new Exception("not callable")); + return false; + } + + if (empty(self::$_tasks)) { + \pcntl_alarm(1); + } + + $run_time = \time() + $time_interval; + if (!isset(self::$_tasks[$run_time])) { + self::$_tasks[$run_time] = array(); + } + + self::$_timerId = self::$_timerId == \PHP_INT_MAX ? 1 : ++self::$_timerId; + self::$_status[self::$_timerId] = true; + self::$_tasks[$run_time][self::$_timerId] = array($func, (array)$args, $persistent, $time_interval); + + return self::$_timerId; + } + + + /** + * Tick. + * + * @return void + */ + public static function tick() + { + if (empty(self::$_tasks)) { + \pcntl_alarm(0); + return; + } + $time_now = \time(); + foreach (self::$_tasks as $run_time => $task_data) { + if ($time_now >= $run_time) { + foreach ($task_data as $index => $one_task) { + $task_func = $one_task[0]; + $task_args = $one_task[1]; + $persistent = $one_task[2]; + $time_interval = $one_task[3]; + try { + \call_user_func_array($task_func, $task_args); + } catch (\Exception $e) { + Worker::safeEcho($e); + } + if($persistent && !empty(self::$_status[$index])) { + $new_run_time = \time() + $time_interval; + if(!isset(self::$_tasks[$new_run_time])) self::$_tasks[$new_run_time] = array(); + self::$_tasks[$new_run_time][$index] = array($task_func, (array)$task_args, $persistent, $time_interval); + } + } + unset(self::$_tasks[$run_time]); + } + } + } + + /** + * Remove a timer. + * + * @param mixed $timer_id + * @return bool + */ + public static function del($timer_id) + { + if (self::$_event) { + return self::$_event->del($timer_id, EventInterface::EV_TIMER); + } + + foreach(self::$_tasks as $run_time => $task_data) + { + if(array_key_exists($timer_id, $task_data)) unset(self::$_tasks[$run_time][$timer_id]); + } + + if(array_key_exists($timer_id, self::$_status)) unset(self::$_status[$timer_id]); + + return true; + } + + /** + * Remove all timers. + * + * @return void + */ + public static function delAll() + { + self::$_tasks = self::$_status = array(); + if (\function_exists('pcntl_alarm')) { + \pcntl_alarm(0); + } + if (self::$_event) { + self::$_event->clearAllTimer(); + } + } +} diff --git a/vendor/workerman/workerman/Worker.php b/vendor/workerman/workerman/Worker.php new file mode 100644 index 0000000..e789322 --- /dev/null +++ b/vendor/workerman/workerman/Worker.php @@ -0,0 +1,2756 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; +require_once __DIR__ . '/Lib/Constants.php'; + +use Workerman\Events\EventInterface; +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\UdpConnection; +use Workerman\Lib\Timer; +use Workerman\Events\Select; +use \Exception; + +/** + * Worker class + * A container for listening ports + */ +#[\AllowDynamicProperties] +class Worker +{ + /** + * Version. + * + * @var string + */ + const VERSION = '4.2.1'; + + /** + * Status starting. + * + * @var int + */ + const STATUS_STARTING = 1; + + /** + * Status running. + * + * @var int + */ + const STATUS_RUNNING = 2; + + /** + * Status shutdown. + * + * @var int + */ + const STATUS_SHUTDOWN = 4; + + /** + * Status reloading. + * + * @var int + */ + const STATUS_RELOADING = 8; + + /** + * Default backlog. Backlog is the maximum length of the queue of pending connections. + * + * @var int + */ + const DEFAULT_BACKLOG = 102400; + + /** + * Max udp package size. + * + * @var int + */ + const MAX_UDP_PACKAGE_SIZE = 65535; + + /** + * The safe distance for columns adjacent + * + * @var int + */ + const UI_SAFE_LENGTH = 4; + + /** + * Worker id. + * + * @var int + */ + public $id = 0; + + /** + * Name of the worker processes. + * + * @var string + */ + public $name = 'none'; + + /** + * Number of worker processes. + * + * @var int + */ + public $count = 1; + + /** + * Unix user of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $user = ''; + + /** + * Unix group of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $group = ''; + + /** + * reloadable. + * + * @var bool + */ + public $reloadable = true; + + /** + * reuse port. + * + * @var bool + */ + public $reusePort = false; + + /** + * Emitted when worker processes start. + * + * @var callable + */ + public $onWorkerStart = null; + + /** + * Emitted when a socket connection is successfully established. + * + * @var callable + */ + public $onConnect = null; + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callable + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callable + */ + public $onBufferDrain = null; + + /** + * Emitted when worker processes stopped. + * + * @var callable + */ + public $onWorkerStop = null; + + /** + * Emitted when worker processes get reload signal. + * + * @var callable + */ + public $onWorkerReload = null; + + /** + * Emitted when worker processes exited. + * + * @var callable + */ + public $onWorkerExit = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Store all connections of clients. + * + * @var array + */ + public $connections = array(); + + /** + * Application layer protocol. + * + * @var string + */ + public $protocol = null; + + /** + * Root path for autoload. + * + * @var string + */ + protected $_autoloadRootPath = ''; + + /** + * Pause accept new connections or not. + * + * @var bool + */ + protected $_pauseAccept = true; + + /** + * Is worker stopping ? + * @var bool + */ + public $stopping = false; + + /** + * Daemonize. + * + * @var bool + */ + public static $daemonize = false; + + /** + * Stdout file. + * + * @var string + */ + public static $stdoutFile = '/dev/null'; + + /** + * The file to store master process PID. + * + * @var string + */ + public static $pidFile = ''; + + /** + * The file used to store the master process status file. + * + * @var string + */ + public static $statusFile = ''; + + /** + * Log file. + * + * @var mixed + */ + public static $logFile = ''; + + /** + * Global event loop. + * + * @var EventInterface + */ + public static $globalEvent = null; + + /** + * Emitted when the master process get reload signal. + * + * @var callable + */ + public static $onMasterReload = null; + + /** + * Emitted when the master process terminated. + * + * @var callable + */ + public static $onMasterStop = null; + + /** + * EventLoopClass + * + * @var string + */ + public static $eventLoopClass = ''; + + /** + * Process title + * + * @var string + */ + public static $processTitle = 'WorkerMan'; + + /** + * After sending the stop command to the child process stopTimeout seconds, + * if the process is still living then forced to kill. + * + * @var int + */ + public static $stopTimeout = 2; + + /** + * The PID of master process. + * + * @var int + */ + protected static $_masterPid = 0; + + /** + * Listening socket. + * + * @var resource + */ + protected $_mainSocket = null; + + /** + * Socket name. The format is like this http://0.0.0.0:80 . + * + * @var string + */ + protected $_socketName = ''; + + /** parse from _socketName avoid parse again in master or worker + * LocalSocket The format is like tcp://0.0.0.0:8080 + * @var string + */ + + protected $_localSocket=null; + + /** + * Context of socket. + * + * @var resource + */ + protected $_context = null; + + /** + * All worker instances. + * + * @var Worker[] + */ + protected static $_workers = array(); + + /** + * All worker processes pid. + * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * + * @var array + */ + protected static $_pidMap = array(); + + /** + * All worker processes waiting for restart. + * The format is like this [pid=>pid, pid=>pid]. + * + * @var array + */ + protected static $_pidsToRestart = array(); + + /** + * Mapping from PID to worker process ID. + * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..]. + * + * @var array + */ + protected static $_idMap = array(); + + /** + * Current status. + * + * @var int + */ + protected static $_status = self::STATUS_STARTING; + + /** + * Maximum length of the worker names. + * + * @var int + */ + protected static $_maxWorkerNameLength = 12; + + /** + * Maximum length of the socket names. + * + * @var int + */ + protected static $_maxSocketNameLength = 12; + + /** + * Maximum length of the process user names. + * + * @var int + */ + protected static $_maxUserNameLength = 12; + + /** + * Maximum length of the Proto names. + * + * @var int + */ + protected static $_maxProtoNameLength = 4; + + /** + * Maximum length of the Processes names. + * + * @var int + */ + protected static $_maxProcessesNameLength = 9; + + /** + * Maximum length of the Status names. + * + * @var int + */ + protected static $_maxStatusNameLength = 1; + + /** + * The file to store status info of current worker process. + * + * @var string + */ + protected static $_statisticsFile = ''; + + /** + * Start file. + * + * @var string + */ + protected static $_startFile = ''; + + /** + * OS. + * + * @var string + */ + protected static $_OS = \OS_TYPE_LINUX; + + /** + * Processes for windows. + * + * @var array + */ + protected static $_processForWindows = array(); + + /** + * Status info of current worker process. + * + * @var array + */ + protected static $_globalStatistics = array( + 'start_timestamp' => 0, + 'worker_exit_info' => array() + ); + + /** + * Available event loops. + * + * @var array + */ + protected static $_availableEventLoops = array( + 'event' => '\Workerman\Events\Event', + 'libevent' => '\Workerman\Events\Libevent' + ); + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'tcp' + ); + + /** + * PHP built-in error types. + * + * @var array + */ + protected static $_errorType = array( + \E_ERROR => 'E_ERROR', // 1 + \E_WARNING => 'E_WARNING', // 2 + \E_PARSE => 'E_PARSE', // 4 + \E_NOTICE => 'E_NOTICE', // 8 + \E_CORE_ERROR => 'E_CORE_ERROR', // 16 + \E_CORE_WARNING => 'E_CORE_WARNING', // 32 + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', // 64 + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', // 128 + \E_USER_ERROR => 'E_USER_ERROR', // 256 + \E_USER_WARNING => 'E_USER_WARNING', // 512 + \E_USER_NOTICE => 'E_USER_NOTICE', // 1024 + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', // 4096 + \E_DEPRECATED => 'E_DEPRECATED', // 8192 + \E_USER_DEPRECATED => 'E_USER_DEPRECATED' // 16384 + ); + + /** + * Graceful stop or not. + * + * @var bool + */ + protected static $_gracefulStop = false; + + /** + * Standard output stream + * @var resource + */ + protected static $_outputStream = null; + + /** + * If $outputStream support decorated + * @var bool + */ + protected static $_outputDecorated = null; + + protected static $liveVersionLength = null; + + /** + * Run all worker instances. + * + * @return void + */ + public static function runAll() + { + static::checkSapiEnv(); + static::init(); + static::parseCommand(); + static::lock(); + static::daemonize(); + static::initWorkers(); + static::installSignal(); + static::saveMasterPid(); + static::lock(\LOCK_UN); + static::displayUI(); + static::forkWorkers(); + static::resetStd(); + static::monitorWorkers(); + } + + /** + * Check sapi. + * + * @return void + */ + protected static function checkSapiEnv() + { + // Only for cli and micro. + if (!in_array(\PHP_SAPI, ['cli', 'micro'])) { + exit("Only run in command line mode \n"); + } + if (\DIRECTORY_SEPARATOR === '\\') { + self::$_OS = \OS_TYPE_WINDOWS; + } + } + + /** + * Init. + * + * @return void + */ + protected static function init() + { + \set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + + // Start file. + $backtrace = \debug_backtrace(); + static::$_startFile = $backtrace[\count($backtrace) - 1]['file']; + + + $unique_prefix = \str_replace('/', '_', static::$_startFile); + + // Pid file. + if (empty(static::$pidFile)) { + static::$pidFile = __DIR__ . "/../$unique_prefix.pid"; + } + + // Log file. + if (empty(static::$logFile)) { + static::$logFile = __DIR__ . '/../workerman.log'; + } + $log_file = (string)static::$logFile; + if (!\is_file($log_file)) { + \touch($log_file); + \chmod($log_file, 0622); + } + + // State. + static::$_status = static::STATUS_STARTING; + + // For statistics. + static::$_globalStatistics['start_timestamp'] = \time(); + + // Process title. + static::setProcessTitle(static::$processTitle . ': master process start_file=' . static::$_startFile); + + // Init data for worker id. + static::initId(); + + // Timer init. + Timer::init(); + } + + /** + * Lock. + * + * @return void + */ + protected static function lock($flag = \LOCK_EX) + { + static $fd; + if (\DIRECTORY_SEPARATOR !== '/') { + return; + } + $lock_file = static::$pidFile . '.lock'; + $fd = $fd ?: \fopen($lock_file, 'a+'); + if ($fd) { + flock($fd, $flag); + if ($flag === \LOCK_UN) { + fclose($fd); + $fd = null; + clearstatcache(); + if (\is_file($lock_file)) { + unlink($lock_file); + } + } + } + } + + /** + * Init All worker instances. + * + * @return void + */ + protected static function initWorkers() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + + static::$_statisticsFile = static::$statusFile ? static::$statusFile : __DIR__ . '/../workerman-' .posix_getpid().'.status'; + + foreach (static::$_workers as $worker) { + // Worker name. + if (empty($worker->name)) { + $worker->name = 'none'; + } + + // Get unix user of the worker process. + if (empty($worker->user)) { + $worker->user = static::getCurrentUser(); + } else { + if (\posix_getuid() !== 0 && $worker->user !== static::getCurrentUser()) { + static::log('Warning: You must have the root privileges to change uid and gid.'); + } + } + + // Socket name. + $worker->socket = $worker->getSocketName(); + + // Status name. + $worker->status = ' [OK] '; + + // Get column mapping for UI + foreach(static::getUiColumns() as $column_name => $prop){ + $prop_length = \strlen((string) static::getWorkerProperty($worker, $prop)); + static::updateMaxNameLength($column_name, $prop_length); + } + + // Listen. + if (!$worker->reusePort) { + $worker->listen(); + } + } + } + + /** + * @param Worker $worker + * @param string $prop + * @return mixed + */ + protected static function getWorkerProperty($worker, $prop) + { + switch ($prop) { + case 'transport': + return $worker->transport; + case 'user': + return $worker->user; + case 'name': + return $worker->name; + case 'socket': + return $worker->socket; + case 'count': + return $worker->count; + case 'status': + return $worker->status; + } + return null; + } + + /** + * Update specified column name length + * + * @param string $column_name + * @param int $length + * @return void + */ + protected static function updateMaxNameLength($column_name, $length) + { + switch ($column_name) { + case 'processes': + static::$_maxProcessesNameLength = max(static::$_maxProcessesNameLength, $length); + break; + case 'proto': + static::$_maxProtoNameLength = max(static::$_maxProtoNameLength, $length); + break; + case 'listen': + case 'socket': + static::$_maxSocketNameLength = max(static::$_maxSocketNameLength, $length); + break; + case 'status': + static::$_maxStatusNameLength = max(static::$_maxStatusNameLength, $length); + break; + case 'user': + static::$_maxUserNameLength = max(static::$_maxUserNameLength, $length); + break; + case 'worker': + static::$_maxWorkerNameLength = max(static::$_maxWorkerNameLength, $length); + break; + } + } + + /** + * @param string $column_name + * @return int + */ + protected static function getMaxNameLength($column_name) + { + switch ($column_name) { + case 'processes': + return static::$_maxProcessesNameLength; + case 'proto': + return static::$_maxProtoNameLength; + case 'listen': + case 'socket': + return static::$_maxSocketNameLength; + case 'status': + return static::$_maxStatusNameLength; + case 'user': + return static::$_maxUserNameLength; + case 'worker': + return static::$_maxWorkerNameLength; + } + return 0; + } + + /** + * Reload all worker instances. + * + * @return void + */ + public static function reloadAllWorkers() + { + static::init(); + static::initWorkers(); + static::displayUI(); + static::$_status = static::STATUS_RELOADING; + } + + /** + * Get all worker instances. + * + * @return array + */ + public static function getAllWorkers() + { + return static::$_workers; + } + + /** + * Get global event-loop instance. + * + * @return EventInterface + */ + public static function getEventLoop() + { + return static::$globalEvent; + } + + /** + * Get main socket resource + * @return resource + */ + public function getMainSocket(){ + return $this->_mainSocket; + } + + /** + * Init idMap. + * return void + */ + protected static function initId() + { + foreach (static::$_workers as $worker_id => $worker) { + $new_id_map = array(); + $worker->count = $worker->count < 1 ? 1 : $worker->count; + for($key = 0; $key < $worker->count; $key++) { + $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0; + } + static::$_idMap[$worker_id] = $new_id_map; + } + } + + /** + * Get unix user of current porcess. + * + * @return string + */ + protected static function getCurrentUser() + { + $user_info = \posix_getpwuid(\posix_getuid()); + return $user_info['name'] ?? 'unknown'; + } + + /** + * Display staring UI. + * + * @return void + */ + protected static function displayUI() + { + global $argv; + if (\in_array('-q', $argv)) { + return; + } + if (static::$_OS !== \OS_TYPE_LINUX) { + static::safeEcho("---------------------------------------------- WORKERMAN -----------------------------------------------\r\n"); + static::safeEcho('Workerman version:'. static::VERSION. ' PHP version:'. \PHP_VERSION. "\r\n"); + static::safeEcho("----------------------------------------------- WORKERS ------------------------------------------------\r\n"); + static::safeEcho("worker listen processes status\r\n"); + return; + } + + //show version + $line_version = 'Workerman version:' . static::VERSION . \str_pad('PHP version:', 22, ' ', \STR_PAD_LEFT) . \PHP_VERSION; + $line_version .= \str_pad('Event-Loop:', 22, ' ', \STR_PAD_LEFT) . static::getEventLoopName() . \PHP_EOL; + if (static::$liveVersionLength === null) { + static::$liveVersionLength = \strlen($line_version); + } + $total_length = static::getSingleLineTotalLength(); + $line_one = '' . \str_pad(' WORKERMAN ', $total_length + \strlen(''), '-', \STR_PAD_BOTH) . ''. \PHP_EOL; + $line_two = \str_pad(' WORKERS ' , $total_length + \strlen(''), '-', \STR_PAD_BOTH) . \PHP_EOL; + static::safeEcho($line_one . $line_version . $line_two); + + //Show title + $title = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $length = static::getMaxNameLength($column_name); + //just keep compatible with listen name + $column_name === 'socket' && $column_name = 'listen'; + $title.= "{$column_name}" . \str_pad('', $length + static::UI_SAFE_LENGTH - \strlen($column_name)); + } + $title && static::safeEcho($title . \PHP_EOL); + + //Show content + foreach (static::$_workers as $worker) { + $content = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + \preg_match_all("/(|<\/n>||<\/w>||<\/g>)/is", (string) static::getWorkerProperty($worker, $prop), $matches); + $place_holder_length = !empty($matches) ? \strlen(\implode('', $matches[0])) : 0; + $content .= \str_pad((string) static::getWorkerProperty($worker, $prop), static::getMaxNameLength($column_name) + static::UI_SAFE_LENGTH + $place_holder_length); + } + $content && static::safeEcho($content . \PHP_EOL); + } + + //Show last line + $line_last = \str_pad('', static::getSingleLineTotalLength(), '-') . \PHP_EOL; + !empty($content) && static::safeEcho($line_last); + + if (static::$daemonize) { + $tmpArgv = $argv; + foreach ($tmpArgv as $index => $value) { + if ($value == '-d') { + unset($tmpArgv[$index]); + } elseif ($value == 'start' || $value == 'restart') { + $tmpArgv[$index] = 'stop'; + } + } + static::safeEcho("Input \"php ".implode(' ', $tmpArgv)."\" to stop. Start success.\n\n"); + } else { + static::safeEcho("Press Ctrl+C to stop. Start success.\n"); + } + } + + /** + * Get UI columns to be shown in terminal + * + * 1. $column_map: array('ui_column_name' => 'clas_property_name') + * 2. Consider move into configuration in future + * + * @return array + */ + public static function getUiColumns() + { + return array( + 'proto' => 'transport', + 'user' => 'user', + 'worker' => 'name', + 'socket' => 'socket', + 'processes' => 'count', + 'status' => 'status', + ); + } + + /** + * Get single line total length for ui + * + * @return int + */ + public static function getSingleLineTotalLength() + { + $total_length = 0; + + foreach(static::getUiColumns() as $column_name => $prop){ + $total_length += static::getMaxNameLength($column_name) + static::UI_SAFE_LENGTH; + } + + //keep beauty when show less colums + if (static::$liveVersionLength === null) { + static::$liveVersionLength = 0; + } + $total_length <= static::$liveVersionLength && $total_length = static::$liveVersionLength; + + return $total_length; + } + + /** + * Parse command. + * + * @return void + */ + protected static function parseCommand() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + global $argv; + // Check argv; + $start_file = $argv[0]; + $usage = "Usage: php yourfile [mode]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n"; + $available_commands = array( + 'start', + 'stop', + 'restart', + 'reload', + 'status', + 'connections', + ); + $available_mode = array( + '-d', + '-g' + ); + $command = $mode = ''; + foreach ($argv as $value) { + if (\in_array($value, $available_commands)) { + $command = $value; + } elseif (\in_array($value, $available_mode)) { + $mode = $value; + } + } + + if (!$command) { + exit($usage); + } + + // Start command. + $mode_str = ''; + if ($command === 'start') { + if ($mode === '-d' || static::$daemonize) { + $mode_str = 'in DAEMON mode'; + } else { + $mode_str = 'in DEBUG mode'; + } + } + static::log("Workerman[$start_file] $command $mode_str"); + + // Get master process PID. + $master_pid = \is_file(static::$pidFile) ? (int)\file_get_contents(static::$pidFile) : 0; + // Master is still alive? + if (static::checkMasterIsAlive($master_pid)) { + if ($command === 'start') { + static::log("Workerman[$start_file] already running"); + exit; + } + } elseif ($command !== 'start' && $command !== 'restart') { + static::log("Workerman[$start_file] not run"); + exit; + } + + $statistics_file = static::$statusFile ? static::$statusFile : __DIR__ . "/../workerman-$master_pid.status"; + + // execute command. + switch ($command) { + case 'start': + if ($mode === '-d') { + static::$daemonize = true; + } + break; + case 'status': + while (1) { + if (\is_file($statistics_file)) { + @\unlink($statistics_file); + } + // Master process will send SIGIOT signal to all child processes. + \posix_kill($master_pid, SIGIOT); + // Sleep 1 second. + \sleep(1); + // Clear terminal. + if ($mode === '-d') { + static::safeEcho("\33[H\33[2J\33(B\33[m", true); + } + // Echo status data. + static::safeEcho(static::formatStatusData($statistics_file)); + if ($mode !== '-d') { + exit(0); + } + static::safeEcho("\nPress Ctrl+C to quit.\n\n"); + } + exit(0); + case 'connections': + if (\is_file($statistics_file) && \is_writable($statistics_file)) { + \unlink($statistics_file); + } + // Master process will send SIGIO signal to all child processes. + \posix_kill($master_pid, SIGIO); + // Waiting amoment. + \usleep(500000); + // Display statisitcs data from a disk file. + if(\is_readable($statistics_file)) { + \readfile($statistics_file); + } + exit(0); + case 'restart': + case 'stop': + if ($mode === '-g') { + static::$_gracefulStop = true; + $sig = \SIGQUIT; + static::log("Workerman[$start_file] is gracefully stopping ..."); + } else { + static::$_gracefulStop = false; + $sig = \SIGINT; + static::log("Workerman[$start_file] is stopping ..."); + } + // Send stop signal to master process. + $master_pid && \posix_kill($master_pid, $sig); + // Timeout. + $timeout = static::$stopTimeout + 3; + $start_time = \time(); + // Check master process is still alive? + while (1) { + $master_is_alive = $master_pid && \posix_kill((int) $master_pid, 0); + if ($master_is_alive) { + // Timeout? + if (!static::$_gracefulStop && \time() - $start_time >= $timeout) { + static::log("Workerman[$start_file] stop fail"); + exit; + } + // Waiting amoment. + \usleep(10000); + continue; + } + // Stop success. + static::log("Workerman[$start_file] stop success"); + if ($command === 'stop') { + exit(0); + } + if ($mode === '-d') { + static::$daemonize = true; + } + break; + } + break; + case 'reload': + if($mode === '-g'){ + $sig = \SIGUSR2; + }else{ + $sig = \SIGUSR1; + } + \posix_kill($master_pid, $sig); + exit; + default : + if (isset($command)) { + static::safeEcho('Unknown command: ' . $command . "\n"); + } + exit($usage); + } + } + + /** + * Format status data. + * + * @param $statistics_file + * @return string + */ + protected static function formatStatusData($statistics_file) + { + static $total_request_cache = array(); + if (!\is_readable($statistics_file)) { + return ''; + } + $info = \file($statistics_file, \FILE_IGNORE_NEW_LINES); + if (!$info) { + return ''; + } + $status_str = ''; + $current_total_request = array(); + $workerInfo = []; + try { + $workerInfo = unserialize($info[0], ['allowed_classes' => false]); + } catch (Throwable $exception) {} + if (!is_array($workerInfo)) { + $workerInfo = []; + } + \ksort($workerInfo, SORT_NUMERIC); + unset($info[0]); + $data_waiting_sort = array(); + $read_process_status = false; + $total_requests = 0; + $total_qps = 0; + $total_connections = 0; + $total_fails = 0; + $total_memory = 0; + $total_timers = 0; + $maxLen1 = static::$_maxSocketNameLength; + $maxLen2 = static::$_maxWorkerNameLength; + foreach($info as $key => $value) { + if (!$read_process_status) { + $status_str .= $value . "\n"; + if (\preg_match('/^pid.*?memory.*?listening/', $value)) { + $read_process_status = true; + } + continue; + } + if(\preg_match('/^[0-9]+/', $value, $pid_math)) { + $pid = $pid_math[0]; + $data_waiting_sort[$pid] = $value; + if(\preg_match('/^\S+?\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?/', $value, $match)) { + $total_memory += \intval(\str_ireplace('M','',$match[1])); + $maxLen1 = \max($maxLen1,\strlen($match[2])); + $maxLen2 = \max($maxLen2,\strlen($match[3])); + $total_connections += \intval($match[4]); + $total_fails += \intval($match[5]); + $total_timers += \intval($match[6]); + $current_total_request[$pid] = $match[7]; + $total_requests += \intval($match[7]); + } + } + } + foreach($workerInfo as $pid => $info) { + if (!isset($data_waiting_sort[$pid])) { + $status_str .= "$pid\t" . \str_pad('N/A', 7) . " " + . \str_pad($info['listen'], static::$_maxSocketNameLength) . " " + . \str_pad($info['name'], static::$_maxWorkerNameLength) . " " + . \str_pad('N/A', 11) . " " . \str_pad('N/A', 9) . " " + . \str_pad('N/A', 7) . " " . \str_pad('N/A', 13) . " N/A [busy] \n"; + continue; + } + //$qps = isset($total_request_cache[$pid]) ? $current_total_request[$pid] + if (!isset($total_request_cache[$pid]) || !isset($current_total_request[$pid])) { + $qps = 0; + } else { + $qps = $current_total_request[$pid] - $total_request_cache[$pid]; + $total_qps += $qps; + } + $status_str .= $data_waiting_sort[$pid]. " " . \str_pad($qps, 6) ." [idle]\n"; + } + $total_request_cache = $current_total_request; + $status_str .= "----------------------------------------------PROCESS STATUS---------------------------------------------------\n"; + $status_str .= "Summary\t" . \str_pad($total_memory.'M', 7) . " " + . \str_pad('-', $maxLen1) . " " + . \str_pad('-', $maxLen2) . " " + . \str_pad($total_connections, 11) . " " . \str_pad($total_fails, 9) . " " + . \str_pad($total_timers, 7) . " " . \str_pad($total_requests, 13) . " " + . \str_pad($total_qps,6)." [Summary] \n"; + return $status_str; + } + + + /** + * Install signal handler. + * + * @return void + */ + protected static function installSignal() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + $signalHandler = '\Workerman\Worker::signalHandler'; + // stop + \pcntl_signal(\SIGINT, $signalHandler, false); + // stop + \pcntl_signal(\SIGTERM, $signalHandler, false); + // stop + \pcntl_signal(\SIGHUP, $signalHandler, false); + // stop + \pcntl_signal(\SIGTSTP, $signalHandler, false); + // graceful stop + \pcntl_signal(\SIGQUIT, $signalHandler, false); + // reload + \pcntl_signal(\SIGUSR1, $signalHandler, false); + // graceful reload + \pcntl_signal(\SIGUSR2, $signalHandler, false); + // status + \pcntl_signal(\SIGIOT, $signalHandler, false); + // connection status + \pcntl_signal(\SIGIO, $signalHandler, false); + // ignore + \pcntl_signal(\SIGPIPE, \SIG_IGN, false); + } + + /** + * Reinstall signal handler. + * + * @return void + */ + protected static function reinstallSignal() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + $signalHandler = '\Workerman\Worker::signalHandler'; + // uninstall stop signal handler + \pcntl_signal(\SIGINT, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGTERM, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGHUP, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGTSTP, \SIG_IGN, false); + // uninstall graceful stop signal handler + \pcntl_signal(\SIGQUIT, \SIG_IGN, false); + // uninstall reload signal handler + \pcntl_signal(\SIGUSR1, \SIG_IGN, false); + // uninstall graceful reload signal handler + \pcntl_signal(\SIGUSR2, \SIG_IGN, false); + // uninstall status signal handler + \pcntl_signal(\SIGIOT, \SIG_IGN, false); + // uninstall connections status signal handler + \pcntl_signal(\SIGIO, \SIG_IGN, false); + // reinstall stop signal handler + static::$globalEvent->add(\SIGINT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGQUIT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGHUP, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGTSTP, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall reload signal handler + static::$globalEvent->add(\SIGUSR1, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful reload signal handler + static::$globalEvent->add(\SIGUSR2, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall status signal handler + static::$globalEvent->add(\SIGIOT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall connection status signal handler + static::$globalEvent->add(\SIGIO, EventInterface::EV_SIGNAL, $signalHandler); + } + + /** + * Signal handler. + * + * @param int $signal + */ + public static function signalHandler($signal) + { + switch ($signal) { + // Stop. + case \SIGINT: + case \SIGTERM: + case \SIGHUP: + case \SIGTSTP: + static::$_gracefulStop = false; + static::stopAll(); + break; + // Graceful stop. + case \SIGQUIT: + static::$_gracefulStop = true; + static::stopAll(); + break; + // Reload. + case \SIGUSR2: + case \SIGUSR1: + if (static::$_status === static::STATUS_SHUTDOWN || static::$_status === static::STATUS_RELOADING) { + return; + } + static::$_gracefulStop = $signal === \SIGUSR2; + static::$_pidsToRestart = static::getAllWorkerPids(); + static::reload(); + break; + // Show status. + case \SIGIOT: + static::writeStatisticsToStatusFile(); + break; + // Show connection status. + case \SIGIO: + static::writeConnectionsStatisticsToStatusFile(); + break; + } + } + + /** + * Run as daemon mode. + * + * @throws Exception + */ + protected static function daemonize() + { + if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) { + return; + } + \umask(0); + $pid = \pcntl_fork(); + if (-1 === $pid) { + throw new Exception('Fork fail'); + } elseif ($pid > 0) { + exit(0); + } + if (-1 === \posix_setsid()) { + throw new Exception("Setsid fail"); + } + // Fork again avoid SVR4 system regain the control of terminal. + $pid = \pcntl_fork(); + if (-1 === $pid) { + throw new Exception("Fork fail"); + } elseif (0 !== $pid) { + exit(0); + } + } + + /** + * Redirect standard input and output. + * + * @throws Exception + */ + public static function resetStd() + { + if (!static::$daemonize || \DIRECTORY_SEPARATOR !== '/') { + return; + } + global $STDOUT, $STDERR; + $handle = \fopen(static::$stdoutFile, "a"); + if ($handle) { + unset($handle); + \set_error_handler(function(){}); + if ($STDOUT) { + \fclose($STDOUT); + } + if ($STDERR) { + \fclose($STDERR); + } + if (\is_resource(\STDOUT)) { + \fclose(\STDOUT); + } + if (\is_resource(\STDERR)) { + \fclose(\STDERR); + } + $STDOUT = \fopen(static::$stdoutFile, "a"); + $STDERR = \fopen(static::$stdoutFile, "a"); + // Fix standard output cannot redirect of PHP 8.1.8's bug + if (\function_exists('posix_isatty') && \posix_isatty(2)) { + \ob_start(function ($string) { + \file_put_contents(static::$stdoutFile, $string, FILE_APPEND); + }, 1); + } + // change output stream + static::$_outputStream = null; + static::outputStream($STDOUT); + \restore_error_handler(); + return; + } + + throw new Exception('Can not open stdoutFile ' . static::$stdoutFile); + } + + /** + * Save pid. + * + * @throws Exception + */ + protected static function saveMasterPid() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + + static::$_masterPid = \posix_getpid(); + if (false === \file_put_contents(static::$pidFile, static::$_masterPid)) { + throw new Exception('can not save pid to ' . static::$pidFile); + } + } + + /** + * Get event loop name. + * + * @return string + */ + protected static function getEventLoopName() + { + if (static::$eventLoopClass) { + return static::$eventLoopClass; + } + + if (!\class_exists('\Swoole\Event', false)) { + unset(static::$_availableEventLoops['swoole']); + } + + $loop_name = ''; + foreach (static::$_availableEventLoops as $name=>$class) { + if (\extension_loaded($name)) { + $loop_name = $name; + break; + } + } + + if ($loop_name) { + static::$eventLoopClass = static::$_availableEventLoops[$loop_name]; + } else { + static::$eventLoopClass = '\Workerman\Events\Select'; + } + return static::$eventLoopClass; + } + + /** + * Get all pids of worker processes. + * + * @return array + */ + protected static function getAllWorkerPids() + { + $pid_array = array(); + foreach (static::$_pidMap as $worker_pid_array) { + foreach ($worker_pid_array as $worker_pid) { + $pid_array[$worker_pid] = $worker_pid; + } + } + return $pid_array; + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkers() + { + if (static::$_OS === \OS_TYPE_LINUX) { + static::forkWorkersForLinux(); + } else { + static::forkWorkersForWindows(); + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForLinux() + { + + foreach (static::$_workers as $worker) { + if (static::$_status === static::STATUS_STARTING) { + if (empty($worker->name)) { + $worker->name = $worker->getSocketName(); + } + $worker_name_length = \strlen($worker->name); + if (static::$_maxWorkerNameLength < $worker_name_length) { + static::$_maxWorkerNameLength = $worker_name_length; + } + } + + while (\count(static::$_pidMap[$worker->workerId]) < $worker->count) { + static::forkOneWorkerForLinux($worker); + } + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForWindows() + { + $files = static::getStartFilesForWindows(); + global $argv; + if(\in_array('-q', $argv) || \count($files) === 1) + { + if(\count(static::$_workers) > 1) + { + static::safeEcho("@@@ Error: multi workers init in one php file are not support @@@\r\n"); + static::safeEcho("@@@ See http://doc.workerman.net/faq/multi-woker-for-windows.html @@@\r\n"); + } + elseif(\count(static::$_workers) <= 0) + { + exit("@@@no worker inited@@@\r\n\r\n"); + } + + \reset(static::$_workers); + /** @var Worker $worker */ + $worker = current(static::$_workers); + + \Workerman\Timer::delAll(); + + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + \register_shutdown_function([__CLASS__, 'checkErrors']); + + // Create a global event loop. + if (!static::$globalEvent) { + $eventLoopClass = static::getEventLoopName(); + static::$globalEvent = new $eventLoopClass; + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + \restore_error_handler(); + + // Add an empty timer to prevent the event-loop from exiting. + Timer::add(1000000, function (){}); + + // Display UI. + static::safeEcho(\str_pad($worker->name, 48) . \str_pad($worker->getSocketName(), 36) . \str_pad('1', 10) . " [ok]\n"); + $worker->listen(); + $worker->run(); + static::$globalEvent->loop(); + if (static::$_status !== self::STATUS_SHUTDOWN) { + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } + exit(0); + } + else + { + static::$globalEvent = new \Workerman\Events\Select(); + Timer::init(static::$globalEvent); + foreach($files as $start_file) + { + static::forkOneWorkerForWindows($start_file); + } + } + } + + /** + * Get start files for windows. + * + * @return array + */ + public static function getStartFilesForWindows() { + global $argv; + $files = array(); + foreach($argv as $file) + { + if(\is_file($file)) + { + $files[$file] = $file; + } + } + return $files; + } + + /** + * Fork one worker process. + * + * @param string $start_file + */ + public static function forkOneWorkerForWindows($start_file) + { + $start_file = \realpath($start_file); + + $descriptorspec = array( + STDIN, STDOUT, STDOUT + ); + + $pipes = array(); + $process = \proc_open("php \"$start_file\" -q", $descriptorspec, $pipes); + + if (empty(static::$globalEvent)) { + static::$globalEvent = new Select(); + Timer::init(static::$globalEvent); + } + + // 保存子进程句柄 + static::$_processForWindows[$start_file] = array($process, $start_file); + } + + /** + * check worker status for windows. + * @return void + */ + public static function checkWorkerStatusForWindows() + { + foreach(static::$_processForWindows as $process_data) + { + $process = $process_data[0]; + $start_file = $process_data[1]; + $status = \proc_get_status($process); + if(isset($status['running'])) + { + if(!$status['running']) + { + static::safeEcho("process $start_file terminated and try to restart\n"); + \proc_close($process); + static::forkOneWorkerForWindows($start_file); + } + } + else + { + static::safeEcho("proc_get_status fail\n"); + } + } + } + + + /** + * Fork one worker process. + * + * @param self $worker + * @throws Exception + */ + protected static function forkOneWorkerForLinux(self $worker) + { + // Get available worker id. + $id = static::getId($worker->workerId, 0); + if ($id === false) { + return; + } + $pid = \pcntl_fork(); + // For master process. + if ($pid > 0) { + static::$_pidMap[$worker->workerId][$pid] = $pid; + static::$_idMap[$worker->workerId][$id] = $pid; + } // For child processes. + elseif (0 === $pid) { + \srand(); + \mt_srand(); + static::$_gracefulStop = false; + if (static::$_status === static::STATUS_STARTING) { + static::resetStd(); + } + static::$_pidMap = array(); + // Remove other listener. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId !== $worker->workerId) { + $one_worker->unlisten(); + unset(static::$_workers[$key]); + } + } + Timer::delAll(); + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + \register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); + + // Create a global event loop. + if (!static::$globalEvent) { + $event_loop_class = static::getEventLoopName(); + static::$globalEvent = new $event_loop_class; + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + \restore_error_handler(); + + static::setProcessTitle(self::$processTitle . ': worker process ' . $worker->name . ' ' . $worker->getSocketName()); + $worker->setUserAndGroup(); + $worker->id = $id; + $worker->run(); + // Main loop. + static::$globalEvent->loop(); + if (strpos(static::$eventLoopClass, 'Workerman\Events\Swoole') !== false) { + exit(0); + } + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } else { + throw new Exception("forkOneWorker fail"); + } + } + + /** + * Get worker id. + * + * @param string $worker_id + * @param int $pid + * + * @return integer + */ + protected static function getId($worker_id, $pid) + { + return \array_search($pid, static::$_idMap[$worker_id]); + } + + /** + * Set unix user and group for current process. + * + * @return void + */ + public function setUserAndGroup() + { + // Get uid. + $user_info = \posix_getpwnam($this->user); + if (!$user_info) { + static::log("Warning: User {$this->user} not exists"); + return; + } + $uid = $user_info['uid']; + // Get gid. + if ($this->group) { + $group_info = \posix_getgrnam($this->group); + if (!$group_info) { + static::log("Warning: Group {$this->group} not exists"); + return; + } + $gid = $group_info['gid']; + } else { + $gid = $user_info['gid']; + } + + // Set uid and gid. + if ($uid !== \posix_getuid() || $gid !== \posix_getgid()) { + if (!\posix_setgid($gid) || !\posix_initgroups($user_info['name'], $gid) || !\posix_setuid($uid)) { + static::log("Warning: change gid or uid fail."); + } + } + } + + /** + * Set process name. + * + * @param string $title + * @return void + */ + protected static function setProcessTitle($title) + { + \set_error_handler(function(){}); + // >=php 5.5 + if (\function_exists('cli_set_process_title')) { + \cli_set_process_title($title); + } // Need proctitle when php<=5.5 . + elseif (\extension_loaded('proctitle') && \function_exists('setproctitle')) { + \setproctitle($title); + } + \restore_error_handler(); + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkers() + { + if (static::$_OS === \OS_TYPE_LINUX) { + static::monitorWorkersForLinux(); + } else { + static::monitorWorkersForWindows(); + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForLinux() + { + static::$_status = static::STATUS_RUNNING; + while (1) { + // Calls signal handlers for pending signals. + \pcntl_signal_dispatch(); + // Suspends execution of the current process until a child has exited, or until a signal is delivered + $status = 0; + $pid = \pcntl_wait($status, \WUNTRACED); + // Calls signal handlers for pending signals again. + \pcntl_signal_dispatch(); + // If a child has already exited. + if ($pid > 0) { + // Find out which worker process exited. + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + if (isset($worker_pid_array[$pid])) { + $worker = static::$_workers[$worker_id]; + // Fix exit with status 2 for php8.2 + if ($status === \SIGINT && static::$_status === static::STATUS_SHUTDOWN) { + $status = 0; + } + // Exit status. + if ($status !== 0) { + static::log("worker[{$worker->name}:$pid] exit with status $status"); + } + + // onWorkerExit + if ($worker->onWorkerExit) { + try { + ($worker->onWorkerExit)($worker, $status, $pid); + } catch (\Throwable $exception) { + static::log("worker[{$worker->name}] onWorkerExit $exception"); + } + } + + // For Statistics. + if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { + static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; + } + ++static::$_globalStatistics['worker_exit_info'][$worker_id][$status]; + + // Clear process data. + unset(static::$_pidMap[$worker_id][$pid]); + + // Mark id is available. + $id = static::getId($worker_id, $pid); + static::$_idMap[$worker_id][$id] = 0; + + break; + } + } + // Is still running state then fork a new worker process. + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::forkWorkers(); + // If reloading continue. + if (isset(static::$_pidsToRestart[$pid])) { + unset(static::$_pidsToRestart[$pid]); + static::reload(); + } + } + } + + // If shutdown state and all child processes exited then master process exit. + if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) { + static::exitAndClearAll(); + } + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForWindows() + { + Timer::add(1, "\\Workerman\\Worker::checkWorkerStatusForWindows"); + + static::$globalEvent->loop(); + } + + /** + * Exit current process. + * + * @return void + */ + protected static function exitAndClearAll() + { + foreach (static::$_workers as $worker) { + $socket_name = $worker->getSocketName(); + if ($worker->transport === 'unix' && $socket_name) { + list(, $address) = \explode(':', $socket_name, 2); + $address = substr($address, strpos($address, '/') + 2); + @\unlink($address); + } + } + @\unlink(static::$pidFile); + static::log("Workerman[" . \basename(static::$_startFile) . "] has been stopped"); + if (static::$onMasterStop) { + \call_user_func(static::$onMasterStop); + } + exit(0); + } + + /** + * Execute reload. + * + * @return void + */ + protected static function reload() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + if (static::$_gracefulStop) { + $sig = \SIGUSR2; + } else { + $sig = \SIGUSR1; + } + // Set reloading state. + if (static::$_status !== static::STATUS_RELOADING && static::$_status !== static::STATUS_SHUTDOWN) { + static::log("Workerman[" . \basename(static::$_startFile) . "] reloading"); + static::$_status = static::STATUS_RELOADING; + // Try to emit onMasterReload callback. + if (static::$onMasterReload) { + try { + \call_user_func(static::$onMasterReload); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + static::initId(); + } + + // Send reload signal to all child processes. + $reloadable_pid_array = array(); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if ($worker->reloadable) { + foreach ($worker_pid_array as $pid) { + $reloadable_pid_array[$pid] = $pid; + } + } else { + foreach ($worker_pid_array as $pid) { + // Send reload signal to a worker process which reloadable is false. + \posix_kill($pid, $sig); + } + } + } + + // Get all pids that are waiting reload. + static::$_pidsToRestart = \array_intersect(static::$_pidsToRestart, $reloadable_pid_array); + + } + + // Reload complete. + if (empty(static::$_pidsToRestart)) { + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::$_status = static::STATUS_RUNNING; + } + return; + } + // Continue reload. + $one_worker_pid = \current(static::$_pidsToRestart); + // Send reload signal to a worker process. + \posix_kill($one_worker_pid, $sig); + // If the process does not exit after static::$stopTimeout seconds try to kill it. + if(!static::$_gracefulStop){ + Timer::add(static::$stopTimeout, '\posix_kill', array($one_worker_pid, \SIGKILL), false); + } + } // For child processes. + else { + \reset(static::$_workers); + $worker = \current(static::$_workers); + // Try to emit onWorkerReload callback. + if ($worker->onWorkerReload) { + try { + \call_user_func($worker->onWorkerReload, $worker); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + + if ($worker->reloadable) { + static::stopAll(); + } + } + } + + /** + * Stop all. + * + * @param int $code + * @param string $log + */ + public static function stopAll($code = 0, $log = '') + { + if ($log) { + static::log($log); + } + + static::$_status = static::STATUS_SHUTDOWN; + // For master process. + if (\DIRECTORY_SEPARATOR === '/' && static::$_masterPid === \posix_getpid()) { + static::log("Workerman[" . \basename(static::$_startFile) . "] stopping ..."); + $worker_pid_array = static::getAllWorkerPids(); + // Send stop signal to all child processes. + if (static::$_gracefulStop) { + $sig = \SIGQUIT; + } else { + $sig = \SIGINT; + } + foreach ($worker_pid_array as $worker_pid) { + if (static::$daemonize) { + \posix_kill($worker_pid, $sig); + } else { + Timer::add(1, '\posix_kill', array($worker_pid, $sig), false); + } + if(!static::$_gracefulStop){ + Timer::add(static::$stopTimeout, '\posix_kill', array($worker_pid, \SIGKILL), false); + } + } + Timer::add(1, "\\Workerman\\Worker::checkIfChildRunning"); + // Remove statistics file. + if (\is_file(static::$_statisticsFile)) { + @\unlink(static::$_statisticsFile); + } + } // For child processes. + else { + // Execute exit. + $workers = array_reverse(static::$_workers); + foreach ($workers as $worker) { + if(!$worker->stopping){ + $worker->stop(); + $worker->stopping = true; + } + } + if (!static::$_gracefulStop || ConnectionInterface::$statistics['connection_count'] <= 0) { + static::$_workers = array(); + if (static::$globalEvent) { + static::$globalEvent->destroy(); + } + + try { + exit($code); + } catch (Exception $e) { + + } + } + } + } + + /** + * check if child processes is really running + */ + public static function checkIfChildRunning() + { + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + foreach ($worker_pid_array as $pid => $worker_pid) { + if (!\posix_kill($pid, 0)) { + unset(static::$_pidMap[$worker_id][$pid]); + } + } + } + } + + /** + * Get process status. + * + * @return number + */ + public static function getStatus() + { + return static::$_status; + } + + /** + * If stop gracefully. + * + * @return bool + */ + public static function getGracefulStop() + { + return static::$_gracefulStop; + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + $all_worker_info = array(); + foreach(static::$_pidMap as $worker_id => $pid_array) { + /** @var Worker $worker */ + $worker = static::$_workers[$worker_id]; + foreach($pid_array as $pid) { + $all_worker_info[$pid] = array('name' => $worker->name, 'listen' => $worker->getSocketName()); + } + } + + \file_put_contents(static::$_statisticsFile, \serialize($all_worker_info)."\n", \FILE_APPEND); + $loadavg = \function_exists('sys_getloadavg') ? \array_map('round', \sys_getloadavg(), array(2,2,2)) : array('-', '-', '-'); + \file_put_contents(static::$_statisticsFile, + "----------------------------------------------GLOBAL STATUS----------------------------------------------------\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + 'Workerman version:' . static::VERSION . " PHP version:" . \PHP_VERSION . "\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, 'start time:' . \date('Y-m-d H:i:s', + static::$_globalStatistics['start_timestamp']) . ' run ' . \floor((\time() - static::$_globalStatistics['start_timestamp']) / (24 * 60 * 60)) . ' days ' . \floor(((\time() - static::$_globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60)) . " hours \n", + FILE_APPEND); + $load_str = 'load average: ' . \implode(", ", $loadavg); + \file_put_contents(static::$_statisticsFile, + \str_pad($load_str, 33) . 'event-loop:' . static::getEventLoopName() . "\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + \count(static::$_pidMap) . ' workers ' . \count(static::getAllWorkerPids()) . " processes\n", + \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + \str_pad('worker_name', static::$_maxWorkerNameLength) . " exit_status exit_count\n", \FILE_APPEND); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if (isset(static::$_globalStatistics['worker_exit_info'][$worker_id])) { + foreach (static::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status => $worker_exit_count) { + \file_put_contents(static::$_statisticsFile, + \str_pad($worker->name, static::$_maxWorkerNameLength) . " " . \str_pad($worker_exit_status, + 16) . " $worker_exit_count\n", \FILE_APPEND); + } + } else { + \file_put_contents(static::$_statisticsFile, + \str_pad($worker->name, static::$_maxWorkerNameLength) . " " . \str_pad(0, 16) . " 0\n", + \FILE_APPEND); + } + } + \file_put_contents(static::$_statisticsFile, + "----------------------------------------------PROCESS STATUS---------------------------------------------------\n", + \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + "pid\tmemory " . \str_pad('listening', static::$_maxSocketNameLength) . " " . \str_pad('worker_name', + static::$_maxWorkerNameLength) . " connections " . \str_pad('send_fail', 9) . " " + . \str_pad('timers', 8) . \str_pad('total_request', 13) ." qps status\n", \FILE_APPEND); + + \chmod(static::$_statisticsFile, 0722); + + foreach (static::getAllWorkerPids() as $worker_pid) { + \posix_kill($worker_pid, \SIGIOT); + } + return; + } + + // For child processes. + \gc_collect_cycles(); + if (\function_exists('gc_mem_caches')) { + \gc_mem_caches(); + } + \reset(static::$_workers); + /** @var \Workerman\Worker $worker */ + $worker = current(static::$_workers); + $worker_status_str = \posix_getpid() . "\t" . \str_pad(round(memory_get_usage(false) / (1024 * 1024), 2) . "M", 7) + . " " . \str_pad($worker->getSocketName(), static::$_maxSocketNameLength) . " " + . \str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), static::$_maxWorkerNameLength) + . " "; + $worker_status_str .= \str_pad(ConnectionInterface::$statistics['connection_count'], 11) + . " " . \str_pad(ConnectionInterface::$statistics['send_fail'], 9) + . " " . \str_pad(static::$globalEvent->getTimerCount(), 7) + . " " . \str_pad(ConnectionInterface::$statistics['total_request'], 13) . "\n"; + \file_put_contents(static::$_statisticsFile, $worker_status_str, \FILE_APPEND); + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeConnectionsStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + \file_put_contents(static::$_statisticsFile, "--------------------------------------------------------------------- WORKERMAN CONNECTION STATUS --------------------------------------------------------------------------------\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, "PID Worker CID Trans Protocol ipv4 ipv6 Recv-Q Send-Q Bytes-R Bytes-W Status Local Address Foreign Address\n", \FILE_APPEND); + \chmod(static::$_statisticsFile, 0722); + foreach (static::getAllWorkerPids() as $worker_pid) { + \posix_kill($worker_pid, \SIGIO); + } + return; + } + + // For child processes. + $bytes_format = function($bytes) + { + if($bytes > 1024*1024*1024*1024) { + return round($bytes/(1024*1024*1024*1024), 1)."TB"; + } + if($bytes > 1024*1024*1024) { + return round($bytes/(1024*1024*1024), 1)."GB"; + } + if($bytes > 1024*1024) { + return round($bytes/(1024*1024), 1)."MB"; + } + if($bytes > 1024) { + return round($bytes/(1024), 1)."KB"; + } + return $bytes."B"; + }; + + $pid = \posix_getpid(); + $str = ''; + \reset(static::$_workers); + $current_worker = current(static::$_workers); + $default_worker_name = $current_worker->name; + + /** @var \Workerman\Worker $worker */ + foreach(TcpConnection::$connections as $connection) { + /** @var \Workerman\Connection\TcpConnection $connection */ + $transport = $connection->transport; + $ipv4 = $connection->isIpV4() ? ' 1' : ' 0'; + $ipv6 = $connection->isIpV6() ? ' 1' : ' 0'; + $recv_q = $bytes_format($connection->getRecvBufferQueueSize()); + $send_q = $bytes_format($connection->getSendBufferQueueSize()); + $local_address = \trim($connection->getLocalAddress()); + $remote_address = \trim($connection->getRemoteAddress()); + $state = $connection->getStatus(false); + $bytes_read = $bytes_format($connection->bytesRead); + $bytes_written = $bytes_format($connection->bytesWritten); + $id = $connection->id; + $protocol = $connection->protocol ? $connection->protocol : $connection->transport; + $pos = \strrpos($protocol, '\\'); + if ($pos) { + $protocol = \substr($protocol, $pos+1); + } + if (\strlen($protocol) > 15) { + $protocol = \substr($protocol, 0, 13) . '..'; + } + $worker_name = isset($connection->worker) ? $connection->worker->name : $default_worker_name; + if (\strlen($worker_name) > 14) { + $worker_name = \substr($worker_name, 0, 12) . '..'; + } + $str .= \str_pad($pid, 9) . \str_pad($worker_name, 16) . \str_pad($id, 10) . \str_pad($transport, 8) + . \str_pad($protocol, 16) . \str_pad($ipv4, 7) . \str_pad($ipv6, 7) . \str_pad($recv_q, 13) + . \str_pad($send_q, 13) . \str_pad($bytes_read, 13) . \str_pad($bytes_written, 13) . ' ' + . \str_pad($state, 14) . ' ' . \str_pad($local_address, 22) . ' ' . \str_pad($remote_address, 22) ."\n"; + } + if ($str) { + \file_put_contents(static::$_statisticsFile, $str, \FILE_APPEND); + } + } + + /** + * Check errors when current process exited. + * + * @return void + */ + public static function checkErrors() + { + if (static::STATUS_SHUTDOWN !== static::$_status) { + $error_msg = static::$_OS === \OS_TYPE_LINUX ? 'Worker['. \posix_getpid() .'] process terminated' : 'Worker process terminated'; + $errors = error_get_last(); + if ($errors && ($errors['type'] === \E_ERROR || + $errors['type'] === \E_PARSE || + $errors['type'] === \E_CORE_ERROR || + $errors['type'] === \E_COMPILE_ERROR || + $errors['type'] === \E_RECOVERABLE_ERROR) + ) { + $error_msg .= ' with ERROR: ' . static::getErrorType($errors['type']) . " \"{$errors['message']} in {$errors['file']} on line {$errors['line']}\""; + } + static::log($error_msg); + } + } + + /** + * Get error message by error code. + * + * @param integer $type + * @return string + */ + protected static function getErrorType($type) + { + if(isset(self::$_errorType[$type])) { + return self::$_errorType[$type]; + } + + return ''; + } + + /** + * Log. + * + * @param string $msg + * @return void + */ + public static function log($msg) + { + $msg = $msg . "\n"; + if (!static::$daemonize) { + static::safeEcho($msg); + } + \file_put_contents((string)static::$logFile, \date('Y-m-d H:i:s') . ' ' . 'pid:' + . (static::$_OS === \OS_TYPE_LINUX ? \posix_getpid() : 1) . ' ' . $msg, \FILE_APPEND | \LOCK_EX); + } + + /** + * Safe Echo. + * @param string $msg + * @param bool $decorated + * @return bool + */ + public static function safeEcho($msg, $decorated = false) + { + $stream = static::outputStream(); + if (!$stream) { + return false; + } + if (!$decorated) { + $line = $white = $green = $end = ''; + if (static::$_outputDecorated) { + $line = "\033[1A\n\033[K"; + $white = "\033[47;30m"; + $green = "\033[32;40m"; + $end = "\033[0m"; + } + $msg = \str_replace(array('', '', ''), array($line, $white, $green), $msg); + $msg = \str_replace(array('', '', ''), $end, $msg); + } elseif (!static::$_outputDecorated) { + return false; + } + set_error_handler(function(){}); + if (!feof($stream)) { + fwrite($stream, $msg); + fflush($stream); + } + restore_error_handler(); + return true; + } + + /** + * @param resource|null $stream + * @return bool|resource + */ + private static function outputStream($stream = null) + { + if (!$stream) { + $stream = static::$_outputStream ? static::$_outputStream : \STDOUT; + } + if (!$stream || !\is_resource($stream) || 'stream' !== \get_resource_type($stream)) { + return false; + } + $stat = \fstat($stream); + if (!$stat) { + return false; + } + if (($stat['mode'] & 0170000) === 0100000) { + // file + static::$_outputDecorated = false; + } else { + static::$_outputDecorated = + static::$_OS === \OS_TYPE_LINUX && + \function_exists('posix_isatty') && + \posix_isatty($stream); + } + return static::$_outputStream = $stream; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', array $context_option = array()) + { + // Save all worker instances. + $this->workerId = \spl_object_hash($this); + static::$_workers[$this->workerId] = $this; + static::$_pidMap[$this->workerId] = array(); + + // Get autoload root path. + $backtrace = \debug_backtrace(); + $this->_autoloadRootPath = \dirname($backtrace[0]['file']); + Autoloader::setRootPath($this->_autoloadRootPath); + + // Context for socket. + if ($socket_name) { + $this->_socketName = $socket_name; + if (!isset($context_option['socket']['backlog'])) { + $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG; + } + $this->_context = \stream_context_create($context_option); + } + + // Turn reusePort on. + /*if (static::$_OS === \OS_TYPE_LINUX // if linux + && \version_compare(\PHP_VERSION,'7.0.0', 'ge') // if php >= 7.0.0 + && \version_compare(php_uname('r'), '3.9', 'ge') // if kernel >=3.9 + && \strtolower(\php_uname('s')) !== 'darwin' // if not Mac OS + && strpos($socket_name,'unix') !== 0) { // if not unix socket + + $this->reusePort = true; + }*/ + } + + + /** + * Listen. + * + * @throws Exception + */ + public function listen() + { + if (!$this->_socketName) { + return; + } + + // Autoload. + Autoloader::setRootPath($this->_autoloadRootPath); + + if (!$this->_mainSocket) { + + $local_socket = $this->parseSocketAddress(); + + // Flag. + $flags = $this->transport === 'udp' ? \STREAM_SERVER_BIND : \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN; + $errno = 0; + $errmsg = ''; + // SO_REUSEPORT. + if ($this->reusePort) { + \stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); + } + + // Create an Internet or Unix domain server socket. + $this->_mainSocket = \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); + if (!$this->_mainSocket) { + throw new Exception($errmsg); + } + + if ($this->transport === 'ssl') { + \stream_socket_enable_crypto($this->_mainSocket, false); + } elseif ($this->transport === 'unix') { + $socket_file = \substr($local_socket, 7); + if ($this->user) { + \chown($socket_file, $this->user); + } + if ($this->group) { + \chgrp($socket_file, $this->group); + } + } + + // Try to open keepalive for tcp and disable Nagle algorithm. + if (\function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') { + \set_error_handler(function(){}); + $socket = \socket_import_stream($this->_mainSocket); + \socket_set_option($socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); + \socket_set_option($socket, \SOL_TCP, \TCP_NODELAY, 1); + \restore_error_handler(); + } + + // Non blocking. + \stream_set_blocking($this->_mainSocket, false); + } + + $this->resumeAccept(); + } + + /** + * Unlisten. + * + * @return void + */ + public function unlisten() { + $this->pauseAccept(); + if ($this->_mainSocket) { + \set_error_handler(function(){}); + \fclose($this->_mainSocket); + \restore_error_handler(); + $this->_mainSocket = null; + } + } + + /** + * Parse local socket address. + * + * @throws Exception + */ + protected function parseSocketAddress() { + if (!$this->_socketName) { + return; + } + // Get the application layer communication protocol and listening address. + list($scheme, $address) = \explode(':', $this->_socketName, 2); + // Check application layer protocol class. + if (!isset(static::$_builtinTransports[$scheme])) { + $scheme = \ucfirst($scheme); + $this->protocol = \substr($scheme,0,1)==='\\' ? $scheme : 'Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + + if (!isset(static::$_builtinTransports[$this->transport])) { + throw new Exception('Bad worker->transport ' . \var_export($this->transport, true)); + } + } else { + $this->transport = $scheme; + } + //local socket + return static::$_builtinTransports[$this->transport] . ":" . $address; + } + + /** + * Pause accept new connections. + * + * @return void + */ + public function pauseAccept() + { + if (static::$globalEvent && false === $this->_pauseAccept && $this->_mainSocket) { + static::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); + $this->_pauseAccept = true; + } + } + + /** + * Resume accept new connections. + * + * @return void + */ + public function resumeAccept() + { + // Register a listener to be notified when server socket is ready to read. + if (static::$globalEvent && true === $this->_pauseAccept && $this->_mainSocket) { + if ($this->transport !== 'udp') { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); + } else { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); + } + $this->_pauseAccept = false; + } + } + + /** + * Get socket name. + * + * @return string + */ + public function getSocketName() + { + return $this->_socketName ? \lcfirst($this->_socketName) : 'none'; + } + + /** + * Run worker instance. + * + * @return void + * @throws Exception + */ + public function run() + { + $this->listen(); + + // Try to emit onWorkerStart callback. + if ($this->onWorkerStart) { + try { + \call_user_func($this->onWorkerStart, $this); + } catch (\Exception $e) { + // Avoid rapid infinite loop exit. + sleep(1); + static::stopAll(250, $e); + } catch (\Error $e) { + // Avoid rapid infinite loop exit. + sleep(1); + static::stopAll(250, $e); + } + } + } + + /** + * Stop current worker instance. + * + * @return void + */ + public function stop() + { + // Try to emit onWorkerStop callback. + if ($this->onWorkerStop) { + try { + \call_user_func($this->onWorkerStop, $this); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + // Remove listener for server socket. + $this->unlisten(); + // Close all connections for the worker. + if (!static::$_gracefulStop) { + foreach ($this->connections as $connection) { + $connection->close(); + } + } + // Remove worker. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId === $this->workerId) { + unset(static::$_workers[$key]); + } + } + + // Clear callback. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferDrain = $this->onBufferFull = null; + } + + /** + * Accept a connection. + * + * @param resource $socket + * @return void + */ + public function acceptConnection($socket) + { + // Accept a connection on server socket. + \set_error_handler(function(){}); + $new_socket = \stream_socket_accept($socket, 0, $remote_address); + \restore_error_handler(); + + // Thundering herd. + if (!$new_socket) { + return; + } + + // TcpConnection. + $connection = new TcpConnection($new_socket, $remote_address); + $this->connections[$connection->id] = $connection; + $connection->worker = $this; + $connection->protocol = $this->protocol; + $connection->transport = $this->transport; + $connection->onMessage = $this->onMessage; + $connection->onClose = $this->onClose; + $connection->onError = $this->onError; + $connection->onBufferDrain = $this->onBufferDrain; + $connection->onBufferFull = $this->onBufferFull; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $connection); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function acceptUdpConnection($socket) + { + \set_error_handler(function(){}); + $recv_buffer = \stream_socket_recvfrom($socket, static::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + \restore_error_handler(); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + // UdpConnection. + $connection = new UdpConnection($socket, $remote_address); + $connection->protocol = $this->protocol; + if ($this->onMessage) { + try { + if ($this->protocol !== null) { + /** @var \Workerman\Protocols\ProtocolInterface $parser */ + $parser = $this->protocol; + if ($parser && \method_exists($parser, 'input')) { + while ($recv_buffer !== '') { + $len = $parser::input($recv_buffer, $connection); + if ($len === 0) + return true; + $package = \substr($recv_buffer, 0, $len); + $recv_buffer = \substr($recv_buffer, $len); + $data = $parser::decode($package, $connection); + if ($data === false) + continue; + \call_user_func($this->onMessage, $connection, $data); + } + } else { + $data = $parser::decode($recv_buffer, $connection); + // Discard bad packets. + if ($data === false) + return true; + \call_user_func($this->onMessage, $connection, $data); + } + } else { + \call_user_func($this->onMessage, $connection, $recv_buffer); + } + ++ConnectionInterface::$statistics['total_request']; + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + return true; + } + + /** + * Check master process is alive + * + * @param int $master_pid + * @return bool + */ + protected static function checkMasterIsAlive($master_pid) + { + if (empty($master_pid)) { + return false; + } + + $master_is_alive = $master_pid && \posix_kill((int) $master_pid, 0) && \posix_getpid() !== $master_pid; + if (!$master_is_alive) { + return false; + } + + $cmdline = "/proc/{$master_pid}/cmdline"; + if (!is_readable($cmdline) || empty(static::$processTitle)) { + return true; + } + + $content = file_get_contents($cmdline); + if (empty($content)) { + return true; + } + + return stripos($content, static::$processTitle) !== false || stripos($content, 'php') !== false; + } +} diff --git a/vendor/workerman/workerman/composer.json b/vendor/workerman/workerman/composer.json new file mode 100644 index 0000000..b9897b1 --- /dev/null +++ b/vendor/workerman/workerman/composer.json @@ -0,0 +1,38 @@ +{ + "name": "workerman/workerman", + "type": "library", + "keywords": [ + "event-loop", + "asynchronous" + ], + "homepage": "http://www.workerman.net", + "license": "MIT", + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "support": { + "email": "walkor@workerman.net", + "issues": "https://github.com/walkor/workerman/issues", + "forum": "http://wenda.workerman.net/", + "wiki": "http://doc.workerman.net/", + "source": "https://github.com/walkor/workerman" + }, + "require": { + "php": ">=8.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "minimum-stability": "dev" +}