163 changed files with 23625 additions and 0 deletions
@ -0,0 +1,7 @@ |
|||||
|
<?php |
||||
|
|
||||
|
// autoload.php @generated by Composer |
||||
|
|
||||
|
require_once __DIR__ . '/composer/autoload_real.php'; |
||||
|
|
||||
|
return ComposerAutoloaderInitabe3555d91d8f0317ed20df0cce34e14::getLoader(); |
||||
@ -0,0 +1,3 @@ |
|||||
|
phpunit.xml |
||||
|
composer.lock |
||||
|
/vendor/ |
||||
@ -0,0 +1,40 @@ |
|||||
|
language: php |
||||
|
|
||||
|
env: |
||||
|
matrix: |
||||
|
- PIMPLE_EXT=no |
||||
|
- PIMPLE_EXT=yes |
||||
|
global: |
||||
|
- REPORT_EXIT_STATUS=1 |
||||
|
|
||||
|
php: |
||||
|
- 5.3 |
||||
|
- 5.4 |
||||
|
- 5.5 |
||||
|
- 5.6 |
||||
|
- 7.0 |
||||
|
- 7.1 |
||||
|
|
||||
|
before_script: |
||||
|
- composer self-update |
||||
|
- COMPOSER_ROOT_VERSION=dev-master composer install |
||||
|
- if [ "$PIMPLE_EXT" == "yes" ]; then sh -c "cd ext/pimple && phpize && ./configure && make && sudo make install"; fi |
||||
|
- if [ "$PIMPLE_EXT" == "yes" ]; then echo "extension=pimple.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi |
||||
|
|
||||
|
script: |
||||
|
- cd ext/pimple |
||||
|
- if [ "$PIMPLE_EXT" == "yes" ]; then yes n | make test | tee output ; grep -E 'Tests failed +. +0' output; fi |
||||
|
- if [ "$PIMPLE_EXT" == "yes" ]; then export SYMFONY_DEPRECATIONS_HELPER=weak; fi |
||||
|
- cd ../.. |
||||
|
- ./vendor/bin/simple-phpunit |
||||
|
|
||||
|
matrix: |
||||
|
include: |
||||
|
- php: hhvm |
||||
|
dist: trusty |
||||
|
env: PIMPLE_EXT=no |
||||
|
exclude: |
||||
|
- php: 7.0 |
||||
|
env: PIMPLE_EXT=yes |
||||
|
- php: 7.1 |
||||
|
env: PIMPLE_EXT=yes |
||||
@ -0,0 +1,59 @@ |
|||||
|
* 3.2.3 (2017-XX-XX) |
||||
|
|
||||
|
* n/a |
||||
|
|
||||
|
* 3.2.2 (2017-07-23) |
||||
|
|
||||
|
* reverted extending a protected closure throws an exception (deprecated it instead) |
||||
|
|
||||
|
* 3.2.1 (2017-07-17) |
||||
|
|
||||
|
* fixed PHP error |
||||
|
|
||||
|
* 3.2.0 (2017-07-17) |
||||
|
|
||||
|
* added a PSR-11 service locator |
||||
|
* added a PSR-11 wrapper |
||||
|
* added ServiceIterator |
||||
|
* fixed extending a protected closure (now throws InvalidServiceIdentifierException) |
||||
|
|
||||
|
* 3.1.0 (2017-07-03) |
||||
|
|
||||
|
* deprecated the C extension |
||||
|
* added support for PSR-11 exceptions |
||||
|
|
||||
|
* 3.0.2 (2015-09-11) |
||||
|
|
||||
|
* refactored the C extension |
||||
|
* minor non-significant changes |
||||
|
|
||||
|
* 3.0.1 (2015-07-30) |
||||
|
|
||||
|
* simplified some code |
||||
|
* fixed a segfault in the C extension |
||||
|
|
||||
|
* 3.0.0 (2014-07-24) |
||||
|
|
||||
|
* removed the Pimple class alias (use Pimple\Container instead) |
||||
|
|
||||
|
* 2.1.1 (2014-07-24) |
||||
|
|
||||
|
* fixed compiler warnings for the C extension |
||||
|
* fixed code when dealing with circular references |
||||
|
|
||||
|
* 2.1.0 (2014-06-24) |
||||
|
|
||||
|
* moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a |
||||
|
deprecated alias which will be removed in Pimple 3.0) |
||||
|
* added Pimple\ServiceProviderInterface (and Pimple::register()) |
||||
|
|
||||
|
* 2.0.0 (2014-02-10) |
||||
|
|
||||
|
* changed extend to automatically re-assign the extended service and keep it as shared or factory |
||||
|
(to keep BC, extend still returns the extended service) |
||||
|
* changed services to be shared by default (use factory() for factory |
||||
|
services) |
||||
|
|
||||
|
* 1.0.0 |
||||
|
|
||||
|
* initial version |
||||
@ -0,0 +1,19 @@ |
|||||
|
Copyright (c) 2009-2017 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 |
||||
|
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. |
||||
@ -0,0 +1,326 @@ |
|||||
|
Pimple |
||||
|
====== |
||||
|
|
||||
|
.. caution:: |
||||
|
|
||||
|
This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read |
||||
|
the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good |
||||
|
way to learn more about how to create a simple Dependency Injection |
||||
|
Container (recent versions of Pimple are more focused on performance). |
||||
|
|
||||
|
Pimple is a small Dependency Injection Container for PHP. |
||||
|
|
||||
|
Installation |
||||
|
------------ |
||||
|
|
||||
|
Before using Pimple in your project, add it to your ``composer.json`` file: |
||||
|
|
||||
|
.. code-block:: bash |
||||
|
|
||||
|
$ ./composer.phar require pimple/pimple "^3.0" |
||||
|
|
||||
|
Usage |
||||
|
----- |
||||
|
|
||||
|
Creating a container is a matter of creating a ``Container`` instance: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
use Pimple\Container; |
||||
|
|
||||
|
$container = new Container(); |
||||
|
|
||||
|
As many other dependency injection containers, Pimple manages two different |
||||
|
kind of data: **services** and **parameters**. |
||||
|
|
||||
|
Defining Services |
||||
|
~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
A service is an object that does something as part of a larger system. Examples |
||||
|
of services: a database connection, a templating engine, or a mailer. Almost |
||||
|
any **global** object can be a service. |
||||
|
|
||||
|
Services are defined by **anonymous functions** that return an instance of an |
||||
|
object: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
// define some services |
||||
|
$container['session_storage'] = function ($c) { |
||||
|
return new SessionStorage('SESSION_ID'); |
||||
|
}; |
||||
|
|
||||
|
$container['session'] = function ($c) { |
||||
|
return new Session($c['session_storage']); |
||||
|
}; |
||||
|
|
||||
|
Notice that the anonymous function has access to the current container |
||||
|
instance, allowing references to other services or parameters. |
||||
|
|
||||
|
As objects are only created when you get them, the order of the definitions |
||||
|
does not matter. |
||||
|
|
||||
|
Using the defined services is also very easy: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
// get the session object |
||||
|
$session = $container['session']; |
||||
|
|
||||
|
// the above call is roughly equivalent to the following code: |
||||
|
// $storage = new SessionStorage('SESSION_ID'); |
||||
|
// $session = new Session($storage); |
||||
|
|
||||
|
Defining Factory Services |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
By default, each time you get a service, Pimple returns the **same instance** |
||||
|
of it. If you want a different instance to be returned for all calls, wrap your |
||||
|
anonymous function with the ``factory()`` method |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
$container['session'] = $container->factory(function ($c) { |
||||
|
return new Session($c['session_storage']); |
||||
|
}); |
||||
|
|
||||
|
Now, each call to ``$container['session']`` returns a new instance of the |
||||
|
session. |
||||
|
|
||||
|
Defining Parameters |
||||
|
~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Defining a parameter allows to ease the configuration of your container from |
||||
|
the outside and to store global values: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
// define some parameters |
||||
|
$container['cookie_name'] = 'SESSION_ID'; |
||||
|
$container['session_storage_class'] = 'SessionStorage'; |
||||
|
|
||||
|
If you change the ``session_storage`` service definition like below: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
$container['session_storage'] = function ($c) { |
||||
|
return new $c['session_storage_class']($c['cookie_name']); |
||||
|
}; |
||||
|
|
||||
|
You can now easily change the cookie name by overriding the |
||||
|
``cookie_name`` parameter instead of redefining the service |
||||
|
definition. |
||||
|
|
||||
|
Protecting Parameters |
||||
|
~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Because Pimple sees anonymous functions as service definitions, you need to |
||||
|
wrap anonymous functions with the ``protect()`` method to store them as |
||||
|
parameters: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
$container['random_func'] = $container->protect(function () { |
||||
|
return rand(); |
||||
|
}); |
||||
|
|
||||
|
Modifying Services after Definition |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
In some cases you may want to modify a service definition after it has been |
||||
|
defined. You can use the ``extend()`` method to define additional code to be |
||||
|
run on your service just after it is created: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
$container['session_storage'] = function ($c) { |
||||
|
return new $c['session_storage_class']($c['cookie_name']); |
||||
|
}; |
||||
|
|
||||
|
$container->extend('session_storage', function ($storage, $c) { |
||||
|
$storage->...(); |
||||
|
|
||||
|
return $storage; |
||||
|
}); |
||||
|
|
||||
|
The first argument is the name of the service to extend, the second a function |
||||
|
that gets access to the object instance and the container. |
||||
|
|
||||
|
Extending a Container |
||||
|
~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
If you use the same libraries over and over, you might want to reuse some |
||||
|
services from one project to the next one; package your services into a |
||||
|
**provider** by implementing ``Pimple\ServiceProviderInterface``: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
use Pimple\Container; |
||||
|
|
||||
|
class FooProvider implements Pimple\ServiceProviderInterface |
||||
|
{ |
||||
|
public function register(Container $pimple) |
||||
|
{ |
||||
|
// register some services and parameters |
||||
|
// on $pimple |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Then, register the provider on a Container: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
$pimple->register(new FooProvider()); |
||||
|
|
||||
|
Fetching the Service Creation Function |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
When you access an object, Pimple automatically calls the anonymous function |
||||
|
that you defined, which creates the service object for you. If you want to get |
||||
|
raw access to this function, you can use the ``raw()`` method: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
$container['session'] = function ($c) { |
||||
|
return new Session($c['session_storage']); |
||||
|
}; |
||||
|
|
||||
|
$sessionFunction = $container->raw('session'); |
||||
|
|
||||
|
PSR-11 compatibility |
||||
|
-------------------- |
||||
|
|
||||
|
For historical reasons, the ``Container`` class does not implement the PSR-11 |
||||
|
``ContainerInterface``. However, Pimple provides a helper class that will let |
||||
|
you decouple your code from the Pimple container class. |
||||
|
|
||||
|
The PSR-11 container class |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
The ``Pimple\Psr11\Container`` class lets you access the content of an |
||||
|
underlying Pimple container using ``Psr\Container\ContainerInterface`` |
||||
|
methods: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
use Pimple\Container; |
||||
|
use Pimple\Psr11\Container as PsrContainer; |
||||
|
|
||||
|
$container = new Container(); |
||||
|
$container['service'] = function ($c) { |
||||
|
return new Service(); |
||||
|
}; |
||||
|
$psr11 = new PsrContainer($container); |
||||
|
|
||||
|
$controller = function (PsrContainer $container) { |
||||
|
$service = $container->get('service'); |
||||
|
}; |
||||
|
$controller($psr11); |
||||
|
|
||||
|
Using the PSR-11 ServiceLocator |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Sometimes, a service needs access to several other services without being sure |
||||
|
that all of them will actually be used. In those cases, you may want the |
||||
|
instantiation of the services to be lazy. |
||||
|
|
||||
|
The traditional solution is to inject the entire service container to get only |
||||
|
the services really needed. However, this is not recommended because it gives |
||||
|
services a too broad access to the rest of the application and it hides their |
||||
|
actual dependencies. |
||||
|
|
||||
|
The ``ServiceLocator`` is intended to solve this problem by giving access to a |
||||
|
set of predefined services while instantiating them only when actually needed. |
||||
|
|
||||
|
It also allows you to make your services available under a different name than |
||||
|
the one used to register them. For instance, you may want to use an object |
||||
|
that expects an instance of ``EventDispatcherInterface`` to be available under |
||||
|
the name ``event_dispatcher`` while your event dispatcher has been |
||||
|
registered under the name ``dispatcher``: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
use Monolog\Logger; |
||||
|
use Pimple\Psr11\ServiceLocator; |
||||
|
use Psr\Container\ContainerInterface; |
||||
|
use Symfony\Component\EventDispatcher\EventDispatcher; |
||||
|
|
||||
|
class MyService |
||||
|
{ |
||||
|
/** |
||||
|
* "logger" must be an instance of Psr\Log\LoggerInterface |
||||
|
* "event_dispatcher" must be an instance of Symfony\Component\EventDispatcher\EventDispatcherInterface |
||||
|
*/ |
||||
|
private $services; |
||||
|
|
||||
|
public function __construct(ContainerInterface $services) |
||||
|
{ |
||||
|
$this->services = $services; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$container['logger'] = function ($c) { |
||||
|
return new Monolog\Logger(); |
||||
|
}; |
||||
|
$container['dispatcher'] = function () { |
||||
|
return new EventDispatcher(); |
||||
|
}; |
||||
|
|
||||
|
$container['service'] = function ($c) { |
||||
|
$locator = new ServiceLocator($c, array('logger', 'event_dispatcher' => 'dispatcher')); |
||||
|
|
||||
|
return new MyService($locator); |
||||
|
}; |
||||
|
|
||||
|
Referencing a Collection of Services Lazily |
||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||||
|
|
||||
|
Passing a collection of services instances in an array may prove inefficient |
||||
|
if the class that consumes the collection only needs to iterate over it at a |
||||
|
later stage, when one of its method is called. It can also lead to problems |
||||
|
if there is a circular dependency between one of the services stored in the |
||||
|
collection and the class that consumes it. |
||||
|
|
||||
|
The ``ServiceIterator`` class helps you solve these issues. It receives a |
||||
|
list of service names during instantiation and will retrieve the services |
||||
|
when iterated over: |
||||
|
|
||||
|
.. code-block:: php |
||||
|
|
||||
|
use Pimple\Container; |
||||
|
use Pimple\ServiceIterator; |
||||
|
|
||||
|
class AuthorizationService |
||||
|
{ |
||||
|
private $voters; |
||||
|
|
||||
|
public function __construct($voters) |
||||
|
{ |
||||
|
$this->voters = $voters; |
||||
|
} |
||||
|
|
||||
|
public function canAccess($resource) |
||||
|
{ |
||||
|
foreach ($this->voters as $voter) { |
||||
|
if (true === $voter->canAccess($resource) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$container = new Container(); |
||||
|
|
||||
|
$container['voter1'] = function ($c) { |
||||
|
return new SomeVoter(); |
||||
|
} |
||||
|
$container['voter2'] = function ($c) { |
||||
|
return new SomeOtherVoter($c['auth']); |
||||
|
} |
||||
|
$container['auth'] = function ($c) { |
||||
|
return new AuthorizationService(new ServiceIterator($c, array('voter1', 'voter2')); |
||||
|
} |
||||
|
|
||||
|
.. _Pimple 1.x documentation: https://github.com/silexphp/Pimple/tree/1.1 |
||||
@ -0,0 +1,29 @@ |
|||||
|
{ |
||||
|
"name": "pimple/pimple", |
||||
|
"type": "library", |
||||
|
"description": "Pimple, a simple Dependency Injection Container", |
||||
|
"keywords": ["dependency injection", "container"], |
||||
|
"homepage": "http://pimple.sensiolabs.org", |
||||
|
"license": "MIT", |
||||
|
"authors": [ |
||||
|
{ |
||||
|
"name": "Fabien Potencier", |
||||
|
"email": "fabien@symfony.com" |
||||
|
} |
||||
|
], |
||||
|
"require": { |
||||
|
"php": ">=5.3.0", |
||||
|
"psr/container": "^1.0" |
||||
|
}, |
||||
|
"require-dev": { |
||||
|
"symfony/phpunit-bridge": "^3.2" |
||||
|
}, |
||||
|
"autoload": { |
||||
|
"psr-0": { "Pimple": "src/" } |
||||
|
}, |
||||
|
"extra": { |
||||
|
"branch-alias": { |
||||
|
"dev-master": "3.2.x-dev" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
*.sw* |
||||
|
.deps |
||||
|
Makefile |
||||
|
Makefile.fragments |
||||
|
Makefile.global |
||||
|
Makefile.objects |
||||
|
acinclude.m4 |
||||
|
aclocal.m4 |
||||
|
build/ |
||||
|
config.cache |
||||
|
config.guess |
||||
|
config.h |
||||
|
config.h.in |
||||
|
config.log |
||||
|
config.nice |
||||
|
config.status |
||||
|
config.sub |
||||
|
configure |
||||
|
configure.in |
||||
|
install-sh |
||||
|
libtool |
||||
|
ltmain.sh |
||||
|
missing |
||||
|
mkinstalldirs |
||||
|
run-tests.php |
||||
|
*.loT |
||||
|
.libs/ |
||||
|
modules/ |
||||
|
*.la |
||||
|
*.lo |
||||
@ -0,0 +1,12 @@ |
|||||
|
This is Pimple 2 implemented in C |
||||
|
|
||||
|
* PHP >= 5.3 |
||||
|
* Not tested under Windows, might work |
||||
|
|
||||
|
Install |
||||
|
======= |
||||
|
|
||||
|
> phpize |
||||
|
> ./configure |
||||
|
> make |
||||
|
> make install |
||||
@ -0,0 +1,63 @@ |
|||||
|
dnl $Id$ |
||||
|
dnl config.m4 for extension pimple |
||||
|
|
||||
|
dnl Comments in this file start with the string 'dnl'. |
||||
|
dnl Remove where necessary. This file will not work |
||||
|
dnl without editing. |
||||
|
|
||||
|
dnl If your extension references something external, use with: |
||||
|
|
||||
|
dnl PHP_ARG_WITH(pimple, for pimple support, |
||||
|
dnl Make sure that the comment is aligned: |
||||
|
dnl [ --with-pimple Include pimple support]) |
||||
|
|
||||
|
dnl Otherwise use enable: |
||||
|
|
||||
|
PHP_ARG_ENABLE(pimple, whether to enable pimple support, |
||||
|
dnl Make sure that the comment is aligned: |
||||
|
[ --enable-pimple Enable pimple support]) |
||||
|
|
||||
|
if test "$PHP_PIMPLE" != "no"; then |
||||
|
dnl Write more examples of tests here... |
||||
|
|
||||
|
dnl # --with-pimple -> check with-path |
||||
|
dnl SEARCH_PATH="/usr/local /usr" # you might want to change this |
||||
|
dnl SEARCH_FOR="/include/pimple.h" # you most likely want to change this |
||||
|
dnl if test -r $PHP_PIMPLE/$SEARCH_FOR; then # path given as parameter |
||||
|
dnl PIMPLE_DIR=$PHP_PIMPLE |
||||
|
dnl else # search default path list |
||||
|
dnl AC_MSG_CHECKING([for pimple files in default path]) |
||||
|
dnl for i in $SEARCH_PATH ; do |
||||
|
dnl if test -r $i/$SEARCH_FOR; then |
||||
|
dnl PIMPLE_DIR=$i |
||||
|
dnl AC_MSG_RESULT(found in $i) |
||||
|
dnl fi |
||||
|
dnl done |
||||
|
dnl fi |
||||
|
dnl |
||||
|
dnl if test -z "$PIMPLE_DIR"; then |
||||
|
dnl AC_MSG_RESULT([not found]) |
||||
|
dnl AC_MSG_ERROR([Please reinstall the pimple distribution]) |
||||
|
dnl fi |
||||
|
|
||||
|
dnl # --with-pimple -> add include path |
||||
|
dnl PHP_ADD_INCLUDE($PIMPLE_DIR/include) |
||||
|
|
||||
|
dnl # --with-pimple -> check for lib and symbol presence |
||||
|
dnl LIBNAME=pimple # you may want to change this |
||||
|
dnl LIBSYMBOL=pimple # you most likely want to change this |
||||
|
|
||||
|
dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, |
||||
|
dnl [ |
||||
|
dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PIMPLE_DIR/lib, PIMPLE_SHARED_LIBADD) |
||||
|
dnl AC_DEFINE(HAVE_PIMPLELIB,1,[ ]) |
||||
|
dnl ],[ |
||||
|
dnl AC_MSG_ERROR([wrong pimple lib version or lib not found]) |
||||
|
dnl ],[ |
||||
|
dnl -L$PIMPLE_DIR/lib -lm |
||||
|
dnl ]) |
||||
|
dnl |
||||
|
dnl PHP_SUBST(PIMPLE_SHARED_LIBADD) |
||||
|
|
||||
|
PHP_NEW_EXTENSION(pimple, pimple.c, $ext_shared) |
||||
|
fi |
||||
@ -0,0 +1,13 @@ |
|||||
|
// $Id$ |
||||
|
// vim:ft=javascript |
||||
|
|
||||
|
// If your extension references something external, use ARG_WITH |
||||
|
// ARG_WITH("pimple", "for pimple support", "no"); |
||||
|
|
||||
|
// Otherwise, use ARG_ENABLE |
||||
|
// ARG_ENABLE("pimple", "enable pimple support", "no"); |
||||
|
|
||||
|
if (PHP_PIMPLE != "no") { |
||||
|
EXTENSION("pimple", "pimple.c"); |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,137 @@ |
|||||
|
|
||||
|
/*
|
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2014 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
#ifndef PHP_PIMPLE_H |
||||
|
#define PHP_PIMPLE_H |
||||
|
|
||||
|
extern zend_module_entry pimple_module_entry; |
||||
|
#define phpext_pimple_ptr &pimple_module_entry |
||||
|
|
||||
|
#ifdef PHP_WIN32 |
||||
|
# define PHP_PIMPLE_API __declspec(dllexport) |
||||
|
#elif defined(__GNUC__) && __GNUC__ >= 4 |
||||
|
# define PHP_PIMPLE_API __attribute__ ((visibility("default"))) |
||||
|
#else |
||||
|
# define PHP_PIMPLE_API |
||||
|
#endif |
||||
|
|
||||
|
#ifdef ZTS |
||||
|
#include "TSRM.h" |
||||
|
#endif |
||||
|
|
||||
|
#define PIMPLE_VERSION "3.2.3-DEV" |
||||
|
|
||||
|
#define PIMPLE_NS "Pimple" |
||||
|
#define PSR_CONTAINER_NS "Psr\\Container" |
||||
|
#define PIMPLE_EXCEPTION_NS "Pimple\\Exception" |
||||
|
|
||||
|
#define PIMPLE_DEFAULT_ZVAL_CACHE_NUM 5 |
||||
|
#define PIMPLE_DEFAULT_ZVAL_VALUES_NUM 10 |
||||
|
|
||||
|
#define PIMPLE_DEPRECATE do { \ |
||||
|
int er = EG(error_reporting); \ |
||||
|
EG(error_reporting) = 0;\ |
||||
|
php_error(E_DEPRECATED, "The Pimple C extension is deprecated since version 3.1 and will be removed in 4.0."); \ |
||||
|
EG(error_reporting) = er; \ |
||||
|
} while (0); |
||||
|
|
||||
|
zend_module_entry *get_module(void); |
||||
|
|
||||
|
PHP_MINIT_FUNCTION(pimple); |
||||
|
PHP_MINFO_FUNCTION(pimple); |
||||
|
|
||||
|
PHP_METHOD(FrozenServiceException, __construct); |
||||
|
PHP_METHOD(InvalidServiceIdentifierException, __construct); |
||||
|
PHP_METHOD(UnknownIdentifierException, __construct); |
||||
|
|
||||
|
PHP_METHOD(Pimple, __construct); |
||||
|
PHP_METHOD(Pimple, factory); |
||||
|
PHP_METHOD(Pimple, protect); |
||||
|
PHP_METHOD(Pimple, raw); |
||||
|
PHP_METHOD(Pimple, extend); |
||||
|
PHP_METHOD(Pimple, keys); |
||||
|
PHP_METHOD(Pimple, register); |
||||
|
PHP_METHOD(Pimple, offsetSet); |
||||
|
PHP_METHOD(Pimple, offsetUnset); |
||||
|
PHP_METHOD(Pimple, offsetGet); |
||||
|
PHP_METHOD(Pimple, offsetExists); |
||||
|
|
||||
|
PHP_METHOD(PimpleClosure, invoker); |
||||
|
|
||||
|
typedef struct _pimple_bucket_value { |
||||
|
zval *value; /* Must be the first element */ |
||||
|
zval *raw; |
||||
|
zend_object_handle handle_num; |
||||
|
enum { |
||||
|
PIMPLE_IS_PARAM = 0, |
||||
|
PIMPLE_IS_SERVICE = 2 |
||||
|
} type; |
||||
|
zend_bool initialized; |
||||
|
zend_fcall_info_cache fcc; |
||||
|
} pimple_bucket_value; |
||||
|
|
||||
|
typedef struct _pimple_object { |
||||
|
zend_object zobj; |
||||
|
HashTable values; |
||||
|
HashTable factories; |
||||
|
HashTable protected; |
||||
|
} pimple_object; |
||||
|
|
||||
|
typedef struct _pimple_closure_object { |
||||
|
zend_object zobj; |
||||
|
zval *callable; |
||||
|
zval *factory; |
||||
|
} pimple_closure_object; |
||||
|
|
||||
|
static const char sensiolabs_logo[] = "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHYAAAAUCAMAAABvRTlyAAAAz1BMVEUAAAAAAAAAAAAsThWB5j4AAACD6T8AAACC6D+C6D6C6D+C6D4AAAAAAACC6D4AAAAAAACC6D8AAAAAAAAAAAAAAAAAAAAAAACC6D4AAAAAAAAAAACC6D4AAAAAAAAAAAAAAAAAAAAAAACC6D8AAACC6D4AAAAAAAAAAAAAAAAAAACC6D8AAACC6D6C6D+B6D+C6D+C6D+C6D8AAACC6D6C6D4AAACC6D/K/2KC6D+B6D6C6D6C6D+C6D8sTxUyWRhEeiEAAACC6D+C5z6B6D7drnEVAAAAQXRSTlMAE3oCNSUuDHFHzxaF9UFsu+irX+zlKzYimaJXktyOSFD6BolxqT7QGMMdarMIpuO28r9EolXKgR16OphfXYd4V14GtB4AAAMpSURBVEjHvVSJctowEF1jjME2RziMwUCoMfd9heZqG4n//6buLpJjkmYm03byZmxJa2nf6u2uQcG2bfhqRN4LoTKBzyGDm68M7mAwcOEdjo4zhA/Rf9Go/CVtTgiRhXfIC3EDH8F/eUX1/9KexRo+QgOdtHDsEe/sM7QT32/+K61Z1LFXcXJxN4pTbu1aTQUzuy2PIA0rDo0/0Aa5XFaJvKaVTrubywXvaa1Wq4Vu/Snr3Y7Aojh4VccwykW2N2oQ8wmjyut6+Q1t5ywIG5Npj1sh5E0B7YOzFDjfuRfaOh3O+MbbVNfTWS9COZk3Obd2su5d0a6IU9KLREbw8gEehWSr1r2sPWciXLG38r5NdW0xu9eioU87omjC9yNaMi5GNf6WppVSOqXCFkmCvMB3p9SROLoYQn5pDgQOujA1xjYvqH+plUdkwnmII8VxR/PKYkrfLLomhVlE3b/LhNbNr7hp0H2JaOc4v8dFB58HSsFTSafaqtY1sT3GO8wsy5rhokYPlRJdjPMajyYqTt1EHF/2uqSWQWmAjCUSmQ1MS3g8Btf1XOsy7YIC0CB1b5Xw1Vhba0zbxiCAQLH9TNPmHJXQUtJAN0KcDsoqLxsNvJrJExa7mKIdp2lRE2WexiS4pqWk/0jROlw6K6bV9YOBDGAuqMJ0bnuUKGB0L27bxgRhGEbzihbhxxXaQC88Vkwq8ldCi86RApWUb0Q+4VDosBCc+1s81lUdnBavH4Zp2mm3O44USwOfvSo9oBiwpFg71lMS1VKJLKljS3j9p+fOTvXXlsSNuEv6YPaZda9uRope0VJfKdo7fPiYfSmvFjXQbkhY0d9hCbBWIktRgEDieDhf1N3wbbkmNNgRy8hyl620yGQat/grV3HMpc2HDKTVmOPFz6ylPCKt/nXcAyV260jaAowwIW0YuBzrOgb/KrddZS9OmJaLgpWK4JX2DDuklcLZSDGcn8Vmx9YDNvT6UsjyBApRyFQVX7Vxm9TGxE16nmfRd8/zQoDmggQOTRh5Hv8pMt9Q/L2JmSwkMCE7dA4BuDjHJwfu0Om4QAhOjrN5XkIatglfiN/bUPdCQFjTYgAAAABJRU5ErkJggg==\">"; |
||||
|
|
||||
|
static void pimple_exception_call_parent_constructor(zval *this_ptr, const char *format, const char *arg1 TSRMLS_DC); |
||||
|
|
||||
|
static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); |
||||
|
static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); |
||||
|
|
||||
|
static void pimple_bucket_dtor(pimple_bucket_value *bucket); |
||||
|
static void pimple_free_bucket(pimple_bucket_value *bucket); |
||||
|
|
||||
|
static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC); |
||||
|
static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC); |
||||
|
static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC); |
||||
|
static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC); |
||||
|
static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC); |
||||
|
static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC); |
||||
|
|
||||
|
static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC); |
||||
|
static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC); |
||||
|
static zend_function *pimple_closure_get_constructor(zval * TSRMLS_DC); |
||||
|
static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC); |
||||
|
|
||||
|
#ifdef ZTS |
||||
|
#define PIMPLE_G(v) TSRMG(pimple_globals_id, zend_pimple_globals *, v) |
||||
|
#else |
||||
|
#define PIMPLE_G(v) (pimple_globals.v) |
||||
|
#endif |
||||
|
|
||||
|
#endif /* PHP_PIMPLE_H */ |
||||
|
|
||||
File diff suppressed because it is too large
@ -0,0 +1,81 @@ |
|||||
|
|
||||
|
/*
|
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2014 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
#ifndef PIMPLE_COMPAT_H_ |
||||
|
#define PIMPLE_COMPAT_H_ |
||||
|
|
||||
|
#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ |
||||
|
|
||||
|
#define PHP_5_0_X_API_NO 220040412 |
||||
|
#define PHP_5_1_X_API_NO 220051025 |
||||
|
#define PHP_5_2_X_API_NO 220060519 |
||||
|
#define PHP_5_3_X_API_NO 220090626 |
||||
|
#define PHP_5_4_X_API_NO 220100525 |
||||
|
#define PHP_5_5_X_API_NO 220121212 |
||||
|
#define PHP_5_6_X_API_NO 220131226 |
||||
|
|
||||
|
#define IS_PHP_56 ZEND_EXTENSION_API_NO == PHP_5_6_X_API_NO |
||||
|
#define IS_AT_LEAST_PHP_56 ZEND_EXTENSION_API_NO >= PHP_5_6_X_API_NO |
||||
|
|
||||
|
#define IS_PHP_55 ZEND_EXTENSION_API_NO == PHP_5_5_X_API_NO |
||||
|
#define IS_AT_LEAST_PHP_55 ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO |
||||
|
|
||||
|
#define IS_PHP_54 ZEND_EXTENSION_API_NO == PHP_5_4_X_API_NO |
||||
|
#define IS_AT_LEAST_PHP_54 ZEND_EXTENSION_API_NO >= PHP_5_4_X_API_NO |
||||
|
|
||||
|
#define IS_PHP_53 ZEND_EXTENSION_API_NO == PHP_5_3_X_API_NO |
||||
|
#define IS_AT_LEAST_PHP_53 ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO |
||||
|
|
||||
|
#if IS_PHP_53 |
||||
|
#define object_properties_init(obj, ce) do { \ |
||||
|
zend_hash_copy(obj->properties, &ce->default_properties, zval_copy_property_ctor(ce), NULL, sizeof(zval *)); \ |
||||
|
} while (0); |
||||
|
#endif |
||||
|
|
||||
|
#define ZEND_OBJ_INIT(obj, ce) do { \ |
||||
|
zend_object_std_init(obj, ce TSRMLS_CC); \ |
||||
|
object_properties_init((obj), (ce)); \ |
||||
|
} while(0); |
||||
|
|
||||
|
#if IS_PHP_53 || IS_PHP_54 |
||||
|
static void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) { |
||||
|
Bucket *p; |
||||
|
|
||||
|
p = pos ? (*pos) : ht->pInternalPointer; |
||||
|
|
||||
|
if (!p) { |
||||
|
Z_TYPE_P(key) = IS_NULL; |
||||
|
} else if (p->nKeyLength) { |
||||
|
Z_TYPE_P(key) = IS_STRING; |
||||
|
Z_STRVAL_P(key) = estrndup(p->arKey, p->nKeyLength - 1); |
||||
|
Z_STRLEN_P(key) = p->nKeyLength - 1; |
||||
|
} else { |
||||
|
Z_TYPE_P(key) = IS_LONG; |
||||
|
Z_LVAL_P(key) = p->h; |
||||
|
} |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
#endif /* PIMPLE_COMPAT_H_ */ |
||||
@ -0,0 +1,45 @@ |
|||||
|
--TEST-- |
||||
|
Test for read_dim/write_dim handlers |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
$p = new Pimple\Container(); |
||||
|
$p[42] = 'foo'; |
||||
|
$p['foo'] = 42; |
||||
|
|
||||
|
echo $p[42]; |
||||
|
echo "\n"; |
||||
|
echo $p['foo']; |
||||
|
echo "\n"; |
||||
|
try { |
||||
|
var_dump($p['nonexistant']); |
||||
|
echo "Exception excpected"; |
||||
|
} catch (InvalidArgumentException $e) { } |
||||
|
|
||||
|
$p[54.2] = 'foo2'; |
||||
|
echo $p[54]; |
||||
|
echo "\n"; |
||||
|
$p[242.99] = 'foo99'; |
||||
|
echo $p[242]; |
||||
|
|
||||
|
echo "\n"; |
||||
|
|
||||
|
$p[5] = 'bar'; |
||||
|
$p[5] = 'baz'; |
||||
|
echo $p[5]; |
||||
|
|
||||
|
echo "\n"; |
||||
|
|
||||
|
$p['str'] = 'str'; |
||||
|
$p['str'] = 'strstr'; |
||||
|
echo $p['str']; |
||||
|
?> |
||||
|
|
||||
|
--EXPECTF-- |
||||
|
foo |
||||
|
42 |
||||
|
foo2 |
||||
|
foo99 |
||||
|
baz |
||||
|
strstr |
||||
@ -0,0 +1,15 @@ |
|||||
|
--TEST-- |
||||
|
Test for constructor |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
$p = new Pimple\Container(); |
||||
|
var_dump($p[42]); |
||||
|
|
||||
|
$p = new Pimple\Container(array(42=>'foo')); |
||||
|
var_dump($p[42]); |
||||
|
?> |
||||
|
--EXPECT-- |
||||
|
NULL |
||||
|
string(3) "foo" |
||||
@ -0,0 +1,16 @@ |
|||||
|
--TEST-- |
||||
|
Test empty dimensions |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
$p = new Pimple\Container(); |
||||
|
$p[] = 42; |
||||
|
var_dump($p[0]); |
||||
|
$p[41] = 'foo'; |
||||
|
$p[] = 'bar'; |
||||
|
var_dump($p[42]); |
||||
|
?> |
||||
|
--EXPECT-- |
||||
|
int(42) |
||||
|
string(3) "bar" |
||||
@ -0,0 +1,30 @@ |
|||||
|
--TEST-- |
||||
|
Test has/unset dim handlers |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
$p = new Pimple\Container(); |
||||
|
$p[] = 42; |
||||
|
var_dump($p[0]); |
||||
|
unset($p[0]); |
||||
|
var_dump($p[0]); |
||||
|
$p['foo'] = 'bar'; |
||||
|
var_dump(isset($p['foo'])); |
||||
|
unset($p['foo']); |
||||
|
try { |
||||
|
var_dump($p['foo']); |
||||
|
echo "Excpected exception"; |
||||
|
} catch (InvalidArgumentException $e) { } |
||||
|
var_dump(isset($p['bar'])); |
||||
|
$p['bar'] = NULL; |
||||
|
var_dump(isset($p['bar'])); |
||||
|
var_dump(empty($p['bar'])); |
||||
|
?> |
||||
|
--EXPECT-- |
||||
|
int(42) |
||||
|
NULL |
||||
|
bool(true) |
||||
|
bool(false) |
||||
|
bool(true) |
||||
|
bool(true) |
||||
@ -0,0 +1,27 @@ |
|||||
|
--TEST-- |
||||
|
Test simple class inheritance |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
class MyPimple extends Pimple\Container |
||||
|
{ |
||||
|
public $someAttr = 'fooAttr'; |
||||
|
|
||||
|
public function offsetget($o) |
||||
|
{ |
||||
|
var_dump("hit"); |
||||
|
return parent::offsetget($o); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$p = new MyPimple; |
||||
|
$p[42] = 'foo'; |
||||
|
echo $p[42]; |
||||
|
echo "\n"; |
||||
|
echo $p->someAttr; |
||||
|
?> |
||||
|
--EXPECT-- |
||||
|
string(3) "hit" |
||||
|
foo |
||||
|
fooAttr |
||||
@ -0,0 +1,51 @@ |
|||||
|
--TEST-- |
||||
|
Test complex class inheritance |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
class MyPimple extends Pimple\Container |
||||
|
{ |
||||
|
public function offsetget($o) |
||||
|
{ |
||||
|
var_dump("hit offsetget in " . __CLASS__); |
||||
|
return parent::offsetget($o); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class TestPimple extends MyPimple |
||||
|
{ |
||||
|
public function __construct($values) |
||||
|
{ |
||||
|
array_shift($values); |
||||
|
parent::__construct($values); |
||||
|
} |
||||
|
|
||||
|
public function offsetget($o) |
||||
|
{ |
||||
|
var_dump('hit offsetget in ' . __CLASS__); |
||||
|
return parent::offsetget($o); |
||||
|
} |
||||
|
|
||||
|
public function offsetset($o, $v) |
||||
|
{ |
||||
|
var_dump('hit offsetset'); |
||||
|
return parent::offsetset($o, $v); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$defaultValues = array('foo' => 'bar', 88 => 'baz'); |
||||
|
|
||||
|
$p = new TestPimple($defaultValues); |
||||
|
$p[42] = 'foo'; |
||||
|
var_dump($p[42]); |
||||
|
var_dump($p[0]); |
||||
|
?> |
||||
|
--EXPECT-- |
||||
|
string(13) "hit offsetset" |
||||
|
string(27) "hit offsetget in TestPimple" |
||||
|
string(25) "hit offsetget in MyPimple" |
||||
|
string(3) "foo" |
||||
|
string(27) "hit offsetget in TestPimple" |
||||
|
string(25) "hit offsetget in MyPimple" |
||||
|
string(3) "baz" |
||||
@ -0,0 +1,22 @@ |
|||||
|
--TEST-- |
||||
|
Test for read_dim/write_dim handlers |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
$p = new Pimple\Container(); |
||||
|
$p[42] = 'foo'; |
||||
|
$p['foo'] = 42; |
||||
|
|
||||
|
echo $p[42]; |
||||
|
echo "\n"; |
||||
|
echo $p['foo']; |
||||
|
echo "\n"; |
||||
|
try { |
||||
|
var_dump($p['nonexistant']); |
||||
|
echo "Exception excpected"; |
||||
|
} catch (InvalidArgumentException $e) { } |
||||
|
?> |
||||
|
--EXPECTF-- |
||||
|
foo |
||||
|
42 |
||||
@ -0,0 +1,29 @@ |
|||||
|
--TEST-- |
||||
|
Test frozen services |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
$p = new Pimple\Container(); |
||||
|
$p[42] = 'foo'; |
||||
|
$p[42] = 'bar'; |
||||
|
|
||||
|
$p['foo'] = function () { }; |
||||
|
$p['foo'] = function () { }; |
||||
|
|
||||
|
$a = $p['foo']; |
||||
|
|
||||
|
try { |
||||
|
$p['foo'] = function () { }; |
||||
|
echo "Exception excpected"; |
||||
|
} catch (RuntimeException $e) { } |
||||
|
|
||||
|
$p[42] = function() { }; |
||||
|
$a = $p[42]; |
||||
|
|
||||
|
try { |
||||
|
$p[42] = function () { }; |
||||
|
echo "Exception excpected"; |
||||
|
} catch (RuntimeException $e) { } |
||||
|
?> |
||||
|
--EXPECTF-- |
||||
@ -0,0 +1,13 @@ |
|||||
|
--TEST-- |
||||
|
Test service is called as callback, and only once |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
$p = new Pimple\Container(); |
||||
|
$p['foo'] = function($arg) use ($p) { var_dump($p === $arg); }; |
||||
|
$a = $p['foo']; |
||||
|
$b = $p['foo']; /* should return not calling the callback */ |
||||
|
?> |
||||
|
--EXPECTF-- |
||||
|
bool(true) |
||||
@ -0,0 +1,45 @@ |
|||||
|
--TEST-- |
||||
|
Test service is called as callback for every callback type |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
function callme() |
||||
|
{ |
||||
|
return 'called'; |
||||
|
} |
||||
|
|
||||
|
$a = function() { return 'called'; }; |
||||
|
|
||||
|
class Foo |
||||
|
{ |
||||
|
public static function bar() |
||||
|
{ |
||||
|
return 'called'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
$p['foo'] = 'callme'; |
||||
|
echo $p['foo'] . "\n"; |
||||
|
|
||||
|
$p['bar'] = $a; |
||||
|
echo $p['bar'] . "\n"; |
||||
|
|
||||
|
$p['baz'] = "Foo::bar"; |
||||
|
echo $p['baz'] . "\n"; |
||||
|
|
||||
|
$p['foobar'] = array('Foo', 'bar'); |
||||
|
var_dump($p['foobar']); |
||||
|
|
||||
|
?> |
||||
|
--EXPECTF-- |
||||
|
callme |
||||
|
called |
||||
|
Foo::bar |
||||
|
array(2) { |
||||
|
[0]=> |
||||
|
string(3) "Foo" |
||||
|
[1]=> |
||||
|
string(3) "bar" |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
--TEST-- |
||||
|
Test service callback throwing an exception |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
class CallBackException extends RuntimeException { } |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
$p['foo'] = function () { throw new CallBackException; }; |
||||
|
try { |
||||
|
echo $p['foo'] . "\n"; |
||||
|
echo "should not come here"; |
||||
|
} catch (CallBackException $e) { |
||||
|
echo "all right!"; |
||||
|
} |
||||
|
?> |
||||
|
--EXPECTF-- |
||||
|
all right! |
||||
@ -0,0 +1,28 @@ |
|||||
|
--TEST-- |
||||
|
Test service factory |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
|
||||
|
$p->factory($f = function() { var_dump('called-1'); return 'ret-1';}); |
||||
|
|
||||
|
$p[] = $f; |
||||
|
|
||||
|
$p[] = function () { var_dump('called-2'); return 'ret-2'; }; |
||||
|
|
||||
|
var_dump($p[0]); |
||||
|
var_dump($p[0]); |
||||
|
var_dump($p[1]); |
||||
|
var_dump($p[1]); |
||||
|
?> |
||||
|
--EXPECTF-- |
||||
|
string(8) "called-1" |
||||
|
string(5) "ret-1" |
||||
|
string(8) "called-1" |
||||
|
string(5) "ret-1" |
||||
|
string(8) "called-2" |
||||
|
string(5) "ret-2" |
||||
|
string(5) "ret-2" |
||||
@ -0,0 +1,33 @@ |
|||||
|
--TEST-- |
||||
|
Test keys() |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
|
||||
|
var_dump($p->keys()); |
||||
|
|
||||
|
$p['foo'] = 'bar'; |
||||
|
$p[] = 'foo'; |
||||
|
|
||||
|
var_dump($p->keys()); |
||||
|
|
||||
|
unset($p['foo']); |
||||
|
|
||||
|
var_dump($p->keys()); |
||||
|
?> |
||||
|
--EXPECTF-- |
||||
|
array(0) { |
||||
|
} |
||||
|
array(2) { |
||||
|
[0]=> |
||||
|
string(3) "foo" |
||||
|
[1]=> |
||||
|
int(0) |
||||
|
} |
||||
|
array(1) { |
||||
|
[0]=> |
||||
|
int(0) |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
--TEST-- |
||||
|
Test raw() |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
$f = function () { var_dump('called-2'); return 'ret-2'; }; |
||||
|
|
||||
|
$p['foo'] = $f; |
||||
|
$p[42] = $f; |
||||
|
|
||||
|
var_dump($p['foo']); |
||||
|
var_dump($p->raw('foo')); |
||||
|
var_dump($p[42]); |
||||
|
|
||||
|
unset($p['foo']); |
||||
|
|
||||
|
try { |
||||
|
$p->raw('foo'); |
||||
|
echo "expected exception"; |
||||
|
} catch (InvalidArgumentException $e) { } |
||||
|
--EXPECTF-- |
||||
|
string(8) "called-2" |
||||
|
string(5) "ret-2" |
||||
|
object(Closure)#%i (0) { |
||||
|
} |
||||
|
string(8) "called-2" |
||||
|
string(5) "ret-2" |
||||
@ -0,0 +1,17 @@ |
|||||
|
--TEST-- |
||||
|
Test protect() |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
$f = function () { return 'foo'; }; |
||||
|
$p['foo'] = $f; |
||||
|
|
||||
|
$p->protect($f); |
||||
|
|
||||
|
var_dump($p['foo']); |
||||
|
--EXPECTF-- |
||||
|
object(Closure)#%i (0) { |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
--TEST-- |
||||
|
Test extend() |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
/* |
||||
|
This is part of Pimple::extend() code : |
||||
|
|
||||
|
$extended = function ($c) use ($callable, $factory) { |
||||
|
return $callable($factory($c), $c); |
||||
|
}; |
||||
|
*/ |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
$p[12] = function ($v) { var_dump($v); return 'foo';}; /* $factory in code above */ |
||||
|
|
||||
|
$c = $p->extend(12, function ($w) { var_dump($w); return 'bar'; }); /* $callable in code above */ |
||||
|
|
||||
|
var_dump($c('param')); |
||||
|
--EXPECTF-- |
||||
|
string(5) "param" |
||||
|
string(3) "foo" |
||||
|
string(3) "bar" |
||||
@ -0,0 +1,17 @@ |
|||||
|
--TEST-- |
||||
|
Test extend() with exception in service extension |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
$p[12] = function ($v) { return 'foo';}; |
||||
|
|
||||
|
$c = $p->extend(12, function ($w) { throw new BadMethodCallException; }); |
||||
|
|
||||
|
try { |
||||
|
$p[12]; |
||||
|
echo "Exception expected"; |
||||
|
} catch (BadMethodCallException $e) { } |
||||
|
--EXPECTF-- |
||||
@ -0,0 +1,17 @@ |
|||||
|
--TEST-- |
||||
|
Test extend() with exception in service factory |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
$p[12] = function ($v) { throw new BadMethodCallException; }; |
||||
|
|
||||
|
$c = $p->extend(12, function ($w) { return 'foobar'; }); |
||||
|
|
||||
|
try { |
||||
|
$p[12]; |
||||
|
echo "Exception expected"; |
||||
|
} catch (BadMethodCallException $e) { } |
||||
|
--EXPECTF-- |
||||
@ -0,0 +1,23 @@ |
|||||
|
--TEST-- |
||||
|
Test register() |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
|
||||
|
class Foo implements Pimple\ServiceProviderInterface |
||||
|
{ |
||||
|
public function register(Pimple\Container $p) |
||||
|
{ |
||||
|
var_dump($p); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
$p->register(new Foo, array(42 => 'bar')); |
||||
|
|
||||
|
var_dump($p[42]); |
||||
|
--EXPECTF-- |
||||
|
object(Pimple\Container)#1 (0) { |
||||
|
} |
||||
|
string(3) "bar" |
||||
@ -0,0 +1,18 @@ |
|||||
|
--TEST-- |
||||
|
Test register() returns static and is a fluent interface |
||||
|
--SKIPIF-- |
||||
|
<?php if (!extension_loaded("pimple")) print "skip"; ?> |
||||
|
--FILE-- |
||||
|
<?php |
||||
|
|
||||
|
class Foo implements Pimple\ServiceProviderInterface |
||||
|
{ |
||||
|
public function register(Pimple\Container $p) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$p = new Pimple\Container(); |
||||
|
var_dump($p === $p->register(new Foo)); |
||||
|
--EXPECTF-- |
||||
|
bool(true) |
||||
@ -0,0 +1,51 @@ |
|||||
|
<?php |
||||
|
|
||||
|
if (!class_exists('Pimple\Container')) { |
||||
|
require_once __DIR__ . '/../../../src/Pimple/Container.php'; |
||||
|
} else { |
||||
|
echo "pimple-c extension detected, using...\n\n"; |
||||
|
} |
||||
|
|
||||
|
$time = microtime(true); |
||||
|
|
||||
|
function foo() { } |
||||
|
$factory = function () { }; |
||||
|
|
||||
|
for ($i=0; $i<10000; $i++) { |
||||
|
|
||||
|
$p = new Pimple\Container; |
||||
|
|
||||
|
$p['foo'] = 'bar'; |
||||
|
|
||||
|
if (!isset($p[3])) { |
||||
|
$p[3] = $p['foo']; |
||||
|
$p[] = 'bar'; |
||||
|
} |
||||
|
|
||||
|
$p[2] = 42; |
||||
|
|
||||
|
if (isset($p[2])) { |
||||
|
unset($p[2]); |
||||
|
} |
||||
|
|
||||
|
$p[42] = $p['foo']; |
||||
|
|
||||
|
$p['cb'] = function($arg) { }; |
||||
|
|
||||
|
$p[] = $p['cb']; |
||||
|
|
||||
|
echo $p['cb']; |
||||
|
echo $p['cb']; |
||||
|
echo $p['cb']; |
||||
|
|
||||
|
//$p->factory($factory); |
||||
|
|
||||
|
$p['factory'] = $factory; |
||||
|
|
||||
|
echo $p['factory']; |
||||
|
echo $p['factory']; |
||||
|
echo $p['factory']; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
echo microtime(true) - $time; |
||||
@ -0,0 +1,25 @@ |
|||||
|
<?php |
||||
|
|
||||
|
if (!class_exists('Pimple\Container')) { |
||||
|
require_once __DIR__ . '/../../../src/Pimple/Container.php'; |
||||
|
} else { |
||||
|
echo "pimple-c extension detected, using...\n\n"; |
||||
|
} |
||||
|
|
||||
|
$time = microtime(true); |
||||
|
|
||||
|
|
||||
|
$service = function ($arg) { return "I'm a service"; }; |
||||
|
|
||||
|
for ($i=0; $i<10000; $i++) { |
||||
|
|
||||
|
$p = new Pimple\Container; |
||||
|
$p['my_service'] = $service; |
||||
|
|
||||
|
$a = $p['my_service']; |
||||
|
$b = $p['my_service']; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
echo microtime(true) - $time; |
||||
|
?> |
||||
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
|
||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd" |
||||
|
backupGlobals="false" |
||||
|
colors="true" |
||||
|
bootstrap="vendor/autoload.php" |
||||
|
> |
||||
|
<testsuites> |
||||
|
<testsuite name="Pimple Test Suite"> |
||||
|
<directory>./src/Pimple/Tests</directory> |
||||
|
</testsuite> |
||||
|
</testsuites> |
||||
|
</phpunit> |
||||
@ -0,0 +1,298 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple; |
||||
|
|
||||
|
use Pimple\Exception\ExpectedInvokableException; |
||||
|
use Pimple\Exception\FrozenServiceException; |
||||
|
use Pimple\Exception\InvalidServiceIdentifierException; |
||||
|
use Pimple\Exception\UnknownIdentifierException; |
||||
|
|
||||
|
/** |
||||
|
* Container main class. |
||||
|
* |
||||
|
* @author Fabien Potencier |
||||
|
*/ |
||||
|
class Container implements \ArrayAccess |
||||
|
{ |
||||
|
private $values = array(); |
||||
|
private $factories; |
||||
|
private $protected; |
||||
|
private $frozen = array(); |
||||
|
private $raw = array(); |
||||
|
private $keys = array(); |
||||
|
|
||||
|
/** |
||||
|
* Instantiates the container. |
||||
|
* |
||||
|
* Objects and parameters can be passed as argument to the constructor. |
||||
|
* |
||||
|
* @param array $values The parameters or objects |
||||
|
*/ |
||||
|
public function __construct(array $values = array()) |
||||
|
{ |
||||
|
$this->factories = new \SplObjectStorage(); |
||||
|
$this->protected = new \SplObjectStorage(); |
||||
|
|
||||
|
foreach ($values as $key => $value) { |
||||
|
$this->offsetSet($key, $value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sets a parameter or an object. |
||||
|
* |
||||
|
* Objects must be defined as Closures. |
||||
|
* |
||||
|
* Allowing any PHP callable leads to difficult to debug problems |
||||
|
* as function names (strings) are callable (creating a function with |
||||
|
* the same name as an existing parameter would break your container). |
||||
|
* |
||||
|
* @param string $id The unique identifier for the parameter or object |
||||
|
* @param mixed $value The value of the parameter or a closure to define an object |
||||
|
* |
||||
|
* @throws FrozenServiceException Prevent override of a frozen service |
||||
|
*/ |
||||
|
public function offsetSet($id, $value) |
||||
|
{ |
||||
|
if (isset($this->frozen[$id])) { |
||||
|
throw new FrozenServiceException($id); |
||||
|
} |
||||
|
|
||||
|
$this->values[$id] = $value; |
||||
|
$this->keys[$id] = true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets a parameter or an object. |
||||
|
* |
||||
|
* @param string $id The unique identifier for the parameter or object |
||||
|
* |
||||
|
* @return mixed The value of the parameter or an object |
||||
|
* |
||||
|
* @throws UnknownIdentifierException If the identifier is not defined |
||||
|
*/ |
||||
|
public function offsetGet($id) |
||||
|
{ |
||||
|
if (!isset($this->keys[$id])) { |
||||
|
throw new UnknownIdentifierException($id); |
||||
|
} |
||||
|
|
||||
|
if ( |
||||
|
isset($this->raw[$id]) |
||||
|
|| !\is_object($this->values[$id]) |
||||
|
|| isset($this->protected[$this->values[$id]]) |
||||
|
|| !\method_exists($this->values[$id], '__invoke') |
||||
|
) { |
||||
|
return $this->values[$id]; |
||||
|
} |
||||
|
|
||||
|
if (isset($this->factories[$this->values[$id]])) { |
||||
|
return $this->values[$id]($this); |
||||
|
} |
||||
|
|
||||
|
$raw = $this->values[$id]; |
||||
|
$val = $this->values[$id] = $raw($this); |
||||
|
$this->raw[$id] = $raw; |
||||
|
|
||||
|
$this->frozen[$id] = true; |
||||
|
|
||||
|
return $val; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks if a parameter or an object is set. |
||||
|
* |
||||
|
* @param string $id The unique identifier for the parameter or object |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function offsetExists($id) |
||||
|
{ |
||||
|
return isset($this->keys[$id]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Unsets a parameter or an object. |
||||
|
* |
||||
|
* @param string $id The unique identifier for the parameter or object |
||||
|
*/ |
||||
|
public function offsetUnset($id) |
||||
|
{ |
||||
|
if (isset($this->keys[$id])) { |
||||
|
if (\is_object($this->values[$id])) { |
||||
|
unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); |
||||
|
} |
||||
|
|
||||
|
unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Marks a callable as being a factory service. |
||||
|
* |
||||
|
* @param callable $callable A service definition to be used as a factory |
||||
|
* |
||||
|
* @return callable The passed callable |
||||
|
* |
||||
|
* @throws ExpectedInvokableException Service definition has to be a closure or an invokable object |
||||
|
*/ |
||||
|
public function factory($callable) |
||||
|
{ |
||||
|
if (!\method_exists($callable, '__invoke')) { |
||||
|
throw new ExpectedInvokableException('Service definition is not a Closure or invokable object.'); |
||||
|
} |
||||
|
|
||||
|
$this->factories->attach($callable); |
||||
|
|
||||
|
return $callable; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Protects a callable from being interpreted as a service. |
||||
|
* |
||||
|
* This is useful when you want to store a callable as a parameter. |
||||
|
* |
||||
|
* @param callable $callable A callable to protect from being evaluated |
||||
|
* |
||||
|
* @return callable The passed callable |
||||
|
* |
||||
|
* @throws ExpectedInvokableException Service definition has to be a closure or an invokable object |
||||
|
*/ |
||||
|
public function protect($callable) |
||||
|
{ |
||||
|
if (!\method_exists($callable, '__invoke')) { |
||||
|
throw new ExpectedInvokableException('Callable is not a Closure or invokable object.'); |
||||
|
} |
||||
|
|
||||
|
$this->protected->attach($callable); |
||||
|
|
||||
|
return $callable; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets a parameter or the closure defining an object. |
||||
|
* |
||||
|
* @param string $id The unique identifier for the parameter or object |
||||
|
* |
||||
|
* @return mixed The value of the parameter or the closure defining an object |
||||
|
* |
||||
|
* @throws UnknownIdentifierException If the identifier is not defined |
||||
|
*/ |
||||
|
public function raw($id) |
||||
|
{ |
||||
|
if (!isset($this->keys[$id])) { |
||||
|
throw new UnknownIdentifierException($id); |
||||
|
} |
||||
|
|
||||
|
if (isset($this->raw[$id])) { |
||||
|
return $this->raw[$id]; |
||||
|
} |
||||
|
|
||||
|
return $this->values[$id]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Extends an object definition. |
||||
|
* |
||||
|
* Useful when you want to extend an existing object definition, |
||||
|
* without necessarily loading that object. |
||||
|
* |
||||
|
* @param string $id The unique identifier for the object |
||||
|
* @param callable $callable A service definition to extend the original |
||||
|
* |
||||
|
* @return callable The wrapped callable |
||||
|
* |
||||
|
* @throws UnknownIdentifierException If the identifier is not defined |
||||
|
* @throws FrozenServiceException If the service is frozen |
||||
|
* @throws InvalidServiceIdentifierException If the identifier belongs to a parameter |
||||
|
* @throws ExpectedInvokableException If the extension callable is not a closure or an invokable object |
||||
|
*/ |
||||
|
public function extend($id, $callable) |
||||
|
{ |
||||
|
if (!isset($this->keys[$id])) { |
||||
|
throw new UnknownIdentifierException($id); |
||||
|
} |
||||
|
|
||||
|
if (isset($this->frozen[$id])) { |
||||
|
throw new FrozenServiceException($id); |
||||
|
} |
||||
|
|
||||
|
if (!\is_object($this->values[$id]) || !\method_exists($this->values[$id], '__invoke')) { |
||||
|
throw new InvalidServiceIdentifierException($id); |
||||
|
} |
||||
|
|
||||
|
if (isset($this->protected[$this->values[$id]])) { |
||||
|
@\trigger_error(\sprintf('How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "%s" should be protected?', $id), \E_USER_DEPRECATED); |
||||
|
} |
||||
|
|
||||
|
if (!\is_object($callable) || !\method_exists($callable, '__invoke')) { |
||||
|
throw new ExpectedInvokableException('Extension service definition is not a Closure or invokable object.'); |
||||
|
} |
||||
|
|
||||
|
$factory = $this->values[$id]; |
||||
|
|
||||
|
$extended = function ($c) use ($callable, $factory) { |
||||
|
return $callable($factory($c), $c); |
||||
|
}; |
||||
|
|
||||
|
if (isset($this->factories[$factory])) { |
||||
|
$this->factories->detach($factory); |
||||
|
$this->factories->attach($extended); |
||||
|
} |
||||
|
|
||||
|
return $this[$id] = $extended; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns all defined value names. |
||||
|
* |
||||
|
* @return array An array of value names |
||||
|
*/ |
||||
|
public function keys() |
||||
|
{ |
||||
|
return \array_keys($this->values); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Registers a service provider. |
||||
|
* |
||||
|
* @param ServiceProviderInterface $provider A ServiceProviderInterface instance |
||||
|
* @param array $values An array of values that customizes the provider |
||||
|
* |
||||
|
* @return static |
||||
|
*/ |
||||
|
public function register(ServiceProviderInterface $provider, array $values = array()) |
||||
|
{ |
||||
|
$provider->register($this); |
||||
|
|
||||
|
foreach ($values as $key => $value) { |
||||
|
$this[$key] = $value; |
||||
|
} |
||||
|
|
||||
|
return $this; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Exception; |
||||
|
|
||||
|
use Psr\Container\ContainerExceptionInterface; |
||||
|
|
||||
|
/** |
||||
|
* A closure or invokable object was expected. |
||||
|
* |
||||
|
* @author Pascal Luna <skalpa@zetareticuli.org> |
||||
|
*/ |
||||
|
class ExpectedInvokableException extends \InvalidArgumentException implements ContainerExceptionInterface |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Exception; |
||||
|
|
||||
|
use Psr\Container\ContainerExceptionInterface; |
||||
|
|
||||
|
/** |
||||
|
* An attempt to modify a frozen service was made. |
||||
|
* |
||||
|
* @author Pascal Luna <skalpa@zetareticuli.org> |
||||
|
*/ |
||||
|
class FrozenServiceException extends \RuntimeException implements ContainerExceptionInterface |
||||
|
{ |
||||
|
/** |
||||
|
* @param string $id Identifier of the frozen service |
||||
|
*/ |
||||
|
public function __construct($id) |
||||
|
{ |
||||
|
parent::__construct(\sprintf('Cannot override frozen service "%s".', $id)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Exception; |
||||
|
|
||||
|
use Psr\Container\NotFoundExceptionInterface; |
||||
|
|
||||
|
/** |
||||
|
* An attempt to perform an operation that requires a service identifier was made. |
||||
|
* |
||||
|
* @author Pascal Luna <skalpa@zetareticuli.org> |
||||
|
*/ |
||||
|
class InvalidServiceIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface |
||||
|
{ |
||||
|
/** |
||||
|
* @param string $id The invalid identifier |
||||
|
*/ |
||||
|
public function __construct($id) |
||||
|
{ |
||||
|
parent::__construct(\sprintf('Identifier "%s" does not contain an object definition.', $id)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Exception; |
||||
|
|
||||
|
use Psr\Container\NotFoundExceptionInterface; |
||||
|
|
||||
|
/** |
||||
|
* The identifier of a valid service or parameter was expected. |
||||
|
* |
||||
|
* @author Pascal Luna <skalpa@zetareticuli.org> |
||||
|
*/ |
||||
|
class UnknownIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface |
||||
|
{ |
||||
|
/** |
||||
|
* @param string $id The unknown identifier |
||||
|
*/ |
||||
|
public function __construct($id) |
||||
|
{ |
||||
|
parent::__construct(\sprintf('Identifier "%s" is not defined.', $id)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009-2017 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Psr11; |
||||
|
|
||||
|
use Pimple\Container as PimpleContainer; |
||||
|
use Psr\Container\ContainerInterface; |
||||
|
|
||||
|
/** |
||||
|
* PSR-11 compliant wrapper. |
||||
|
* |
||||
|
* @author Pascal Luna <skalpa@zetareticuli.org> |
||||
|
*/ |
||||
|
final class Container implements ContainerInterface |
||||
|
{ |
||||
|
private $pimple; |
||||
|
|
||||
|
public function __construct(PimpleContainer $pimple) |
||||
|
{ |
||||
|
$this->pimple = $pimple; |
||||
|
} |
||||
|
|
||||
|
public function get($id) |
||||
|
{ |
||||
|
return $this->pimple[$id]; |
||||
|
} |
||||
|
|
||||
|
public function has($id) |
||||
|
{ |
||||
|
return isset($this->pimple[$id]); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Psr11; |
||||
|
|
||||
|
use Pimple\Container as PimpleContainer; |
||||
|
use Pimple\Exception\UnknownIdentifierException; |
||||
|
use Psr\Container\ContainerInterface; |
||||
|
|
||||
|
/** |
||||
|
* Pimple PSR-11 service locator. |
||||
|
* |
||||
|
* @author Pascal Luna <skalpa@zetareticuli.org> |
||||
|
*/ |
||||
|
class ServiceLocator implements ContainerInterface |
||||
|
{ |
||||
|
private $container; |
||||
|
private $aliases = array(); |
||||
|
|
||||
|
/** |
||||
|
* @param PimpleContainer $container The Container instance used to locate services |
||||
|
* @param array $ids Array of service ids that can be located. String keys can be used to define aliases |
||||
|
*/ |
||||
|
public function __construct(PimpleContainer $container, array $ids) |
||||
|
{ |
||||
|
$this->container = $container; |
||||
|
|
||||
|
foreach ($ids as $key => $id) { |
||||
|
$this->aliases[\is_int($key) ? $id : $key] = $id; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@inheritdoc} |
||||
|
*/ |
||||
|
public function get($id) |
||||
|
{ |
||||
|
if (!isset($this->aliases[$id])) { |
||||
|
throw new UnknownIdentifierException($id); |
||||
|
} |
||||
|
|
||||
|
return $this->container[$this->aliases[$id]]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@inheritdoc} |
||||
|
*/ |
||||
|
public function has($id) |
||||
|
{ |
||||
|
return isset($this->aliases[$id]) && isset($this->container[$this->aliases[$id]]); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple; |
||||
|
|
||||
|
/** |
||||
|
* Lazy service iterator. |
||||
|
* |
||||
|
* @author Pascal Luna <skalpa@zetareticuli.org> |
||||
|
*/ |
||||
|
final class ServiceIterator implements \Iterator |
||||
|
{ |
||||
|
private $container; |
||||
|
private $ids; |
||||
|
|
||||
|
public function __construct(Container $container, array $ids) |
||||
|
{ |
||||
|
$this->container = $container; |
||||
|
$this->ids = $ids; |
||||
|
} |
||||
|
|
||||
|
public function rewind() |
||||
|
{ |
||||
|
\reset($this->ids); |
||||
|
} |
||||
|
|
||||
|
public function current() |
||||
|
{ |
||||
|
return $this->container[\current($this->ids)]; |
||||
|
} |
||||
|
|
||||
|
public function key() |
||||
|
{ |
||||
|
return \current($this->ids); |
||||
|
} |
||||
|
|
||||
|
public function next() |
||||
|
{ |
||||
|
\next($this->ids); |
||||
|
} |
||||
|
|
||||
|
public function valid() |
||||
|
{ |
||||
|
return null !== \key($this->ids); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple; |
||||
|
|
||||
|
/** |
||||
|
* Pimple service provider interface. |
||||
|
* |
||||
|
* @author Fabien Potencier |
||||
|
* @author Dominik Zogg |
||||
|
*/ |
||||
|
interface ServiceProviderInterface |
||||
|
{ |
||||
|
/** |
||||
|
* Registers services on the given container. |
||||
|
* |
||||
|
* This method should only be used to configure services and parameters. |
||||
|
* It should not get services. |
||||
|
* |
||||
|
* @param Container $pimple A container instance |
||||
|
*/ |
||||
|
public function register(Container $pimple); |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests\Fixtures; |
||||
|
|
||||
|
class Invokable |
||||
|
{ |
||||
|
public function __invoke($value = null) |
||||
|
{ |
||||
|
$service = new Service(); |
||||
|
$service->value = $value; |
||||
|
|
||||
|
return $service; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests\Fixtures; |
||||
|
|
||||
|
class NonInvokable |
||||
|
{ |
||||
|
public function __call($a, $b) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests\Fixtures; |
||||
|
|
||||
|
use Pimple\Container; |
||||
|
use Pimple\ServiceProviderInterface; |
||||
|
|
||||
|
class PimpleServiceProvider implements ServiceProviderInterface |
||||
|
{ |
||||
|
/** |
||||
|
* Registers services on the given container. |
||||
|
* |
||||
|
* This method should only be used to configure services and parameters. |
||||
|
* It should not get services. |
||||
|
* |
||||
|
* @param Container $pimple An Container instance |
||||
|
*/ |
||||
|
public function register(Container $pimple) |
||||
|
{ |
||||
|
$pimple['param'] = 'value'; |
||||
|
|
||||
|
$pimple['service'] = function () { |
||||
|
return new Service(); |
||||
|
}; |
||||
|
|
||||
|
$pimple['factory'] = $pimple->factory(function () { |
||||
|
return new Service(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests\Fixtures; |
||||
|
|
||||
|
/** |
||||
|
* @author Igor Wiedler <igor@wiedler.ch> |
||||
|
*/ |
||||
|
class Service |
||||
|
{ |
||||
|
public $value; |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests; |
||||
|
|
||||
|
use Pimple\Container; |
||||
|
|
||||
|
/** |
||||
|
* @author Dominik Zogg <dominik.zogg@gmail.com> |
||||
|
*/ |
||||
|
class PimpleServiceProviderInterfaceTest extends \PHPUnit_Framework_TestCase |
||||
|
{ |
||||
|
public function testProvider() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
|
||||
|
$pimpleServiceProvider = new Fixtures\PimpleServiceProvider(); |
||||
|
$pimpleServiceProvider->register($pimple); |
||||
|
|
||||
|
$this->assertEquals('value', $pimple['param']); |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); |
||||
|
|
||||
|
$serviceOne = $pimple['factory']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); |
||||
|
|
||||
|
$serviceTwo = $pimple['factory']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); |
||||
|
|
||||
|
$this->assertNotSame($serviceOne, $serviceTwo); |
||||
|
} |
||||
|
|
||||
|
public function testProviderWithRegisterMethod() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
|
||||
|
$pimple->register(new Fixtures\PimpleServiceProvider(), array( |
||||
|
'anotherParameter' => 'anotherValue', |
||||
|
)); |
||||
|
|
||||
|
$this->assertEquals('value', $pimple['param']); |
||||
|
$this->assertEquals('anotherValue', $pimple['anotherParameter']); |
||||
|
|
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); |
||||
|
|
||||
|
$serviceOne = $pimple['factory']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); |
||||
|
|
||||
|
$serviceTwo = $pimple['factory']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); |
||||
|
|
||||
|
$this->assertNotSame($serviceOne, $serviceTwo); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,589 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests; |
||||
|
|
||||
|
use Pimple\Container; |
||||
|
|
||||
|
/** |
||||
|
* @author Igor Wiedler <igor@wiedler.ch> |
||||
|
*/ |
||||
|
class PimpleTest extends \PHPUnit_Framework_TestCase |
||||
|
{ |
||||
|
public function testWithString() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['param'] = 'value'; |
||||
|
|
||||
|
$this->assertEquals('value', $pimple['param']); |
||||
|
} |
||||
|
|
||||
|
public function testWithClosure() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
|
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); |
||||
|
} |
||||
|
|
||||
|
public function testServicesShouldBeDifferent() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = $pimple->factory(function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}); |
||||
|
|
||||
|
$serviceOne = $pimple['service']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); |
||||
|
|
||||
|
$serviceTwo = $pimple['service']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); |
||||
|
|
||||
|
$this->assertNotSame($serviceOne, $serviceTwo); |
||||
|
} |
||||
|
|
||||
|
public function testShouldPassContainerAsParameter() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$pimple['container'] = function ($container) { |
||||
|
return $container; |
||||
|
}; |
||||
|
|
||||
|
$this->assertNotSame($pimple, $pimple['service']); |
||||
|
$this->assertSame($pimple, $pimple['container']); |
||||
|
} |
||||
|
|
||||
|
public function testIsset() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['param'] = 'value'; |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
|
||||
|
$pimple['null'] = null; |
||||
|
|
||||
|
$this->assertTrue(isset($pimple['param'])); |
||||
|
$this->assertTrue(isset($pimple['service'])); |
||||
|
$this->assertTrue(isset($pimple['null'])); |
||||
|
$this->assertFalse(isset($pimple['non_existent'])); |
||||
|
} |
||||
|
|
||||
|
public function testConstructorInjection() |
||||
|
{ |
||||
|
$params = array('param' => 'value'); |
||||
|
$pimple = new Container($params); |
||||
|
|
||||
|
$this->assertSame($params['param'], $pimple['param']); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\UnknownIdentifierException |
||||
|
* @expectedExceptionMessage Identifier "foo" is not defined. |
||||
|
*/ |
||||
|
public function testOffsetGetValidatesKeyIsPresent() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
echo $pimple['foo']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @expectedException \InvalidArgumentException |
||||
|
* @expectedExceptionMessage Identifier "foo" is not defined. |
||||
|
*/ |
||||
|
public function testLegacyOffsetGetValidatesKeyIsPresent() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
echo $pimple['foo']; |
||||
|
} |
||||
|
|
||||
|
public function testOffsetGetHonorsNullValues() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = null; |
||||
|
$this->assertNull($pimple['foo']); |
||||
|
} |
||||
|
|
||||
|
public function testUnset() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['param'] = 'value'; |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
|
||||
|
unset($pimple['param'], $pimple['service']); |
||||
|
$this->assertFalse(isset($pimple['param'])); |
||||
|
$this->assertFalse(isset($pimple['service'])); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @dataProvider serviceDefinitionProvider |
||||
|
*/ |
||||
|
public function testShare($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['shared_service'] = $service; |
||||
|
|
||||
|
$serviceOne = $pimple['shared_service']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); |
||||
|
|
||||
|
$serviceTwo = $pimple['shared_service']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); |
||||
|
|
||||
|
$this->assertSame($serviceOne, $serviceTwo); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @dataProvider serviceDefinitionProvider |
||||
|
*/ |
||||
|
public function testProtect($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['protected'] = $pimple->protect($service); |
||||
|
|
||||
|
$this->assertSame($service, $pimple['protected']); |
||||
|
} |
||||
|
|
||||
|
public function testGlobalFunctionNameAsParameterValue() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['global_function'] = 'strlen'; |
||||
|
$this->assertSame('strlen', $pimple['global_function']); |
||||
|
} |
||||
|
|
||||
|
public function testRaw() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = $definition = $pimple->factory(function () { return 'foo'; }); |
||||
|
$this->assertSame($definition, $pimple->raw('service')); |
||||
|
} |
||||
|
|
||||
|
public function testRawHonorsNullValues() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = null; |
||||
|
$this->assertNull($pimple->raw('foo')); |
||||
|
} |
||||
|
|
||||
|
public function testFluentRegister() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$this->assertSame($pimple, $pimple->register($this->getMockBuilder('Pimple\ServiceProviderInterface')->getMock())); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\UnknownIdentifierException |
||||
|
* @expectedExceptionMessage Identifier "foo" is not defined. |
||||
|
*/ |
||||
|
public function testRawValidatesKeyIsPresent() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple->raw('foo'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @expectedException \InvalidArgumentException |
||||
|
* @expectedExceptionMessage Identifier "foo" is not defined. |
||||
|
*/ |
||||
|
public function testLegacyRawValidatesKeyIsPresent() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple->raw('foo'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @dataProvider serviceDefinitionProvider |
||||
|
*/ |
||||
|
public function testExtend($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['shared_service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$pimple['factory_service'] = $pimple->factory(function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}); |
||||
|
|
||||
|
$pimple->extend('shared_service', $service); |
||||
|
$serviceOne = $pimple['shared_service']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); |
||||
|
$serviceTwo = $pimple['shared_service']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); |
||||
|
$this->assertSame($serviceOne, $serviceTwo); |
||||
|
$this->assertSame($serviceOne->value, $serviceTwo->value); |
||||
|
|
||||
|
$pimple->extend('factory_service', $service); |
||||
|
$serviceOne = $pimple['factory_service']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); |
||||
|
$serviceTwo = $pimple['factory_service']; |
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); |
||||
|
$this->assertNotSame($serviceOne, $serviceTwo); |
||||
|
$this->assertNotSame($serviceOne->value, $serviceTwo->value); |
||||
|
} |
||||
|
|
||||
|
public function testExtendDoesNotLeakWithFactories() |
||||
|
{ |
||||
|
if (extension_loaded('pimple')) { |
||||
|
$this->markTestSkipped('Pimple extension does not support this test'); |
||||
|
} |
||||
|
$pimple = new Container(); |
||||
|
|
||||
|
$pimple['foo'] = $pimple->factory(function () { return; }); |
||||
|
$pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { return; }); |
||||
|
unset($pimple['foo']); |
||||
|
|
||||
|
$p = new \ReflectionProperty($pimple, 'values'); |
||||
|
$p->setAccessible(true); |
||||
|
$this->assertEmpty($p->getValue($pimple)); |
||||
|
|
||||
|
$p = new \ReflectionProperty($pimple, 'factories'); |
||||
|
$p->setAccessible(true); |
||||
|
$this->assertCount(0, $p->getValue($pimple)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\UnknownIdentifierException |
||||
|
* @expectedExceptionMessage Identifier "foo" is not defined. |
||||
|
*/ |
||||
|
public function testExtendValidatesKeyIsPresent() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple->extend('foo', function () {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @expectedException \InvalidArgumentException |
||||
|
* @expectedExceptionMessage Identifier "foo" is not defined. |
||||
|
*/ |
||||
|
public function testLegacyExtendValidatesKeyIsPresent() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple->extend('foo', function () {}); |
||||
|
} |
||||
|
|
||||
|
public function testKeys() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = 123; |
||||
|
$pimple['bar'] = 123; |
||||
|
|
||||
|
$this->assertEquals(array('foo', 'bar'), $pimple->keys()); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function settingAnInvokableObjectShouldTreatItAsFactory() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['invokable'] = new Fixtures\Invokable(); |
||||
|
|
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']); |
||||
|
} |
||||
|
|
||||
|
/** @test */ |
||||
|
public function settingNonInvokableObjectShouldTreatItAsParameter() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['non_invokable'] = new Fixtures\NonInvokable(); |
||||
|
|
||||
|
$this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @dataProvider badServiceDefinitionProvider |
||||
|
* @expectedException \Pimple\Exception\ExpectedInvokableException |
||||
|
* @expectedExceptionMessage Service definition is not a Closure or invokable object. |
||||
|
*/ |
||||
|
public function testFactoryFailsForInvalidServiceDefinitions($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple->factory($service); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @dataProvider badServiceDefinitionProvider |
||||
|
* @expectedException \InvalidArgumentException |
||||
|
* @expectedExceptionMessage Service definition is not a Closure or invokable object. |
||||
|
*/ |
||||
|
public function testLegacyFactoryFailsForInvalidServiceDefinitions($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple->factory($service); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @dataProvider badServiceDefinitionProvider |
||||
|
* @expectedException \Pimple\Exception\ExpectedInvokableException |
||||
|
* @expectedExceptionMessage Callable is not a Closure or invokable object. |
||||
|
*/ |
||||
|
public function testProtectFailsForInvalidServiceDefinitions($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple->protect($service); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @dataProvider badServiceDefinitionProvider |
||||
|
* @expectedException \InvalidArgumentException |
||||
|
* @expectedExceptionMessage Callable is not a Closure or invokable object. |
||||
|
*/ |
||||
|
public function testLegacyProtectFailsForInvalidServiceDefinitions($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple->protect($service); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @dataProvider badServiceDefinitionProvider |
||||
|
* @expectedException \Pimple\Exception\InvalidServiceIdentifierException |
||||
|
* @expectedExceptionMessage Identifier "foo" does not contain an object definition. |
||||
|
*/ |
||||
|
public function testExtendFailsForKeysNotContainingServiceDefinitions($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = $service; |
||||
|
$pimple->extend('foo', function () {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @dataProvider badServiceDefinitionProvider |
||||
|
* @expectedException \InvalidArgumentException |
||||
|
* @expectedExceptionMessage Identifier "foo" does not contain an object definition. |
||||
|
*/ |
||||
|
public function testLegacyExtendFailsForKeysNotContainingServiceDefinitions($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = $service; |
||||
|
$pimple->extend('foo', function () {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @expectedDeprecation How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "foo" should be protected? |
||||
|
*/ |
||||
|
public function testExtendingProtectedClosureDeprecation() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = $pimple->protect(function () { |
||||
|
return 'bar'; |
||||
|
}); |
||||
|
|
||||
|
$pimple->extend('foo', function ($value) { |
||||
|
return $value.'-baz'; |
||||
|
}); |
||||
|
|
||||
|
$this->assertSame('bar-baz', $pimple['foo']); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @dataProvider badServiceDefinitionProvider |
||||
|
* @expectedException \Pimple\Exception\ExpectedInvokableException |
||||
|
* @expectedExceptionMessage Extension service definition is not a Closure or invokable object. |
||||
|
*/ |
||||
|
public function testExtendFailsForInvalidServiceDefinitions($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () {}; |
||||
|
$pimple->extend('foo', $service); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @dataProvider badServiceDefinitionProvider |
||||
|
* @expectedException \InvalidArgumentException |
||||
|
* @expectedExceptionMessage Extension service definition is not a Closure or invokable object. |
||||
|
*/ |
||||
|
public function testLegacyExtendFailsForInvalidServiceDefinitions($service) |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () {}; |
||||
|
$pimple->extend('foo', $service); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\FrozenServiceException |
||||
|
* @expectedExceptionMessage Cannot override frozen service "foo". |
||||
|
*/ |
||||
|
public function testExtendFailsIfFrozenServiceIsNonInvokable() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () { |
||||
|
return new Fixtures\NonInvokable(); |
||||
|
}; |
||||
|
$foo = $pimple['foo']; |
||||
|
|
||||
|
$pimple->extend('foo', function () {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\FrozenServiceException |
||||
|
* @expectedExceptionMessage Cannot override frozen service "foo". |
||||
|
*/ |
||||
|
public function testExtendFailsIfFrozenServiceIsInvokable() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () { |
||||
|
return new Fixtures\Invokable(); |
||||
|
}; |
||||
|
$foo = $pimple['foo']; |
||||
|
|
||||
|
$pimple->extend('foo', function () {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Provider for invalid service definitions. |
||||
|
*/ |
||||
|
public function badServiceDefinitionProvider() |
||||
|
{ |
||||
|
return array( |
||||
|
array(123), |
||||
|
array(new Fixtures\NonInvokable()), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Provider for service definitions. |
||||
|
*/ |
||||
|
public function serviceDefinitionProvider() |
||||
|
{ |
||||
|
return array( |
||||
|
array(function ($value) { |
||||
|
$service = new Fixtures\Service(); |
||||
|
$service->value = $value; |
||||
|
|
||||
|
return $service; |
||||
|
}), |
||||
|
array(new Fixtures\Invokable()), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public function testDefiningNewServiceAfterFreeze() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () { |
||||
|
return 'foo'; |
||||
|
}; |
||||
|
$foo = $pimple['foo']; |
||||
|
|
||||
|
$pimple['bar'] = function () { |
||||
|
return 'bar'; |
||||
|
}; |
||||
|
$this->assertSame('bar', $pimple['bar']); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\FrozenServiceException |
||||
|
* @expectedExceptionMessage Cannot override frozen service "foo". |
||||
|
*/ |
||||
|
public function testOverridingServiceAfterFreeze() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () { |
||||
|
return 'foo'; |
||||
|
}; |
||||
|
$foo = $pimple['foo']; |
||||
|
|
||||
|
$pimple['foo'] = function () { |
||||
|
return 'bar'; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @group legacy |
||||
|
* @expectedException \RuntimeException |
||||
|
* @expectedExceptionMessage Cannot override frozen service "foo". |
||||
|
*/ |
||||
|
public function testLegacyOverridingServiceAfterFreeze() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () { |
||||
|
return 'foo'; |
||||
|
}; |
||||
|
$foo = $pimple['foo']; |
||||
|
|
||||
|
$pimple['foo'] = function () { |
||||
|
return 'bar'; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public function testRemovingServiceAfterFreeze() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () { |
||||
|
return 'foo'; |
||||
|
}; |
||||
|
$foo = $pimple['foo']; |
||||
|
|
||||
|
unset($pimple['foo']); |
||||
|
$pimple['foo'] = function () { |
||||
|
return 'bar'; |
||||
|
}; |
||||
|
$this->assertSame('bar', $pimple['foo']); |
||||
|
} |
||||
|
|
||||
|
public function testExtendingService() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () { |
||||
|
return 'foo'; |
||||
|
}; |
||||
|
$pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { |
||||
|
return "$foo.bar"; |
||||
|
}); |
||||
|
$pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { |
||||
|
return "$foo.baz"; |
||||
|
}); |
||||
|
$this->assertSame('foo.bar.baz', $pimple['foo']); |
||||
|
} |
||||
|
|
||||
|
public function testExtendingServiceAfterOtherServiceFreeze() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['foo'] = function () { |
||||
|
return 'foo'; |
||||
|
}; |
||||
|
$pimple['bar'] = function () { |
||||
|
return 'bar'; |
||||
|
}; |
||||
|
$foo = $pimple['foo']; |
||||
|
|
||||
|
$pimple['bar'] = $pimple->extend('bar', function ($bar, $app) { |
||||
|
return "$bar.baz"; |
||||
|
}); |
||||
|
$this->assertSame('bar.baz', $pimple['bar']); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009-2017 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests\Psr11; |
||||
|
|
||||
|
use PHPUnit\Framework\TestCase; |
||||
|
use Pimple\Container; |
||||
|
use Pimple\Psr11\Container as PsrContainer; |
||||
|
use Pimple\Tests\Fixtures\Service; |
||||
|
|
||||
|
class ContainerTest extends TestCase |
||||
|
{ |
||||
|
public function testGetReturnsExistingService() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Service(); |
||||
|
}; |
||||
|
$psr = new PsrContainer($pimple); |
||||
|
|
||||
|
$this->assertSame($pimple['service'], $psr->get('service')); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Psr\Container\NotFoundExceptionInterface |
||||
|
* @expectedExceptionMessage Identifier "service" is not defined. |
||||
|
*/ |
||||
|
public function testGetThrowsExceptionIfServiceIsNotFound() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$psr = new PsrContainer($pimple); |
||||
|
|
||||
|
$psr->get('service'); |
||||
|
} |
||||
|
|
||||
|
public function testHasReturnsTrueIfServiceExists() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Service(); |
||||
|
}; |
||||
|
$psr = new PsrContainer($pimple); |
||||
|
|
||||
|
$this->assertTrue($psr->has('service')); |
||||
|
} |
||||
|
|
||||
|
public function testHasReturnsFalseIfServiceDoesNotExist() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$psr = new PsrContainer($pimple); |
||||
|
|
||||
|
$this->assertFalse($psr->has('service')); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,134 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests\Psr11; |
||||
|
|
||||
|
use PHPUnit\Framework\TestCase; |
||||
|
use Pimple\Container; |
||||
|
use Pimple\Psr11\ServiceLocator; |
||||
|
use Pimple\Tests\Fixtures; |
||||
|
|
||||
|
/** |
||||
|
* ServiceLocator test case. |
||||
|
* |
||||
|
* @author Pascal Luna <skalpa@zetareticuli.org> |
||||
|
*/ |
||||
|
class ServiceLocatorTest extends TestCase |
||||
|
{ |
||||
|
public function testCanAccessServices() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$locator = new ServiceLocator($pimple, array('service')); |
||||
|
|
||||
|
$this->assertSame($pimple['service'], $locator->get('service')); |
||||
|
} |
||||
|
|
||||
|
public function testCanAccessAliasedServices() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$locator = new ServiceLocator($pimple, array('alias' => 'service')); |
||||
|
|
||||
|
$this->assertSame($pimple['service'], $locator->get('alias')); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\UnknownIdentifierException |
||||
|
* @expectedExceptionMessage Identifier "service" is not defined. |
||||
|
*/ |
||||
|
public function testCannotAccessAliasedServiceUsingRealIdentifier() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$locator = new ServiceLocator($pimple, array('alias' => 'service')); |
||||
|
|
||||
|
$service = $locator->get('service'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\UnknownIdentifierException |
||||
|
* @expectedExceptionMessage Identifier "foo" is not defined. |
||||
|
*/ |
||||
|
public function testGetValidatesServiceCanBeLocated() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$locator = new ServiceLocator($pimple, array('alias' => 'service')); |
||||
|
|
||||
|
$service = $locator->get('foo'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @expectedException \Pimple\Exception\UnknownIdentifierException |
||||
|
* @expectedExceptionMessage Identifier "invalid" is not defined. |
||||
|
*/ |
||||
|
public function testGetValidatesTargetServiceExists() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$locator = new ServiceLocator($pimple, array('alias' => 'invalid')); |
||||
|
|
||||
|
$service = $locator->get('alias'); |
||||
|
} |
||||
|
|
||||
|
public function testHasValidatesServiceCanBeLocated() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service1'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$pimple['service2'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$locator = new ServiceLocator($pimple, array('service1')); |
||||
|
|
||||
|
$this->assertTrue($locator->has('service1')); |
||||
|
$this->assertFalse($locator->has('service2')); |
||||
|
} |
||||
|
|
||||
|
public function testHasChecksIfTargetServiceExists() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service'] = function () { |
||||
|
return new Fixtures\Service(); |
||||
|
}; |
||||
|
$locator = new ServiceLocator($pimple, array('foo' => 'service', 'bar' => 'invalid')); |
||||
|
|
||||
|
$this->assertTrue($locator->has('foo')); |
||||
|
$this->assertFalse($locator->has('bar')); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/* |
||||
|
* This file is part of Pimple. |
||||
|
* |
||||
|
* Copyright (c) 2009 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 |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
namespace Pimple\Tests; |
||||
|
|
||||
|
use PHPUnit\Framework\TestCase; |
||||
|
use Pimple\Container; |
||||
|
use Pimple\ServiceIterator; |
||||
|
use Pimple\Tests\Fixtures\Service; |
||||
|
|
||||
|
class ServiceIteratorTest extends TestCase |
||||
|
{ |
||||
|
public function testIsIterable() |
||||
|
{ |
||||
|
$pimple = new Container(); |
||||
|
$pimple['service1'] = function () { |
||||
|
return new Service(); |
||||
|
}; |
||||
|
$pimple['service2'] = function () { |
||||
|
return new Service(); |
||||
|
}; |
||||
|
$pimple['service3'] = function () { |
||||
|
return new Service(); |
||||
|
}; |
||||
|
$iterator = new ServiceIterator($pimple, array('service1', 'service2')); |
||||
|
|
||||
|
$this->assertSame(array('service1' => $pimple['service1'], 'service2' => $pimple['service2']), iterator_to_array($iterator)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
# Workerman\Mysql\Connection |
||||
|
|
||||
|
Long-living MySQL connection for daemon. |
||||
|
|
||||
|
# Install |
||||
|
```composer require workerman/mysql``` |
||||
|
|
||||
|
# Usage |
||||
|
```php |
||||
|
$db = new Workerman\MySQL\Connection($mysql_host, $mysql_port, $user, $password, $db_bname); |
||||
|
|
||||
|
// Get all rows. |
||||
|
$db1->select('ID,Sex')->from('Persons')->where('sex= :sex')->bindValues(array('sex'=>'M'))->query(); |
||||
|
// Equivalent to. |
||||
|
$db1->select('ID,Sex')->from('Persons')->where("sex='F'")->query(); |
||||
|
// Equivalent to. |
||||
|
$db->query("SELECT ID,Sex FROM `Persons` WHERE sex='M'"); |
||||
|
|
||||
|
|
||||
|
// Get one row. |
||||
|
$db->select('ID,Sex')->from('Persons')->where('sex= :sex')->bindValues(array('sex'=>'M'))->row(); |
||||
|
// Equivalent to. |
||||
|
$db->select('ID,Sex')->from('Persons')->where("sex= 'F' ")->row(); |
||||
|
// Equivalent to. |
||||
|
$db->row("SELECT ID,Sex FROM `Persons` WHERE sex='M'"); |
||||
|
|
||||
|
|
||||
|
// Get a column. |
||||
|
$db->select('ID')->from('Persons')->where('sex= :sex')->bindValues(array('sex'=>'M'))->column(); |
||||
|
// Equivalent to. |
||||
|
$db->select('ID')->from('Persons')->where("sex= 'F' ")->column(); |
||||
|
// Equivalent to. |
||||
|
$db->column("SELECT `ID` FROM `Persons` WHERE sex='M'"); |
||||
|
|
||||
|
// Get single. |
||||
|
$db->select('ID,Sex')->from('Persons')->where('sex= :sex')->bindValues(array('sex'=>'M'))->single(); |
||||
|
// Equivalent to. |
||||
|
$db->select('ID,Sex')->from('Persons')->where("sex= 'F' ")->single(); |
||||
|
// Equivalent to. |
||||
|
$db->single("SELECT ID,Sex FROM `Persons` WHERE sex='M'"); |
||||
|
|
||||
|
// Complex query. |
||||
|
$db->select('*')->from('table1')->innerJoin('table2','table1.uid = table2.uid')->where('age > :age') |
||||
|
->groupBy(array('aid'))->having('foo="foo"')->orderByASC/*orderByDESC*/(array('did')) |
||||
|
->limit(10)->offset(20)->bindValues(array('age' => 13)); |
||||
|
// Equivalent to. |
||||
|
$db->query(SELECT * FROM `table1` INNER JOIN `table2` ON `table1`.`uid` = `table2`.`uid` WHERE age > 13 |
||||
|
GROUP BY aid HAVING foo="foo" ORDER BY did LIMIT 10 OFFSET 20“); |
||||
|
|
||||
|
// Insert. |
||||
|
$insert_id = $db->insert('Persons')->cols(array( |
||||
|
'Firstname'=>'abc', |
||||
|
'Lastname'=>'efg', |
||||
|
'Sex'=>'M', |
||||
|
'Age'=>13))->query(); |
||||
|
// Equivalent to. |
||||
|
$insert_id = $db->query("INSERT INTO `Persons` ( `Firstname`,`Lastname`,`Sex`,`Age`) |
||||
|
VALUES ( 'abc', 'efg', 'M', 13)"); |
||||
|
|
||||
|
// Updagte. |
||||
|
$row_count = $db->update('Persons')->cols(array('sex'))->where('ID=1') |
||||
|
->bindValue('sex', 'F')->query(); |
||||
|
// Equivalent to. |
||||
|
$row_count = $db->update('Persons')->cols(array('sex'=>'F'))->where('ID=1')->query(); |
||||
|
// Equivalent to. |
||||
|
$row_count = $db->query("UPDATE `Persons` SET `sex` = 'F' WHERE ID=1"); |
||||
|
|
||||
|
// Delete. |
||||
|
$row_count = $db->delete('Persons')->where('ID=9')->query(); |
||||
|
// Equivalent to. |
||||
|
$row_count = $db->query("DELETE FROM `Persons` WHERE ID=9"); |
||||
|
|
||||
|
// Transaction. |
||||
|
$db1->beginTrans(); |
||||
|
.... |
||||
|
$db1->commitTrans(); // or $db1->rollBackTrans(); |
||||
|
|
||||
|
``` |
||||
@ -0,0 +1,16 @@ |
|||||
|
{ |
||||
|
"name" : "workerman/mysql", |
||||
|
"type" : "library", |
||||
|
"keywords": ["mysql", "pdo", "pdo_mysql"], |
||||
|
"homepage": "http://www.workerman.net", |
||||
|
"license" : "MIT", |
||||
|
"description": "Long-living MySQL connection for daemon.", |
||||
|
"require": { |
||||
|
"php": ">=5.3", |
||||
|
"ext-pdo": "*", |
||||
|
"ext-pdo_mysql": "*" |
||||
|
}, |
||||
|
"autoload": { |
||||
|
"psr-4": {"Workerman\\MySQL\\": "./src"} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,649 @@ |
|||||
|
2021-03-05 14:58:33 pid:1 Error: Class 'Im' not found in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php:32 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #18) |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php(36): Workerman\Worker::runAll() |
||||
|
#7 {main} |
||||
|
2021-03-05 14:58:33 pid:1 Worker process terminated |
||||
|
2021-03-05 14:59:04 pid:1 Error: Class 'Im' not found in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php:32 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #18) |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php(36): Workerman\Worker::runAll() |
||||
|
#7 {main} |
||||
|
2021-03-05 14:59:04 pid:1 Worker process terminated |
||||
|
2021-03-05 15:32:32 pid:1 Error: Class 'Im' not found in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php:32 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #18) |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php(36): Workerman\Worker::runAll() |
||||
|
#7 {main} |
||||
|
2021-03-05 15:32:32 pid:1 Worker process terminated |
||||
|
2021-03-05 15:32:55 pid:1 Error: Class 'Im' not found in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php:32 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #18) |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php(36): Workerman\Worker::runAll() |
||||
|
#7 {main} |
||||
|
2021-03-05 15:32:55 pid:1 Worker process terminated |
||||
|
2021-03-05 15:34:26 pid:1 Error: Class 'ImModuleUniapp' not found in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php:32 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #18) |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\api\Im.php(36): Workerman\Worker::runAll() |
||||
|
#7 {main} |
||||
|
2021-03-05 15:34:26 pid:1 Worker process terminated |
||||
|
2021-03-05 17:20:53 pid:1 ArgumentCountError: Too few arguments to function {closure}(), 1 passed in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php on line 931 and exactly 2 expected in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:58 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(931): {closure}(Object(Workerman\Connection\TcpConnection)) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(755): Workerman\Connection\TcpConnection->destroy() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(561): Workerman\Connection\TcpConnection->doSslHandshake(Resource id #64) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #64) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(168): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-05 17:20:53 pid:1 Worker process terminated |
||||
|
2021-03-09 16:34:34 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:139 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(40): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #67) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(204): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:34:34 pid:1 Worker process terminated |
||||
|
2021-03-09 16:34:52 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:139 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(40): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #67) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(204): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:34:52 pid:1 Worker process terminated |
||||
|
2021-03-09 16:35:46 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:141 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(40): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #67) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(206): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:35:46 pid:1 Worker process terminated |
||||
|
2021-03-09 16:37:03 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:141 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(40): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #67) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(206): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:37:03 pid:1 Worker process terminated |
||||
|
2021-03-09 16:37:46 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:142 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(41): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #68) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(207): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:37:46 pid:1 Worker process terminated |
||||
|
2021-03-09 16:38:01 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:143 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(41): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #68) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(208): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:38:01 pid:1 Worker process terminated |
||||
|
2021-03-09 16:53:16 pid:1 PDOException: SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO) in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1711 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1711): PDO->__construct('mysql:dbname=;h...', NULL, NULL, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1698): Workerman\MySQL\Connection->connect() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(41): Workerman\MySQL\Connection->__construct(NULL, NULL, NULL, NULL, NULL) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2415): {closure}(Object(Workerman\Worker)) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(171): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:53:17 pid:1 Worker process terminated |
||||
|
2021-03-09 16:54:22 pid:1 PDOException: SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO) in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1711 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1711): PDO->__construct('mysql:dbname=;h...', NULL, NULL, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1698): Workerman\MySQL\Connection->connect() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(41): Workerman\MySQL\Connection->__construct(NULL, NULL, NULL, NULL, NULL) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2415): {closure}(Object(Workerman\Worker)) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(171): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:54:23 pid:1 Worker process terminated |
||||
|
2021-03-09 16:55:24 pid:1 PDOException: SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO) in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1711 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1711): PDO->__construct('mysql:dbname=;h...', NULL, NULL, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1698): Workerman\MySQL\Connection->connect() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(42): Workerman\MySQL\Connection->__construct(NULL, NULL, NULL, NULL, NULL) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2415): {closure}(Object(Workerman\Worker)) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(172): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:55:25 pid:1 Worker process terminated |
||||
|
2021-03-09 16:55:35 pid:1 PDOException: SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO) in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1711 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1711): PDO->__construct('mysql:dbname=;h...', NULL, NULL, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1698): Workerman\MySQL\Connection->connect() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(42): Workerman\MySQL\Connection->__construct(NULL, NULL, NULL, NULL, NULL) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2415): {closure}(Object(Workerman\Worker)) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(172): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:55:36 pid:1 Worker process terminated |
||||
|
2021-03-09 16:55:52 pid:1 PDOException: SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: NO) in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1711 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1711): PDO->__construct('mysql:dbname=;h...', NULL, NULL, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1698): Workerman\MySQL\Connection->connect() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(42): Workerman\MySQL\Connection->__construct(NULL, NULL, NULL, NULL, NULL) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2415): {closure}(Object(Workerman\Worker)) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(172): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:55:53 pid:1 Worker process terminated |
||||
|
2021-03-09 16:59:18 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:143 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(219): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 16:59:18 pid:1 Worker process terminated |
||||
|
2021-03-09 17:05:09 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:139 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(205): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 17:05:09 pid:1 Worker process terminated |
||||
|
2021-03-09 17:05:13 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:139 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(205): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 17:05:13 pid:1 Worker process terminated |
||||
|
2021-03-09 17:05:18 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:139 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(205): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 17:05:18 pid:1 Worker process terminated |
||||
|
2021-03-09 17:05:30 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:139 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(205): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 17:05:30 pid:1 Worker process terminated |
||||
|
2021-03-09 17:06:02 pid:1 Error: Access to undeclared static property: handle::$table in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:139 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(206): Workerman\Worker::runAll() |
||||
|
#8 {main} |
||||
|
2021-03-09 17:06:02 pid:1 Worker process terminated |
||||
|
2021-03-09 17:20:39 pid:1 PDOException: SQL:INSERT INTO ``ims_wlmerchant_im`` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'ims_wlmerchant_im`` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type' at line 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO ``i...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(100): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(222): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-09 17:20:39 pid:1 Worker process terminated |
||||
|
2021-03-09 17:21:21 pid:1 PDOException: SQL:INSERT INTO ``ims_wlmerchant_im`` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'ims_wlmerchant_im`` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type' at line 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO ``i...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(101): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(223): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-09 17:21:21 pid:1 Worker process terminated |
||||
|
2021-03-12 10:02:19 pid:1 Error: Call to a member function send() on int in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:203 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(178): handle->socketSend(3, '\xE9\x80\x9A\xE8\xAE\xAF\xE8\xAE\xB0\xE5\xBD\x95', 4, 5, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(222): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-12 10:02:19 pid:1 Worker process terminated |
||||
|
2021-03-12 10:02:52 pid:1 Error: Call to a member function send() on int in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:203 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(178): handle->socketSend(24, '\xE9\x80\x9A\xE8\xAE\xAF\xE8\xAE\xB0\xE5\xBD\x95', 4, 5, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(222): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-12 10:02:52 pid:1 Worker process terminated |
||||
|
2021-03-12 11:16:54 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:200 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(173): handle->socketSend(3, '\xE9\x80\x9A\xE8\xAE\xAF\xE8\xAE\xB0\xE5\xBD\x95', 4, 5, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(292): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-12 11:16:54 pid:1 Worker process terminated |
||||
|
2021-03-12 11:17:20 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:200 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(173): handle->socketSend(3, '\xE9\x80\x9A\xE8\xAE\xAF\xE8\xAE\xB0\xE5\xBD\x95', 4, 5, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(292): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-12 11:17:20 pid:1 Worker process terminated |
||||
|
2021-03-12 11:18:23 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:200 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(173): handle->socketSend(3, '\xE9\x80\x9A\xE8\xAE\xAF\xE8\xAE\xB0\xE5\xBD\x95', 4, 5, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":4,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(292): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-12 11:18:23 pid:1 Worker process terminated |
||||
|
2021-03-12 11:18:57 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:200 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(103): handle->socketSend(24, '\xE8\xBF\x99\xE9\x87\x8C\xE6\x98\xAF\xE5\x8F\x91\xE9\x80\x81...', 2, 0) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(296): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-12 11:18:57 pid:1 Worker process terminated |
||||
|
2021-03-12 11:19:18 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:200 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(103): handle->socketSend(24, '\xE8\xBF\x99\xE9\x87\x8C\xE6\x98\xAF\xE5\x8F\x91\xE9\x80\x81...', 2, 0) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(296): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-12 11:19:18 pid:1 Worker process terminated |
||||
|
2021-03-12 11:20:17 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:204 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(103): handle->socketSend(24, '\xE8\xBF\x99\xE9\x87\x8C\xE6\x98\xAF\xE5\x8F\x91\xE9\x80\x81...', 2, 0) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(249): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-12 11:20:17 pid:1 Worker process terminated |
||||
|
2021-03-18 11:02:30 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:218 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(119): handle->socketSend(24, '\xE5\x8D\xB3\xE6\x97\xB6\xE9\x80\x9A\xE8\xAE\xAF\xE4\xBF\xA1...', 2, 0, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(265): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-18 11:02:30 pid:1 Worker process terminated |
||||
|
2021-03-18 11:03:16 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:218 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(119): handle->socketSend(24, '\xE5\x8D\xB3\xE6\x97\xB6\xE9\x80\x9A\xE8\xAE\xAF\xE4\xBF\xA1...', 2, 0, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(265): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-18 11:03:16 pid:1 Worker process terminated |
||||
|
2021-03-18 11:05:22 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:219 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(120): handle->socketSend(24, '\xE5\x8D\xB3\xE6\x97\xB6\xE9\x80\x9A\xE8\xAE\xAF\xE4\xBF\xA1...', 2, 0, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(266): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-18 11:05:22 pid:1 Worker process terminated |
||||
|
2021-03-18 11:05:54 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:217 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(118): handle->socketSend(24, '\xE5\x8D\xB3\xE6\x97\xB6\xE9\x80\x9A\xE8\xAE\xAF\xE4\xBF\xA1...', 2, 0, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(264): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-18 11:05:54 pid:1 Worker process terminated |
||||
|
2021-03-18 11:06:19 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:217 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(118): handle->socketSend(24, '\xE5\x8D\xB3\xE6\x97\xB6\xE9\x80\x9A\xE8\xAE\xAF\xE4\xBF\xA1...', 2, 0, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(264): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-18 11:06:19 pid:1 Worker process terminated |
||||
|
2021-03-18 11:06:37 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:216 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(118): handle->socketSend(24, '\xE5\x8D\xB3\xE6\x97\xB6\xE9\x80\x9A\xE8\xAE\xAF\xE4\xBF\xA1...', 2, 0, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(263): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-18 11:06:37 pid:1 Worker process terminated |
||||
|
2021-03-18 11:07:08 pid:1 Error: Call to a member function send() on null in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php:216 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(118): handle->socketSend(24, '\xE5\x8D\xB3\xE6\x97\xB6\xE9\x80\x9A\xE8\xAE\xAF\xE4\xBF\xA1...', 2, 0, Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(263): Workerman\Worker::runAll() |
||||
|
#9 {main} |
||||
|
2021-03-18 11:07:08 pid:1 Worker process terminated |
||||
|
2021-03-18 11:14:16 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x98\x8A\xF0\x9F...' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(100): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(280): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:14:16 pid:1 Worker process terminated |
||||
|
2021-03-18 11:16:05 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x98\xA2\xF0\x9F...' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(100): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(280): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:16:05 pid:1 Worker process terminated |
||||
|
2021-03-18 11:17:07 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\xA4\xA6\xE2\x80...' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(100): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(280): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:17:07 pid:1 Worker process terminated |
||||
|
2021-03-18 11:17:36 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x8E\xB6' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(101): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(281): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:17:36 pid:1 Worker process terminated |
||||
|
2021-03-18 11:18:25 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x98\x81' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(101): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(281): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:18:25 pid:1 Worker process terminated |
||||
|
2021-03-18 11:18:46 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x8E\xB6' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(100): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(281): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:18:46 pid:1 Worker process terminated |
||||
|
2021-03-18 11:19:21 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\xA4\xA6\xE2\x80...' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(100): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(281): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:19:21 pid:1 Worker process terminated |
||||
|
2021-03-18 11:19:46 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x98\x8D' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(100): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(282): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:19:46 pid:1 Worker process terminated |
||||
|
2021-03-18 11:20:11 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x90\xB1\xE2\x80...' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(100): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(283): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:20:11 pid:1 Worker process terminated |
||||
|
2021-03-18 11:20:34 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x98\x81' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(101): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(284): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:20:34 pid:1 Worker process terminated |
||||
|
2021-03-18 11:21:27 pid:1 PDOException: SQL:INSERT INTO `ims_wlmerchant_im` ( `uniacid`,`send_id`,`send_type`,`receive_id`,`receive_type`,`create_time`,`type`,`content`) VALUES ( :uniacid,:send_id,:send_type,:receive_id,:receive_type,:create_time,:type,:content) SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x98\x8D' for column 'content' at row 1 in D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php:1770 |
||||
|
Stack trace: |
||||
|
#0 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\mysql\src\Connection.php(1828): Workerman\MySQL\Connection->execute('INSERT INTO `im...', Array) |
||||
|
#1 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(101): Workerman\MySQL\Connection->query() |
||||
|
#2 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(47): handle->handleData() |
||||
|
#3 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Connection\TcpConnection.php(637): {closure}(Object(Workerman\Connection\TcpConnection), '{"im_type":2,"s...') |
||||
|
#4 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Events\Select.php(293): Workerman\Connection\TcpConnection->baseRead(Resource id #70) |
||||
|
#5 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(2430): Workerman\Events\Select->loop() |
||||
|
#6 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1418): Workerman\Worker->run() |
||||
|
#7 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(1361): Workerman\Worker::forkWorkersForWindows() |
||||
|
#8 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\vendor\workerman\workerman\Worker.php(542): Workerman\Worker::forkWorkers() |
||||
|
#9 D:\Software\phpstudy_pro\WWW\merchant\addons\weliam_smartcity\core\common\Im.php(281): Workerman\Worker::runAll() |
||||
|
#10 {main} |
||||
|
2021-03-18 11:21:27 pid:1 Worker process terminated |
||||
@ -0,0 +1,6 @@ |
|||||
|
logs |
||||
|
.buildpath |
||||
|
.project |
||||
|
.settings |
||||
|
.idea |
||||
|
.DS_Store |
||||
@ -0,0 +1,69 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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'); |
||||
@ -0,0 +1,377 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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\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 (!$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->_remoteAddress = "{$address_info['host']}:{$address_info['port']}"; |
||||
|
$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->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; |
||||
|
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); |
||||
|
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); |
||||
|
} |
||||
|
// 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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
// 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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
} 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,209 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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; |
||||
|
|
||||
|
/** |
||||
|
* AsyncTcpConnection. |
||||
|
*/ |
||||
|
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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
$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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @link http://www.workerman.net/ |
||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
|
*/ |
||||
|
namespace Workerman\Connection; |
||||
|
|
||||
|
/** |
||||
|
* ConnectionInterface. |
||||
|
*/ |
||||
|
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 $data |
||||
|
* @return void |
||||
|
*/ |
||||
|
abstract public function close($data = null); |
||||
|
} |
||||
@ -0,0 +1,989 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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; |
||||
|
|
||||
|
/** |
||||
|
* 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(); |
||||
|
|
||||
|
/** |
||||
|
* 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; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
$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() |
||||
|
{ |
||||
|
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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
// 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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
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 $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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* This method pulls all the data out of a readable stream, and writes it to the supplied destination. |
||||
|
* |
||||
|
* @param self $dest |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function pipe(self $dest) |
||||
|
{ |
||||
|
$source = $this; |
||||
|
$this->onMessage = function ($source, $data) use ($dest) { |
||||
|
$dest->send($data); |
||||
|
}; |
||||
|
$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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
// 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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
$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]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Destruct. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function __destruct() |
||||
|
{ |
||||
|
static $mod; |
||||
|
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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,191 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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; |
||||
|
|
||||
|
/** |
||||
|
* 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->_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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,195 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author 有个鬼<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; |
||||
|
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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
}; |
||||
|
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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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() |
||||
|
{ |
||||
|
foreach ($this->_allEvents as $event) { |
||||
|
$event->stop(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get timer count. |
||||
|
* |
||||
|
* @return integer |
||||
|
*/ |
||||
|
public function getTimerCount() |
||||
|
{ |
||||
|
return \count($this->_eventTimer); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,217 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author 有个鬼<42765633@qq.com> |
||||
|
* @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 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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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 mixed $args |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function add($fd, $flag, $func, $args = null); |
||||
|
|
||||
|
/** |
||||
|
* 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(); |
||||
|
} |
||||
@ -0,0 +1,227 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,264 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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 $fd |
||||
|
* @param $flag |
||||
|
* @param $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(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,339 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @link http://www.workerman.net/ |
||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
|
*/ |
||||
|
namespace Workerman\Events; |
||||
|
|
||||
|
/** |
||||
|
* 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; |
||||
|
if( $this->_selectTimeout > $select_timeout ){ |
||||
|
$this->_selectTimeout = $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() |
||||
|
{ |
||||
|
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 = ($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]; |
||||
|
$this->_scheduler->insert($timer_id, -$next_run_time); |
||||
|
} |
||||
|
\call_user_func_array($task_data[0], $task_data[1]); |
||||
|
if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { |
||||
|
$this->del($timer_id, self::EV_TIMER_ONCE); |
||||
|
} |
||||
|
continue; |
||||
|
} |
||||
|
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; |
||||
|
|
||||
|
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 { |
||||
|
usleep($this->_selectTimeout); |
||||
|
$ret = false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,222 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author Ares<aresrr#qq.com> |
||||
|
* @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 Swoole\Event; |
||||
|
use Swoole\Timer; |
||||
|
|
||||
|
class Swoole implements EventInterface |
||||
|
{ |
||||
|
|
||||
|
protected $_timer = array(); |
||||
|
|
||||
|
protected $_timerOnceMap = array(); |
||||
|
|
||||
|
protected $mapId = 0; |
||||
|
|
||||
|
protected $_fd = array(); |
||||
|
|
||||
|
// milisecond |
||||
|
public static $signalDispatchInterval = 500; |
||||
|
|
||||
|
protected $_hasSignal = false; |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* {@inheritdoc} |
||||
|
* |
||||
|
* @see \Workerman\Events\EventInterface::add() |
||||
|
*/ |
||||
|
public function add($fd, $flag, $func, $args = null) |
||||
|
{ |
||||
|
if (! isset($args)) { |
||||
|
$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++; |
||||
|
$timer_id = Timer::$method($fd * 1000, |
||||
|
function ($timer_id = null) use ($func, $args, $mapId) { |
||||
|
\call_user_func_array($func, $args); |
||||
|
// 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 (! isset($this->_fd[$fd_key])) { |
||||
|
if ($flag === self::EV_READ) { |
||||
|
$res = Event::add($fd, $func, 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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* {@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 (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() |
||||
|
{ |
||||
|
Event::exit(); |
||||
|
posix_kill(posix_getpid(), SIGINT); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* |
||||
|
* {@inheritdoc} |
||||
|
* |
||||
|
* @see \Workerman\Events\EventInterface::getTimerCount() |
||||
|
*/ |
||||
|
public function getTimerCount() |
||||
|
{ |
||||
|
return \count($this->_timer); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
|
* |
||||
|
* @link http://www.workerman.net/ |
||||
|
*/ |
||||
|
|
||||
|
// Display errors. |
||||
|
ini_set('display_errors', 'on'); |
||||
|
// Reporting all. |
||||
|
error_reporting(E_ALL); |
||||
|
// 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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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 {} |
||||
@ -0,0 +1,21 @@ |
|||||
|
The MIT License |
||||
|
|
||||
|
Copyright (c) 2009-2015 walkor<walkor@workerman.net> 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. |
||||
@ -0,0 +1,61 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,326 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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\Websocket; |
||||
|
use Workerman\Worker; |
||||
|
|
||||
|
/** |
||||
|
* Class Http. |
||||
|
* @package Workerman\Protocols |
||||
|
*/ |
||||
|
class Http |
||||
|
{ |
||||
|
/** |
||||
|
* Request class name. |
||||
|
* |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected static $_requestClass = 'Workerman\Protocols\Http\Request'; |
||||
|
|
||||
|
/** |
||||
|
* Session name. |
||||
|
* |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected static $_sessionName = 'PHPSID'; |
||||
|
|
||||
|
/** |
||||
|
* Upload tmp dir. |
||||
|
* |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected static $_uploadTmpDir = ''; |
||||
|
|
||||
|
/** |
||||
|
* Open cache. |
||||
|
* |
||||
|
* @var bool. |
||||
|
*/ |
||||
|
protected static $_enableCache = true; |
||||
|
|
||||
|
/** |
||||
|
* Get or set session name. |
||||
|
* |
||||
|
* @param null $name |
||||
|
* @return string |
||||
|
*/ |
||||
|
public static function sessionName($name = null) |
||||
|
{ |
||||
|
if ($name !== null && $name !== '') { |
||||
|
static::$_sessionName = (string)$name; |
||||
|
} |
||||
|
return static::$_sessionName; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get or set the request class name. |
||||
|
* |
||||
|
* @param 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 $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 = array(); |
||||
|
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 ($recv_len = \strlen($recv_buffer) >= 16384) { |
||||
|
$connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n"); |
||||
|
return 0; |
||||
|
} |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
$head_len = $crlf_pos + 4; |
||||
|
$method = \strstr($recv_buffer, ' ', true); |
||||
|
|
||||
|
if ($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD' || $method === 'DELETE') { |
||||
|
if (!isset($recv_buffer[512])) { |
||||
|
$input[$recv_buffer] = $head_len; |
||||
|
if (\count($input) > 512) { |
||||
|
unset($input[key($input)]); |
||||
|
} |
||||
|
} |
||||
|
return $head_len; |
||||
|
} else if ($method !== 'POST' && $method !== 'PUT') { |
||||
|
$connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
$header = \substr($recv_buffer, 0, $crlf_pos); |
||||
|
$length = false; |
||||
|
if ($pos = \strpos($header, "\r\nContent-Length: ")) { |
||||
|
$length = $head_len + (int)\substr($header, $pos + 18, 10); |
||||
|
} else if (\preg_match("/\r\ncontent-length: ?(\d+)/i", $header, $match)) { |
||||
|
$length = $head_len + $match[1]; |
||||
|
} |
||||
|
|
||||
|
if ($length !== false) { |
||||
|
if (!isset($recv_buffer[512])) { |
||||
|
$input[$recv_buffer] = $length; |
||||
|
if (\count($input) > 512) { |
||||
|
unset($input[key($input)]); |
||||
|
} |
||||
|
} |
||||
|
return $length; |
||||
|
} |
||||
|
|
||||
|
$connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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($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']; |
||||
|
$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 $handler |
||||
|
* @param $offset |
||||
|
* @param $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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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 $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"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,623 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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(); |
||||
|
|
||||
|
/** |
||||
|
* Http buffer. |
||||
|
* |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $_buffer = null; |
||||
|
|
||||
|
/** |
||||
|
* Request data. |
||||
|
* |
||||
|
* @var array |
||||
|
*/ |
||||
|
protected $_data = null; |
||||
|
|
||||
|
/** |
||||
|
* Header cache. |
||||
|
* |
||||
|
* @var array |
||||
|
*/ |
||||
|
protected static $_headerCache = array(); |
||||
|
|
||||
|
/** |
||||
|
* Get cache. |
||||
|
* |
||||
|
* @var array |
||||
|
*/ |
||||
|
protected static $_getCache = array(); |
||||
|
|
||||
|
/** |
||||
|
* Post cache. |
||||
|
* |
||||
|
* @var array |
||||
|
*/ |
||||
|
protected static $_postCache = array(); |
||||
|
|
||||
|
/** |
||||
|
* Enable cache. |
||||
|
* |
||||
|
* @var bool |
||||
|
*/ |
||||
|
protected static $_enableCache = true; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Request constructor. |
||||
|
* |
||||
|
* @param $buffer |
||||
|
*/ |
||||
|
public function __construct($buffer) |
||||
|
{ |
||||
|
$this->_buffer = $buffer; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* $_GET. |
||||
|
* |
||||
|
* @param null $name |
||||
|
* @param 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 $name |
||||
|
* @param 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 null $name |
||||
|
* @param null $default |
||||
|
* @return 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 null $name |
||||
|
* @param null $default |
||||
|
* @return string|null |
||||
|
*/ |
||||
|
public function cookie($name = null, $default = null) |
||||
|
{ |
||||
|
if (!isset($this->_data['cookie'])) { |
||||
|
\parse_str(\str_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 null $name |
||||
|
* @return array|null |
||||
|
*/ |
||||
|
public function file($name = null) |
||||
|
{ |
||||
|
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 ($without_port && $pos = \strpos($host, ':')) { |
||||
|
return \substr($host, 0, $pos); |
||||
|
} |
||||
|
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'] = \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'] = \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 session id. |
||||
|
* |
||||
|
* @return bool|mixed |
||||
|
*/ |
||||
|
public function sessionId() |
||||
|
{ |
||||
|
if (!isset($this->_data['sid'])) { |
||||
|
$session_name = Http::sessionName(); |
||||
|
$sid = $this->cookie($session_name); |
||||
|
if ($sid === '' || $sid === null) { |
||||
|
if ($this->connection === null) { |
||||
|
Worker::safeEcho('Request->session() fail, header already send'); |
||||
|
return false; |
||||
|
} |
||||
|
$sid = static::createSessionId(); |
||||
|
$cookie_params = \session_get_cookie_params(); |
||||
|
$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'] + \time())) |
||||
|
. (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->_data['sid'] = $sid; |
||||
|
} |
||||
|
return $this->_data['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 $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() |
||||
|
{ |
||||
|
$this->_data['headers'] = array(); |
||||
|
$raw_head = $this->rawHead(); |
||||
|
$head_buffer = \substr($raw_head, \strpos($raw_head, "\r\n") + 2); |
||||
|
$cacheable = static::$_enableCache && !isset($head_buffer[2048]); |
||||
|
if ($cacheable && isset(static::$_headerCache[$head_buffer])) { |
||||
|
$this->_data['headers'] = static::$_headerCache[$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); |
||||
|
$this->_data['headers'][\strtolower($key)] = \ltrim($value); |
||||
|
} else { |
||||
|
$this->_data['headers'][\strtolower($content)] = ''; |
||||
|
} |
||||
|
} |
||||
|
if ($cacheable) { |
||||
|
static::$_headerCache[$head_buffer] = $this->_data['headers']; |
||||
|
if (\count(static::$_headerCache) > 128) { |
||||
|
unset(static::$_headerCache[key(static::$_headerCache)]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parse head. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
protected function parseGet() |
||||
|
{ |
||||
|
$query_string = $this->queryString(); |
||||
|
$this->_data['get'] = array(); |
||||
|
if ($query_string === '') { |
||||
|
return; |
||||
|
} |
||||
|
$cacheable = static::$_enableCache && !isset($query_string[1024]); |
||||
|
if ($cacheable && isset(static::$_getCache[$query_string])) { |
||||
|
$this->_data['get'] = static::$_getCache[$query_string]; |
||||
|
return; |
||||
|
} |
||||
|
\parse_str($query_string, $this->_data['get']); |
||||
|
if ($cacheable) { |
||||
|
static::$_getCache[$query_string] = $this->_data['get']; |
||||
|
if (\count(static::$_getCache) > 256) { |
||||
|
unset(static::$_getCache[key(static::$_getCache)]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parse post. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
protected function parsePost() |
||||
|
{ |
||||
|
$body_buffer = $this->rawBody(); |
||||
|
$this->_data['post'] = $this->_data['files'] = array(); |
||||
|
if ($body_buffer === '') { |
||||
|
return; |
||||
|
} |
||||
|
$cacheable = static::$_enableCache && !isset($body_buffer[1024]); |
||||
|
if ($cacheable && isset(static::$_postCache[$body_buffer])) { |
||||
|
$this->_data['post'] = static::$_postCache[$body_buffer]; |
||||
|
return; |
||||
|
} |
||||
|
$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; |
||||
|
} |
||||
|
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) { |
||||
|
static::$_postCache[$body_buffer] = $this->_data['post']; |
||||
|
if (\count(static::$_postCache) > 256) { |
||||
|
unset(static::$_postCache[key(static::$_postCache)]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parse upload files. |
||||
|
* |
||||
|
* @param $http_post_boundary |
||||
|
* @return void |
||||
|
*/ |
||||
|
protected function parseUploadFiles($http_post_boundary) |
||||
|
{ |
||||
|
$http_body = $this->rawBody(); |
||||
|
$http_body = \substr($http_body, 0, \strlen($http_body) - (\strlen($http_post_boundary) + 4)); |
||||
|
$boundary_data_array = \explode($http_post_boundary . "\r\n", $http_body); |
||||
|
if ($boundary_data_array[0] === '') { |
||||
|
unset($boundary_data_array[0]); |
||||
|
} |
||||
|
$key = -1; |
||||
|
$files = array(); |
||||
|
foreach ($boundary_data_array as $boundary_data_buffer) { |
||||
|
list($boundary_header_buffer, $boundary_value) = \explode("\r\n\r\n", $boundary_data_buffer, 2); |
||||
|
// Remove \r\n from the end of buffer. |
||||
|
$boundary_value = \substr($boundary_value, 0, -2); |
||||
|
$key++; |
||||
|
foreach (\explode("\r\n", $boundary_header_buffer) as $item) { |
||||
|
list($header_key, $header_value) = \explode(": ", $item); |
||||
|
$header_key = \strtolower($header_key); |
||||
|
switch ($header_key) { |
||||
|
case "content-disposition": |
||||
|
// Is file data. |
||||
|
if (\preg_match('/name="(.*?)"; filename="(.*?)"$/i', $header_value, $match)) { |
||||
|
$error = 0; |
||||
|
$tmp_file = ''; |
||||
|
$size = \strlen($boundary_value); |
||||
|
$tmp_upload_dir = HTTP::uploadTmpDir(); |
||||
|
if (!$tmp_upload_dir) { |
||||
|
$error = UPLOAD_ERR_NO_TMP_DIR; |
||||
|
} 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; |
||||
|
} |
||||
|
} |
||||
|
// Parse upload files. |
||||
|
$files[$key] = array( |
||||
|
'key' => $match[1], |
||||
|
'name' => $match[2], |
||||
|
'tmp_name' => $tmp_file, |
||||
|
'size' => $size, |
||||
|
'error' => $error |
||||
|
); |
||||
|
break; |
||||
|
} // Is post field. |
||||
|
else { |
||||
|
// Parse $_POST. |
||||
|
if (\preg_match('/name="(.*?)"$/', $header_value, $match)) { |
||||
|
$this->_data['post'][$match[1]] = $boundary_value; |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
case "content-type": |
||||
|
// add file_type |
||||
|
$files[$key]['type'] = \trim($header_value); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
foreach ($files as $file) { |
||||
|
$key = $file['key']; |
||||
|
unset($file['key']); |
||||
|
$this->_data['files'][$key] = $file; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create session id. |
||||
|
* |
||||
|
* @return string |
||||
|
*/ |
||||
|
protected static function createSessionId() |
||||
|
{ |
||||
|
return \bin2hex(\pack('d', \microtime(true)) . \pack('N', \mt_rand())); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Setter. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @param $value |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function __set($name, $value) |
||||
|
{ |
||||
|
$this->properties[$name] = $value; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Getter. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @return mixed|null |
||||
|
*/ |
||||
|
public function __get($name) |
||||
|
{ |
||||
|
return isset($this->properties[$name]) ? $this->properties[$name] : null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Isset. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function __isset($name) |
||||
|
{ |
||||
|
return isset($this->properties[$name]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Unset. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function __unset($name) |
||||
|
{ |
||||
|
unset($this->properties[$name]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* __destruct. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function __destruct() |
||||
|
{ |
||||
|
if (isset($this->_data['files'])) { |
||||
|
\clearstatcache(); |
||||
|
foreach ($this->_data['files'] as $item) { |
||||
|
if (\is_file($item['tmp_name'])) { |
||||
|
\unlink($item['tmp_name']); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,447 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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; |
||||
|
|
||||
|
/** |
||||
|
* 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 = $body; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set header. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @param $value |
||||
|
* @return $this |
||||
|
*/ |
||||
|
public function header($name, $value) { |
||||
|
$this->_header[$name] = $value; |
||||
|
return $this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set header. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @param $value |
||||
|
* @return Response |
||||
|
*/ |
||||
|
public function withHeader($name, $value) { |
||||
|
return $this->header($name, $value); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set headers. |
||||
|
* |
||||
|
* @param $headers |
||||
|
* @return $this |
||||
|
*/ |
||||
|
public function withHeaders($headers) { |
||||
|
$this->_header = \array_merge($this->_header, $headers); |
||||
|
return $this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove header. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @return $this |
||||
|
*/ |
||||
|
public function withoutHeader($name) { |
||||
|
unset($this->_header[$name]); |
||||
|
return $this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get header. |
||||
|
* |
||||
|
* @param $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 $code |
||||
|
* @param 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 $version |
||||
|
* @return $this |
||||
|
*/ |
||||
|
public function withProtocolVersion($version) { |
||||
|
$this->_version = $version; |
||||
|
return $this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set http body. |
||||
|
* |
||||
|
* @param $body |
||||
|
* @return $this |
||||
|
*/ |
||||
|
public function withBody($body) { |
||||
|
$this->_body = $body; |
||||
|
return $this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get http raw body. |
||||
|
*/ |
||||
|
public function rawBody() { |
||||
|
return $this->_body; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Send file. |
||||
|
* |
||||
|
* @param $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('<h3>404 Not Found</h3>'); |
||||
|
} |
||||
|
$this->file = array('file' => $file, 'offset' => $offset, 'length' => $length); |
||||
|
return $this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set cookie. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @param string $value |
||||
|
* @param int $maxage |
||||
|
* @param string $path |
||||
|
* @param string $domain |
||||
|
* @param bool $secure |
||||
|
* @param bool $http_only |
||||
|
* @return $this |
||||
|
*/ |
||||
|
public function cookie($name, $value = '', $max_age = 0, $path = '', $domain = '', $secure = false, $http_only = false) |
||||
|
{ |
||||
|
$this->_header['Set-Cookie'][] = $name . '=' . \rawurlencode($value) |
||||
|
. (empty($domain) ? '' : '; Domain=' . $domain) |
||||
|
. (empty($max_age) ? '' : '; Max-Age=' . $max_age) |
||||
|
. (empty($path) ? '' : '; Path=' . $path) |
||||
|
. (!$secure ? '' : '; Secure') |
||||
|
. (!$http_only ? '' : '; HttpOnly'); |
||||
|
return $this; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create header for file. |
||||
|
* |
||||
|
* @param $file |
||||
|
* @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: '.\date('D, d M Y H:i:s', $mtime) . ' ' . \date_default_timezone_get() ."\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 "$head\r\n".dechex($body_len)."\r\n{$this->_body}\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(); |
||||
@ -0,0 +1,64 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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['data'])) { |
||||
|
$buffer .= 'data: ' . \str_replace("\n", "\ndata: ", $data['data']) . "\n\n"; |
||||
|
} |
||||
|
if (isset($data['id'])) { |
||||
|
$buffer .= "id: {$data['id']}\n"; |
||||
|
} |
||||
|
if (isset($data['retry'])) { |
||||
|
$buffer .= "retry: {$data['retry']}\n"; |
||||
|
} |
||||
|
return $buffer; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,359 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @link http://www.workerman.net/ |
||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
|
*/ |
||||
|
namespace Workerman\Protocols\Http; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 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.gc_probability |
||||
|
* |
||||
|
* @var int |
||||
|
*/ |
||||
|
protected static $_sessionGcProbability = 1; |
||||
|
|
||||
|
/** |
||||
|
* Session.gc_divisor |
||||
|
* |
||||
|
* @var int |
||||
|
*/ |
||||
|
protected static $_sessionGcDivisor = 1000; |
||||
|
|
||||
|
/** |
||||
|
* Session.gc_maxlifetime |
||||
|
* |
||||
|
* @var int |
||||
|
*/ |
||||
|
protected static $_sessionGcMaxLifeTime = 1440; |
||||
|
|
||||
|
/** |
||||
|
* Session handler instance. |
||||
|
* |
||||
|
* @var \SessionHandlerInterface |
||||
|
*/ |
||||
|
protected static $_handler = null; |
||||
|
|
||||
|
/** |
||||
|
* Session data. |
||||
|
* |
||||
|
* @var array |
||||
|
*/ |
||||
|
protected $_data = array(); |
||||
|
|
||||
|
/** |
||||
|
* Session changed and need to save. |
||||
|
* |
||||
|
* @var bool |
||||
|
*/ |
||||
|
protected $_needSave = false; |
||||
|
|
||||
|
/** |
||||
|
* Session id. |
||||
|
* |
||||
|
* @var null |
||||
|
*/ |
||||
|
protected $_sessionId = null; |
||||
|
|
||||
|
/** |
||||
|
* Session constructor. |
||||
|
* |
||||
|
* @param $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 $name |
||||
|
* @param 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 $name |
||||
|
* @param $value |
||||
|
*/ |
||||
|
public function set($name, $value) |
||||
|
{ |
||||
|
$this->_data[$name] = $value; |
||||
|
$this->_needSave = true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Delete an item from the session. |
||||
|
* |
||||
|
* @param $name |
||||
|
*/ |
||||
|
public function delete($name) |
||||
|
{ |
||||
|
unset($this->_data[$name]); |
||||
|
$this->_needSave = true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Retrieve and delete an item from the session. |
||||
|
* |
||||
|
* @param $name |
||||
|
* @param 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 $key |
||||
|
* @param 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 $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 = array(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Determining If An Item Exists In The Session. |
||||
|
* |
||||
|
* @param $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 $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)); |
||||
|
} |
||||
|
} |
||||
|
$this->_needSave = false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Init. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public static function init() |
||||
|
{ |
||||
|
if ($gc_probability = \ini_get('session.gc_probability')) { |
||||
|
self::$_sessionGcProbability = (int)$gc_probability; |
||||
|
} |
||||
|
|
||||
|
if ($gc_divisor = \ini_get('session.gc_divisor')) { |
||||
|
self::$_sessionGcDivisor = (int)$gc_divisor; |
||||
|
} |
||||
|
|
||||
|
if ($gc_max_life_time = \ini_get('session.gc_maxlifetime')) { |
||||
|
self::$_sessionGcMaxLifeTime = (int)$gc_max_life_time; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Set session handler class. |
||||
|
* |
||||
|
* @param null $class_name |
||||
|
* @param 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; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Try GC sessions. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function tryGcSessions() |
||||
|
{ |
||||
|
if (\rand(1, static::$_sessionGcDivisor) > static::$_sessionGcProbability) { |
||||
|
return; |
||||
|
} |
||||
|
static::$_handler->gc(static::$_sessionGcMaxLifeTime); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* __destruct. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public function __destruct() |
||||
|
{ |
||||
|
$this->save(); |
||||
|
$this->tryGcSessions(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check session id. |
||||
|
* |
||||
|
* @param $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(); |
||||
@ -0,0 +1,153 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @link http://www.workerman.net/ |
||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
|
*/ |
||||
|
namespace 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)) { |
||||
|
$data = \file_get_contents($session_file); |
||||
|
return $data ? $data : ''; |
||||
|
} |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@inheritdoc} |
||||
|
*/ |
||||
|
public function write($session_id, $session_data) |
||||
|
{ |
||||
|
$temp_file = static::$_sessionSavePath.uniqid(mt_rand(), true); |
||||
|
if (!\file_put_contents($temp_file, $session_data)) { |
||||
|
return false; |
||||
|
} |
||||
|
return \rename($temp_file, static::sessionFile($session_id)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@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 $session_id |
||||
|
* @return string |
||||
|
*/ |
||||
|
protected static function sessionFile($session_id) { |
||||
|
return static::$_sessionSavePath.static::$_sessionFilePrefix.$session_id; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get or set session file path. |
||||
|
* |
||||
|
* @param $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(); |
||||
@ -0,0 +1,119 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @link http://www.workerman.net/ |
||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
|
*/ |
||||
|
namespace Workerman\Protocols\Http\Session; |
||||
|
|
||||
|
/** |
||||
|
* Class RedisSessionHandler |
||||
|
* @package Workerman\Protocols\Http\Session |
||||
|
*/ |
||||
|
class RedisSessionHandler extends \SessionHandler |
||||
|
{ |
||||
|
|
||||
|
/** |
||||
|
* @var \Redis |
||||
|
*/ |
||||
|
protected $_redis; |
||||
|
|
||||
|
/** |
||||
|
* @var int |
||||
|
*/ |
||||
|
protected $_maxLifeTime; |
||||
|
|
||||
|
/** |
||||
|
* RedisSessionHandler constructor. |
||||
|
* @param $config = [ |
||||
|
* 'host' => '127.0.0.1', |
||||
|
* 'port' => 6379, |
||||
|
* 'timeout' => 2, |
||||
|
* 'auth' => '******', |
||||
|
* 'database' => 2, |
||||
|
* 'prefix' => 'redis_session_', |
||||
|
* ] |
||||
|
*/ |
||||
|
public function __construct($config) |
||||
|
{ |
||||
|
if (false === extension_loaded('redis')) { |
||||
|
throw new \RuntimeException('Please install redis extension.'); |
||||
|
} |
||||
|
$this->_maxLifeTime = (int)ini_get('session.gc_maxlifetime'); |
||||
|
|
||||
|
if (!isset($config['timeout'])) { |
||||
|
$config['timeout'] = 2; |
||||
|
} |
||||
|
|
||||
|
$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) |
||||
|
{ |
||||
|
return $this->_redis->get($session_id); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@inheritdoc} |
||||
|
*/ |
||||
|
public function write($session_id, $session_data) |
||||
|
{ |
||||
|
return true === $this->_redis->setex($session_id, $this->_maxLifeTime, $session_data); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* {@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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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); |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,503 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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\Worker; |
||||
|
|
||||
|
/** |
||||
|
* WebSocket protocol. |
||||
|
*/ |
||||
|
class Websocket implements \Workerman\Protocols\ProtocolInterface |
||||
|
{ |
||||
|
/** |
||||
|
* 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) |
||||
|
{ |
||||
|
// 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->websocketHandshake)) { |
||||
|
return static::dealHandshake($buffer, $connection); |
||||
|
} |
||||
|
|
||||
|
// Buffer websocket frame data. |
||||
|
if ($connection->websocketCurrentFrameLength) { |
||||
|
// We need more frame data. |
||||
|
if ($connection->websocketCurrentFrameLength > $recv_len) { |
||||
|
// 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]); |
||||
|
$data_len = $secondbyte & 127; |
||||
|
$is_fin_frame = $firstbyte >> 7; |
||||
|
$masked = $secondbyte >> 7; |
||||
|
|
||||
|
if (!$masked) { |
||||
|
Worker::safeEcho("frame not masked so close the connection\n"); |
||||
|
$connection->close(); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
$opcode = $firstbyte & 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. |
||||
|
if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) { |
||||
|
try { |
||||
|
\call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} // 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->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"; |
||||
|
if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) { |
||||
|
try { |
||||
|
\call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection, $ping_data); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} 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. |
||||
|
if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) { |
||||
|
try { |
||||
|
\call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection, $pong_data); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
$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->websocketCurrentFrameLength = $current_frame_length; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Received just a frame length data. |
||||
|
if ($connection->websocketCurrentFrameLength === $recv_len) { |
||||
|
static::decode($buffer, $connection); |
||||
|
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); |
||||
|
$connection->websocketCurrentFrameLength = 0; |
||||
|
return 0; |
||||
|
} // The length of the received data is greater than the length of a frame. |
||||
|
elseif ($connection->websocketCurrentFrameLength < $recv_len) { |
||||
|
static::decode(\substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); |
||||
|
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); |
||||
|
$current_frame_length = $connection->websocketCurrentFrameLength; |
||||
|
$connection->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. "); |
||||
|
} |
||||
|
$len = \strlen($buffer); |
||||
|
if (empty($connection->websocketType)) { |
||||
|
$connection->websocketType = static::BINARY_TYPE_BLOB; |
||||
|
} |
||||
|
|
||||
|
$first_byte = $connection->websocketType; |
||||
|
|
||||
|
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->websocketHandshake)) { |
||||
|
if (empty($connection->tmpWebsocketData)) { |
||||
|
$connection->tmpWebsocketData = ''; |
||||
|
} |
||||
|
// If buffer has already full then discard the current package. |
||||
|
if (\strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { |
||||
|
if ($connection->onError) { |
||||
|
try { |
||||
|
\call_user_func($connection->onError, $connection, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
return ''; |
||||
|
} |
||||
|
$connection->tmpWebsocketData .= $encode_buffer; |
||||
|
// Check buffer is full. |
||||
|
if ($connection->maxSendBufferSize <= \strlen($connection->tmpWebsocketData)) { |
||||
|
if ($connection->onBufferFull) { |
||||
|
try { |
||||
|
\call_user_func($connection->onBufferFull, $connection); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Return empty string. |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
|
return $encode_buffer; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Websocket decode. |
||||
|
* |
||||
|
* @param string $buffer |
||||
|
* @param ConnectionInterface $connection |
||||
|
* @return string |
||||
|
*/ |
||||
|
public static function decode($buffer, ConnectionInterface $connection) |
||||
|
{ |
||||
|
$len = \ord($buffer[1]) & 127; |
||||
|
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->websocketCurrentFrameLength) { |
||||
|
$connection->websocketDataBuffer .= $decoded; |
||||
|
return $connection->websocketDataBuffer; |
||||
|
} else { |
||||
|
if ($connection->websocketDataBuffer !== '') { |
||||
|
$decoded = $connection->websocketDataBuffer . $decoded; |
||||
|
$connection->websocketDataBuffer = ''; |
||||
|
} |
||||
|
return $decoded; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Websocket handshake. |
||||
|
* |
||||
|
* @param string $buffer |
||||
|
* @param TcpConnection $connection |
||||
|
* @return int |
||||
|
*/ |
||||
|
public static function dealHandshake($buffer, TcpConnection $connection) |
||||
|
{ |
||||
|
// HTTP protocol. |
||||
|
if (0 === \strpos($buffer, 'GET')) { |
||||
|
// Find \r\n\r\n. |
||||
|
$heder_end_pos = \strpos($buffer, "\r\n\r\n"); |
||||
|
if (!$heder_end_pos) { |
||||
|
return 0; |
||||
|
} |
||||
|
$header_length = $heder_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->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n<div style=\"text-align:center\"><h1>Websocket</h1><hr>powered by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>", |
||||
|
true); |
||||
|
$connection->close(); |
||||
|
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->websocketDataBuffer = ''; |
||||
|
// Current websocket frame length. |
||||
|
$connection->websocketCurrentFrameLength = 0; |
||||
|
// Current websocket frame data. |
||||
|
$connection->websocketCurrentFrameBuffer = ''; |
||||
|
// Consume handshake data. |
||||
|
$connection->consumeRecvBuffer($header_length); |
||||
|
|
||||
|
// blob or arraybuffer |
||||
|
if (empty($connection->websocketType)) { |
||||
|
$connection->websocketType = static::BINARY_TYPE_BLOB; |
||||
|
} |
||||
|
|
||||
|
$has_server_header = false; |
||||
|
|
||||
|
// Try to emit onWebSocketConnect callback. |
||||
|
if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) { |
||||
|
static::parseHttpHeader($buffer); |
||||
|
try { |
||||
|
\call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
if (!empty($_SESSION) && \class_exists('\GatewayWorker\Lib\Context')) { |
||||
|
$connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); |
||||
|
} |
||||
|
$_GET = $_SERVER = $_SESSION = $_COOKIE = array(); |
||||
|
|
||||
|
if (isset($connection->headers)) { |
||||
|
if (\is_array($connection->headers)) { |
||||
|
foreach ($connection->headers as $header) { |
||||
|
if (\strpos($header, 'Server:') === 0) { |
||||
|
$has_server_header = true; |
||||
|
} |
||||
|
$handshake_message .= "$header\r\n"; |
||||
|
} |
||||
|
} else { |
||||
|
$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->websocketHandshake = true; |
||||
|
// There are data waiting to be sent. |
||||
|
if (!empty($connection->tmpWebsocketData)) { |
||||
|
$connection->send($connection->tmpWebsocketData, true); |
||||
|
$connection->tmpWebsocketData = ''; |
||||
|
} |
||||
|
if (\strlen($buffer) > $header_length) { |
||||
|
return static::input(\substr($buffer, $header_length), $connection); |
||||
|
} |
||||
|
return 0; |
||||
|
} // Is flash policy-file-request. |
||||
|
elseif (0 === \strpos($buffer, '<polic')) { |
||||
|
$policy_xml = '<?xml version="1.0"?><cross-domain-policy><site-control permitted-cross-domain-policies="all"/><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>' . "\0"; |
||||
|
$connection->send($policy_xml, true); |
||||
|
$connection->consumeRecvBuffer(\strlen($buffer)); |
||||
|
return 0; |
||||
|
} |
||||
|
// Bad websocket handshake request. |
||||
|
$connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n<div style=\"text-align:center\"><h1>Websocket</h1><hr>powered by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>", |
||||
|
true); |
||||
|
$connection->close(); |
||||
|
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'] = ''; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,472 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @link http://www.workerman.net/ |
||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
|
*/ |
||||
|
namespace Workerman\Protocols; |
||||
|
|
||||
|
use Workerman\Worker; |
||||
|
use Workerman\Lib\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->handshakeStep)) { |
||||
|
Worker::safeEcho("recv data before handshake. Buffer:" . \bin2hex($buffer) . "\n"); |
||||
|
return false; |
||||
|
} |
||||
|
// Recv handshake response |
||||
|
if ($connection->handshakeStep === 1) { |
||||
|
return self::dealHandshake($buffer, $connection); |
||||
|
} |
||||
|
$recv_len = \strlen($buffer); |
||||
|
if ($recv_len < 2) { |
||||
|
return 0; |
||||
|
} |
||||
|
// Buffer websocket frame data. |
||||
|
if ($connection->websocketCurrentFrameLength) { |
||||
|
// We need more frame data. |
||||
|
if ($connection->websocketCurrentFrameLength > $recv_len) { |
||||
|
// 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]); |
||||
|
$data_len = $secondbyte & 127; |
||||
|
$is_fin_frame = $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: |
||||
|
break; |
||||
|
// Blob type. |
||||
|
case 0x1: |
||||
|
break; |
||||
|
// Arraybuffer type. |
||||
|
case 0x2: |
||||
|
break; |
||||
|
// Close package. |
||||
|
case 0x8: |
||||
|
// Try to emit onWebSocketClose callback. |
||||
|
if (isset($connection->onWebSocketClose)) { |
||||
|
try { |
||||
|
\call_user_func($connection->onWebSocketClose, $connection); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} // Close connection. |
||||
|
else { |
||||
|
$connection->close(); |
||||
|
} |
||||
|
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:" . $buffer . "\n"); |
||||
|
$connection->close(); |
||||
|
return 0; |
||||
|
} |
||||
|
// Calculate packet length. |
||||
|
if ($data_len === 126) { |
||||
|
if (\strlen($buffer) < 4) { |
||||
|
return 0; |
||||
|
} |
||||
|
$pack = \unpack('nn/ntotal_len', $buffer); |
||||
|
$current_frame_length = $pack['total_len'] + 4; |
||||
|
} else if ($data_len === 127) { |
||||
|
if (\strlen($buffer) < 10) { |
||||
|
return 0; |
||||
|
} |
||||
|
$arr = \unpack('n/N2c', $buffer); |
||||
|
$current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; |
||||
|
} else { |
||||
|
$current_frame_length = $data_len + 2; |
||||
|
} |
||||
|
|
||||
|
$total_package_size = \strlen($connection->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"; |
||||
|
if (isset($connection->onWebSocketPing)) { |
||||
|
try { |
||||
|
\call_user_func($connection->onWebSocketPing, $connection, $ping_data); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} 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. |
||||
|
if (isset($connection->onWebSocketPong)) { |
||||
|
try { |
||||
|
\call_user_func($connection->onWebSocketPong, $connection, $pong_data); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
$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->websocketCurrentFrameLength = $current_frame_length; |
||||
|
} |
||||
|
} |
||||
|
// Received just a frame length data. |
||||
|
if ($connection->websocketCurrentFrameLength === $recv_len) { |
||||
|
self::decode($buffer, $connection); |
||||
|
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); |
||||
|
$connection->websocketCurrentFrameLength = 0; |
||||
|
return 0; |
||||
|
} // The length of the received data is greater than the length of a 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; |
||||
|
// Continue to read next frame. |
||||
|
return self::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($payload, ConnectionInterface $connection) |
||||
|
{ |
||||
|
if (empty($connection->websocketType)) { |
||||
|
$connection->websocketType = self::BINARY_TYPE_BLOB; |
||||
|
} |
||||
|
$payload = (string)$payload; |
||||
|
if (empty($connection->handshakeStep)) { |
||||
|
static::sendHandshake($connection); |
||||
|
} |
||||
|
$mask = 1; |
||||
|
$mask_key = "\x00\x00\x00\x00"; |
||||
|
|
||||
|
$pack = ''; |
||||
|
$length = $length_flag = \strlen($payload); |
||||
|
if (65535 < $length) { |
||||
|
$pack = \pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); |
||||
|
$length_flag = 127; |
||||
|
} else if (125 < $length) { |
||||
|
$pack = \pack('n*', $length); |
||||
|
$length_flag = 126; |
||||
|
} |
||||
|
|
||||
|
$head = ($mask << 7) | $length_flag; |
||||
|
$head = $connection->websocketType . \chr($head) . $pack; |
||||
|
|
||||
|
$frame = $head . $mask_key; |
||||
|
// append payload to frame: |
||||
|
$mask_key = \str_repeat($mask_key, \floor($length / 4)) . \substr($mask_key, 0, $length % 4); |
||||
|
$frame .= $payload ^ $mask_key; |
||||
|
if ($connection->handshakeStep === 1) { |
||||
|
// If buffer has already full then discard the current package. |
||||
|
if (\strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { |
||||
|
if ($connection->onError) { |
||||
|
try { |
||||
|
\call_user_func($connection->onError, $connection, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
return ''; |
||||
|
} |
||||
|
$connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame; |
||||
|
// Check buffer is full. |
||||
|
if ($connection->maxSendBufferSize <= \strlen($connection->tmpWebsocketData)) { |
||||
|
if ($connection->onBufferFull) { |
||||
|
try { |
||||
|
\call_user_func($connection->onBufferFull, $connection); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return ''; |
||||
|
} |
||||
|
return $frame; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Websocket decode. |
||||
|
* |
||||
|
* @param string $buffer |
||||
|
* @param ConnectionInterface $connection |
||||
|
* @return string |
||||
|
*/ |
||||
|
public static function decode($bytes, ConnectionInterface $connection) |
||||
|
{ |
||||
|
$data_length = \ord($bytes[1]); |
||||
|
|
||||
|
if ($data_length === 126) { |
||||
|
$decoded_data = \substr($bytes, 4); |
||||
|
} else if ($data_length === 127) { |
||||
|
$decoded_data = \substr($bytes, 10); |
||||
|
} else { |
||||
|
$decoded_data = \substr($bytes, 2); |
||||
|
} |
||||
|
if ($connection->websocketCurrentFrameLength) { |
||||
|
$connection->websocketDataBuffer .= $decoded_data; |
||||
|
return $connection->websocketDataBuffer; |
||||
|
} else { |
||||
|
if ($connection->websocketDataBuffer !== '') { |
||||
|
$decoded_data = $connection->websocketDataBuffer . $decoded_data; |
||||
|
$connection->websocketDataBuffer = ''; |
||||
|
} |
||||
|
return $decoded_data; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Send websocket handshake data. |
||||
|
* |
||||
|
* @return void |
||||
|
*/ |
||||
|
public static function onConnect($connection) |
||||
|
{ |
||||
|
static::sendHandshake($connection); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Clean |
||||
|
* |
||||
|
* @param $connection |
||||
|
*/ |
||||
|
public static function onClose($connection) |
||||
|
{ |
||||
|
$connection->handshakeStep = null; |
||||
|
$connection->websocketCurrentFrameLength = 0; |
||||
|
$connection->tmpWebsocketData = ''; |
||||
|
$connection->websocketDataBuffer = ''; |
||||
|
if (!empty($connection->websocketPingTimer)) { |
||||
|
Timer::del($connection->websocketPingTimer); |
||||
|
$connection->websocketPingTimer = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Send websocket handshake. |
||||
|
* |
||||
|
* @param TcpConnection $connection |
||||
|
* @return void |
||||
|
*/ |
||||
|
public static function sendHandshake(TcpConnection $connection) |
||||
|
{ |
||||
|
if (!empty($connection->handshakeStep)) { |
||||
|
return; |
||||
|
} |
||||
|
// Get Host. |
||||
|
$port = $connection->getRemotePort(); |
||||
|
$host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; |
||||
|
// Handshake header. |
||||
|
$connection->websocketSecKey = \base64_encode(\md5(\mt_rand(), true)); |
||||
|
$user_header = isset($connection->headers) ? $connection->headers : |
||||
|
(isset($connection->wsHttpHeader) ? $connection->wsHttpHeader : null); |
||||
|
$user_header_str = ''; |
||||
|
if (!empty($user_header)) { |
||||
|
if (\is_array($user_header)){ |
||||
|
foreach($user_header as $k=>$v){ |
||||
|
$user_header_str .= "$k: $v\r\n"; |
||||
|
} |
||||
|
} else { |
||||
|
$user_header_str .= $user_header; |
||||
|
} |
||||
|
$user_header_str = "\r\n".\trim($user_header_str); |
||||
|
} |
||||
|
$header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". |
||||
|
(!\preg_match("/\nHost:/i", $user_header_str) ? "Host: $host\r\n" : ''). |
||||
|
"Connection: Upgrade\r\n". |
||||
|
"Upgrade: websocket\r\n". |
||||
|
(isset($connection->websocketOrigin) ? "Origin: ".$connection->websocketOrigin."\r\n":''). |
||||
|
(isset($connection->WSClientProtocol)?"Sec-WebSocket-Protocol: ".$connection->WSClientProtocol."\r\n":''). |
||||
|
"Sec-WebSocket-Version: 13\r\n". |
||||
|
"Sec-WebSocket-Key: " . $connection->websocketSecKey . $user_header_str . "\r\n\r\n"; |
||||
|
$connection->send($header, true); |
||||
|
$connection->handshakeStep = 1; |
||||
|
$connection->websocketCurrentFrameLength = 0; |
||||
|
$connection->websocketDataBuffer = ''; |
||||
|
$connection->tmpWebsocketData = ''; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Websocket handshake. |
||||
|
* |
||||
|
* @param string $buffer |
||||
|
* @param TcpConnection $connection |
||||
|
* @return int |
||||
|
*/ |
||||
|
public static function dealHandshake($buffer, TcpConnection $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->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->WSServerProtocol = \trim($match[1]); |
||||
|
} |
||||
|
|
||||
|
$connection->handshakeStep = 2; |
||||
|
$handshake_response_length = $pos + 4; |
||||
|
// Try to emit onWebSocketConnect callback. |
||||
|
if (isset($connection->onWebSocketConnect)) { |
||||
|
try { |
||||
|
\call_user_func($connection->onWebSocketConnect, $connection, \substr($buffer, 0, $handshake_response_length)); |
||||
|
} catch (\Exception $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} catch (\Error $e) { |
||||
|
Worker::log($e); |
||||
|
exit(250); |
||||
|
} |
||||
|
} |
||||
|
// Headbeat. |
||||
|
if (!empty($connection->websocketPingInterval)) { |
||||
|
$connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ |
||||
|
if (false === $connection->send(\pack('H*', '898000000000'), true)) { |
||||
|
Timer::del($connection->websocketPingTimer); |
||||
|
$connection->websocketPingTimer = null; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
$connection->consumeRecvBuffer($handshake_response_length); |
||||
|
if (!empty($connection->tmpWebsocketData)) { |
||||
|
$connection->send($connection->tmpWebsocketData, true); |
||||
|
$connection->tmpWebsocketData = ''; |
||||
|
} |
||||
|
if (\strlen($buffer) > $handshake_response_length) { |
||||
|
return self::input(\substr($buffer, $handshake_response_length), $connection); |
||||
|
} |
||||
|
} |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
public static function WSSetProtocol($connection, $params) { |
||||
|
$connection->WSClientProtocol = $params[0]; |
||||
|
} |
||||
|
|
||||
|
public static function WSGetServerProtocol($connection) { |
||||
|
return (\property_exists($connection, 'WSServerProtocol') ? $connection->WSServerProtocol : null); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,306 @@ |
|||||
|
# Workerman |
||||
|
[](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) |
||||
|
[](https://packagist.org/packages/workerman/workerman) |
||||
|
[](https://packagist.org/packages/workerman/workerman) |
||||
|
[](https://packagist.org/packages/workerman/workerman) |
||||
|
[](https://packagist.org/packages/workerman/workerman) |
||||
|
[](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 5.3 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 |
||||
|
<?php |
||||
|
|
||||
|
use Workerman\Worker; |
||||
|
|
||||
|
require_once __DIR__ . '/vendor/autoload.php'; |
||||
|
|
||||
|
// Create a Websocket server |
||||
|
$ws_worker = new Worker('websocket://0.0.0.0:2346'); |
||||
|
|
||||
|
// 4 processes |
||||
|
$ws_worker->count = 4; |
||||
|
|
||||
|
// Emitted when new connection come |
||||
|
$ws_worker->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 |
||||
|
use Workerman\Worker; |
||||
|
|
||||
|
require_once __DIR__ . '/vendor/autoload.php'; |
||||
|
|
||||
|
// #### http worker #### |
||||
|
$http_worker = new Worker('http://0.0.0.0:2345'); |
||||
|
|
||||
|
// 4 processes |
||||
|
$http_worker->count = 4; |
||||
|
|
||||
|
// Emitted when data received |
||||
|
$http_worker->onMessage = function ($connection, $request) { |
||||
|
//$request->get(); |
||||
|
//$request->post(); |
||||
|
//$request->header(); |
||||
|
//$request->cookie(); |
||||
|
//$requset->session(); |
||||
|
//$request->uri(); |
||||
|
//$request->path(); |
||||
|
//$request->method(); |
||||
|
|
||||
|
// Send data to client |
||||
|
$connection->send("Hello World"); |
||||
|
}; |
||||
|
|
||||
|
// Run all workers |
||||
|
Worker::runAll(); |
||||
|
``` |
||||
|
|
||||
|
### A tcp server |
||||
|
```php |
||||
|
use Workerman\Worker; |
||||
|
|
||||
|
require_once __DIR__ . '/vendor/autoload.php'; |
||||
|
|
||||
|
// #### create socket and listen 1234 port #### |
||||
|
$tcp_worker = new Worker('tcp://0.0.0.0:1234'); |
||||
|
|
||||
|
// 4 processes |
||||
|
$tcp_worker->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(); |
||||
|
``` |
||||
|
|
||||
|
### Enable SSL |
||||
|
```php |
||||
|
<?php |
||||
|
|
||||
|
use Workerman\Worker; |
||||
|
|
||||
|
require_once __DIR__ . '/vendor/autoload.php'; |
||||
|
|
||||
|
// SSL context. |
||||
|
$context = array( |
||||
|
'ssl' => 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 |
||||
|
|
||||
|
namespace Protocols; |
||||
|
|
||||
|
/** |
||||
|
* User defined protocol |
||||
|
* Format Text+"\n" |
||||
|
*/ |
||||
|
class MyTextProtocol |
||||
|
{ |
||||
|
public static function input($recv_buffer) |
||||
|
{ |
||||
|
// Find the position of the first occurrence of "\n" |
||||
|
$pos = strpos($recv_buffer, "\n"); |
||||
|
|
||||
|
// Not a complete package. Return 0 because the length of package can not be calculated |
||||
|
if ($pos === false) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// Return length of the package |
||||
|
return $pos+1; |
||||
|
} |
||||
|
|
||||
|
public static function decode($recv_buffer) |
||||
|
{ |
||||
|
return trim($recv_buffer); |
||||
|
} |
||||
|
|
||||
|
public static function encode($data) |
||||
|
{ |
||||
|
return $data . "\n"; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
```php |
||||
|
use Workerman\Worker; |
||||
|
|
||||
|
require_once __DIR__ . '/vendor/autoload.php'; |
||||
|
|
||||
|
// #### MyTextProtocol worker #### |
||||
|
$text_worker = new Worker('MyTextProtocol://0.0.0.0:5678'); |
||||
|
|
||||
|
$text_worker->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 |
||||
|
|
||||
|
use Workerman\Worker; |
||||
|
use Workerman\Timer; |
||||
|
|
||||
|
require_once __DIR__ . '/vendor/autoload.php'; |
||||
|
|
||||
|
$task = new Worker(); |
||||
|
$task->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 |
||||
|
|
||||
|
use Workerman\Worker; |
||||
|
use Workerman\Connection\AsyncTcpConnection; |
||||
|
|
||||
|
require_once __DIR__ . '/vendor/autoload.php'; |
||||
|
|
||||
|
$worker = new Worker(); |
||||
|
$worker->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 ``` |
||||
|
 |
||||
|
```php start.php status ``` |
||||
|
 |
||||
|
```php start.php connections``` |
||||
|
```php start.php stop ``` |
||||
|
```php start.php restart ``` |
||||
|
```php start.php reload ``` |
||||
|
|
||||
|
## Documentation |
||||
|
|
||||
|
中文主页:[http://www.workerman.net](http://www.workerman.net) |
||||
|
|
||||
|
中文文档: [http://doc.workerman.net](http://doc.workerman.net) |
||||
|
|
||||
|
Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md) |
||||
|
|
||||
|
# Benchmarks |
||||
|
https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext&l=zik073-1r |
||||
|
|
||||
|
|
||||
|
## Other links with workerman |
||||
|
|
||||
|
[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) |
||||
|
|
||||
|
## Donate |
||||
|
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UQGGS9UB35WWG"><img src="http://donate.workerman.net/img/donate.png"></a> |
||||
|
|
||||
|
## LICENSE |
||||
|
|
||||
|
Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). |
||||
@ -0,0 +1,213 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* This file is part of workerman. |
||||
|
* |
||||
|
* Licensed under The MIT License |
||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
|
* Redistributions of files must retain the above copyright notice. |
||||
|
* |
||||
|
* @author walkor<walkor@workerman.net> |
||||
|
* @copyright walkor<walkor@workerman.net> |
||||
|
* @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 (!\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(); |
||||
|
\pcntl_alarm(0); |
||||
|
if (self::$_event) { |
||||
|
self::$_event->clearAllTimer(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -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": ">=5.3" |
||||
|
}, |
||||
|
"suggest": { |
||||
|
"ext-event": "For better performance. " |
||||
|
}, |
||||
|
"autoload": { |
||||
|
"psr-4": { |
||||
|
"Workerman\\": "./" |
||||
|
} |
||||
|
}, |
||||
|
"minimum-stability": "dev" |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
--- |
||||
|
name: Bug Report |
||||
|
about: Bug report |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 包版本号 |
||||
|
|
||||
|
|
||||
|
## 问题描述 |
||||
|
|
||||
|
|
||||
|
## 你的代码 |
||||
|
|
||||
|
|
||||
|
## 报错详情 |
||||
|
|
||||
|
|
||||
|
## sdk 日志 |
||||
|
|
||||
|
|
||||
|
## nginx/apache 日志 |
||||
|
> 涉及到 异步通知、同步通知 的问题,请贴出来 |
||||
@ -0,0 +1,17 @@ |
|||||
|
--- |
||||
|
name: Feature request |
||||
|
about: Suggest an idea for this project |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Is your feature request related to a problem? Please describe.** |
||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] |
||||
|
|
||||
|
**Describe the solution you'd like** |
||||
|
A clear and concise description of what you want to happen. |
||||
|
|
||||
|
**Describe alternatives you've considered** |
||||
|
A clear and concise description of any alternative solutions or features you've considered. |
||||
|
|
||||
|
**Additional context** |
||||
|
Add any other context or screenshots about the feature request here. |
||||
@ -0,0 +1,4 @@ |
|||||
|
/vendor |
||||
|
composer.lock |
||||
|
*.DS_Store |
||||
|
.idea |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue