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