diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..8bdda39 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,9 @@ +languages: + Ruby: true + JavaScript: true + PHP: true + Python: true +exclude_paths: + - examples/* + - vendor/* + - tests/* \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5793a5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/.idea/ +/build/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d04d149 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - hhvm + - nightly +install: composer install --dev +before_script: + - git config --global user.email "dev.pmill@gmail.com" + - git config --global user.name "pmill" + +after_script: + - php vendor/bin/test-reporter \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b2c8553 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 pmill + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1025960 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +php-chat +============ + +[![Build Status](https://secure.travis-ci.org/pmill/php-chat.svg?branch=master)](http://travis-ci.org/pmill/php-chat) [![Code Climate](https://codeclimate.com/github/pmill/php-chat/badges/gpa.svg)](https://codeclimate.com/github/pmill/php-chat) [![Test Coverage](https://codeclimate.com/github/pmill/php-chat/badges/coverage.svg)](https://codeclimate.com/github/pmill/php-chat/coverage) [![Test Coverage](https://scrutinizer-ci.com/g/pmill/php-chat/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/pmill/php-chat/) + +Introduction +------------ + +A multi-user multi-room ratchet server. + +Requirements +------------ + +This library package requires PHP 5.4 or later. + +Installation +------------ + +### Installing via Composer + +The recommended way to install php-chat is through +[Composer](http://getcomposer.org). + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php +``` + +Next, run the Composer command to install the latest version of php-chat: + +```bash +composer.phar require pmill/php-chat +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` + +Usage +----- + +An example is provided in the example/ directory. Start the server with the command: + + php example/server.php + +An example HTML client interface is located at example/client.html. You will need to update the chatUrl variable in +example/chat.js with the host name (or ip address) of the server you ran the previous command on. + + var chatUrl = 'ws://your-host-name:9911'; + +Version History +--------------- + +0.1.0 (08/07/2015) + +* First public release of php-chat + + +Copyright +--------- + +php-chat +Copyright (c) 2015 pmill (dev.pmill@gmail.com) +All rights reserved. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..47aa077 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "pmill/php-chat", + "description": " A multi-user multi-room ratchet server ", + "keywords": ["php", "web socket", "ratchet", "chat"], + "homepage": "https://github.com/pmill/php-chat", + "time": "2015-07-08", + "license": "MIT", + "authors": [ + { + "name": "pmill", + "email": "dev.pmill@gmail.com", + "homepage": "https://github.com/pmill", + "role": "Author" + } + ], + "autoload": { + "psr-0": { + "pmill\\Chat": "src/" + } + }, + "require": { + "php": ">=5.4.0", + "cboden/ratchet": "0.3.*" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "codeclimate/php-test-reporter": "dev-master" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a0865fc --- /dev/null +++ b/composer.lock @@ -0,0 +1,1845 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "7ef843af175e6a97508591e3a387d948", + "packages": [ + { + "name": "cboden/ratchet", + "version": "v0.3.3", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/Ratchet.git", + "reference": "6b247c05251b4b5cbd14243366b649f99a0e5681" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/6b247c05251b4b5cbd14243366b649f99a0e5681", + "reference": "6b247c05251b4b5cbd14243366b649f99a0e5681", + "shasum": "" + }, + "require": { + "guzzle/http": "^3.6", + "php": ">=5.3.9", + "react/socket": "^0.3 || ^0.4", + "symfony/http-foundation": "^2.2", + "symfony/routing": "^2.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\": "src/Ratchet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + } + ], + "description": "PHP WebSocket library", + "homepage": "http://socketo.me", + "keywords": [ + "Ratchet", + "WebSockets", + "server", + "sockets" + ], + "time": "2015-05-27 12:51:05" + }, + { + "name": "evenement/evenement", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "f6e843799fd4f4184d54d8fc7b5b3551c9fa803e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/f6e843799fd4f4184d54d8fc7b5b3551c9fa803e", + "reference": "f6e843799fd4f4184d54d8fc7b5b3551c9fa803e", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-0": { + "Evenement": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch", + "homepage": "http://wiedler.ch/igor/" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "time": "2012-11-02 14:49:47" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-03-18 18:23:50" + }, + { + "name": "react/event-loop", + "version": "v0.4.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "18c5297087ca01de85518e2b55078f444144aa1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/18c5297087ca01de85518e2b55078f444144aa1b", + "reference": "18c5297087ca01de85518e2b55078f444144aa1b", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "suggest": { + "ext-event": "~1.0", + "ext-libev": "*", + "ext-libevent": ">=0.1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "React\\EventLoop\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Event loop abstraction layer that libraries can use for evented I/O.", + "keywords": [ + "event-loop" + ], + "time": "2014-02-26 17:36:58" + }, + { + "name": "react/socket", + "version": "v0.4.2", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "a6acf405ca53fc6cfbfe7c77778ededff46aa7cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/a6acf405ca53fc6cfbfe7c77778ededff46aa7cc", + "reference": "a6acf405ca53fc6cfbfe7c77778ededff46aa7cc", + "shasum": "" + }, + "require": { + "evenement/evenement": "~2.0", + "php": ">=5.4.0", + "react/event-loop": "0.4.*", + "react/stream": "0.4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "React\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Library for building an evented socket server.", + "keywords": [ + "Socket" + ], + "time": "2014-05-25 17:02:16" + }, + { + "name": "react/stream", + "version": "v0.4.2", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "acc7a5fec02e0aea674560e1d13c40ed0c8c5465" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/acc7a5fec02e0aea674560e1d13c40ed0c8c5465", + "reference": "acc7a5fec02e0aea674560e1d13c40ed0c8c5465", + "shasum": "" + }, + "require": { + "evenement/evenement": "~2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "react/event-loop": "0.4.*", + "react/promise": "~2.0" + }, + "suggest": { + "react/event-loop": "0.4.*", + "react/promise": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5-dev" + } + }, + "autoload": { + "psr-4": { + "React\\Stream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Basic readable and writable stream interfaces that support piping.", + "keywords": [ + "pipe", + "stream" + ], + "time": "2014-09-10 03:32:31" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher.git", + "reference": "be3c5ff8d503c46768aeb78ce6333051aa6f26d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/be3c5ff8d503c46768aeb78ce6333051aa6f26d9", + "reference": "be3c5ff8d503c46768aeb78ce6333051aa6f26d9", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5", + "symfony/dependency-injection": "~2.6", + "symfony/expression-language": "~2.6", + "symfony/phpunit-bridge": "~2.7", + "symfony/stopwatch": "~2.3" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2015-06-08 09:37:21" + }, + { + "name": "symfony/http-foundation", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpFoundation.git", + "reference": "4f363c426b0ced57e3d14460022feb63937980ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/4f363c426b0ced57e3d14460022feb63937980ff", + "reference": "4f363c426b0ced57e3d14460022feb63937980ff", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/expression-language": "~2.4", + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2015-06-10 15:30:22" + }, + { + "name": "symfony/routing", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing.git", + "reference": "5581be29185b8fb802398904555f70da62f6d50d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Routing/zipball/5581be29185b8fb802398904555f70da62f6d50d", + "reference": "5581be29185b8fb802398904555f70da62f6d50d", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.7", + "symfony/expression-language": "~2.4", + "symfony/http-foundation": "~2.3", + "symfony/phpunit-bridge": "~2.7", + "symfony/yaml": "~2.0,>=2.0.5" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2015-06-11 17:20:40" + } + ], + "packages-dev": [ + { + "name": "codeclimate/php-test-reporter", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/codeclimate/php-test-reporter.git", + "reference": "418ae782307841ac50fe26daa4cfe04520b0de9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeclimate/php-test-reporter/zipball/418ae782307841ac50fe26daa4cfe04520b0de9c", + "reference": "418ae782307841ac50fe26daa4cfe04520b0de9c", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3", + "satooshi/php-coveralls": "0.6.*", + "symfony/console": ">=2.0" + }, + "require-dev": { + "ext-xdebug": "*", + "phpunit/phpunit": "3.7.*@stable" + }, + "bin": [ + "composer/bin/test-reporter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "CodeClimate\\Component": "src/", + "CodeClimate\\Bundle": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Code Climate", + "email": "hello@codeclimate.com", + "homepage": "https://codeclimate.com" + } + ], + "description": "PHP client for reporting test coverage to Code Climate", + "homepage": "https://github.com/codeclimate/php-test-reporter", + "keywords": [ + "codeclimate", + "coverage" + ], + "time": "2015-04-18 14:43:54" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpspec/prophecy", + "version": "v1.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373", + "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "phpdocumentor/reflection-docblock": "~2.0", + "sebastian/comparator": "~1.1" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2015-04-27 22:15:08" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.1.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "07e27765596d72c378a6103e80da5d84e802f1e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/07e27765596d72c378a6103e80da5d84e802f1e4", + "reference": "07e27765596d72c378a6103e80da5d84e802f1e4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "~1.0", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-06-30 06:52:35" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb", + "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-04-02 05:19:05" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "83fe1bdc5d47658b727595c14da140da92b3d66d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/83fe1bdc5d47658b727595c14da140da92b3d66d", + "reference": "83fe1bdc5d47658b727595c14da140da92b3d66d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2015-06-13 07:35:30" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/7a9b0969488c3c54fd62b4d504b3ec758fd005d9", + "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-06-19 03:43:16" + }, + { + "name": "phpunit/phpunit", + "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0ebabb4cda7d066be8391dfdbaf57fe70ac9a99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0ebabb4cda7d066be8391dfdbaf57fe70ac9a99b", + "reference": "0ebabb4cda7d066be8391dfdbaf57fe70ac9a99b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "~1.3,>=1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": ">=1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.2", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2015-06-30 06:53:57" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "1c330b1b6e1ea8fd15f2fbea46770576e366855c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/1c330b1b6e1ea8fd15f2fbea46770576e366855c", + "reference": "1c330b1b6e1ea8fd15f2fbea46770576e366855c", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "~1.0,>=1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-07-04 05:41:32" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "satooshi/php-coveralls", + "version": "v0.6.1", + "source": { + "type": "git", + "url": "https://github.com/satooshi/php-coveralls.git", + "reference": "dd0df95bd37a7cf5c5c50304dfe260ffe4b50760" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/dd0df95bd37a7cf5c5c50304dfe260ffe4b50760", + "reference": "dd0df95bd37a7cf5c5c50304dfe260ffe4b50760", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": ">=3.0", + "php": ">=5.3", + "psr/log": "1.0.0", + "symfony/config": ">=2.0", + "symfony/console": ">=2.0", + "symfony/stopwatch": ">=2.2", + "symfony/yaml": ">=2.0" + }, + "require-dev": { + "apigen/apigen": "2.8.*@stable", + "pdepend/pdepend": "dev-master", + "phpmd/phpmd": "dev-master", + "phpunit/php-invoker": ">=1.1.0,<1.2.0", + "phpunit/phpunit": "3.7.*@stable", + "sebastian/finder-facade": "dev-master", + "sebastian/phpcpd": "1.4.*@stable", + "squizlabs/php_codesniffer": "1.4.*@stable", + "theseer/fdomdocument": "dev-master" + }, + "bin": [ + "composer/bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-0": { + "Contrib\\Component": "src/", + "Contrib\\Bundle": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/satooshi/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2013-05-04 08:07:33" + }, + { + "name": "sebastian/comparator", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "1dd8869519a225f7f2b9eb663e225298fade819e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dd8869519a225f7f2b9eb663e225298fade819e", + "reference": "1dd8869519a225f7f2b9eb663e225298fade819e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-01-29 16:28:08" + }, + { + "name": "sebastian/diff", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3", + "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "http://www.github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-02-22 15:13:53" + }, + { + "name": "sebastian/environment", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5a8c7d31914337b69923db26c4221b81ff5a196e", + "reference": "5a8c7d31914337b69923db26c4221b81ff5a196e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2015-01-01 10:01:08" + }, + { + "name": "sebastian/exporter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "84839970d05254c73cde183a721c7af13aede943" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/84839970d05254c73cde183a721c7af13aede943", + "reference": "84839970d05254c73cde183a721c7af13aede943", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2015-01-27 07:23:06" + }, + { + "name": "sebastian/global-state", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01", + "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2014-10-06 09:23:50" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "3989662bbb30a29d20d9faa04a846af79b276252" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/3989662bbb30a29d20d9faa04a846af79b276252", + "reference": "3989662bbb30a29d20d9faa04a846af79b276252", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-01-24 09:48:32" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "symfony/config", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/Config.git", + "reference": "58ded81f1f582a87c528ef3dae9a859f78b5f374" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Config/zipball/58ded81f1f582a87c528ef3dae9a859f78b5f374", + "reference": "58ded81f1f582a87c528ef3dae9a859f78b5f374", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2015-06-11 14:06:56" + }, + { + "name": "symfony/console", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console.git", + "reference": "564398bc1f33faf92fc2ec86859983d30eb81806" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/564398bc1f33faf92fc2ec86859983d30eb81806", + "reference": "564398bc1f33faf92fc2ec86859983d30eb81806", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.1" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2015-06-10 15:30:22" + }, + { + "name": "symfony/filesystem", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/Filesystem.git", + "reference": "a0d43eb3e17d4f4c6990289805a488a0482a07f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/a0d43eb3e17d4f4c6990289805a488a0482a07f3", + "reference": "a0d43eb3e17d4f4c6990289805a488a0482a07f3", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2015-06-08 09:37:21" + }, + { + "name": "symfony/stopwatch", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/Stopwatch.git", + "reference": "c653f1985f6c2b7dbffd04d48b9c0a96aaef814b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/c653f1985f6c2b7dbffd04d48b9c0a96aaef814b", + "reference": "c653f1985f6c2b7dbffd04d48b9c0a96aaef814b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2015-06-04 20:11:48" + }, + { + "name": "symfony/yaml", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "9808e75c609a14f6db02f70fccf4ca4aab53c160" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/9808e75c609a14f6db02f70fccf4ca4aab53c160", + "reference": "9808e75c609a14f6db02f70fccf4ca4aab53c160", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2015-06-10 15:30:22" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "codeclimate/php-test-reporter": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [] +} diff --git a/example/chat.js b/example/chat.js new file mode 100644 index 0000000..a061b0e --- /dev/null +++ b/example/chat.js @@ -0,0 +1,73 @@ +// Change localhost to the name or ip address of the host running the chat server +var chatUrl = 'ws://localhost:9911'; + +function displayChatMessage(from, message) { + var node = document.createElement("LI"); + + if (from) { + var nameNode = document.createElement("STRONG"); + var nameTextNode = document.createTextNode(from); + nameNode.appendChild(nameTextNode); + node.appendChild(nameNode); + } + + var messageTextNode = document.createTextNode(message); + node.appendChild(messageTextNode); + + document.getElementById("messageList").appendChild(node); +} + +var conn; + +function connectToChat() { + conn = new WebSocket(chatUrl); + + conn.onopen = function() { + document.getElementById('connectFormDialog').style.display = 'none'; + document.getElementById('messageDialog').style.display = 'block'; + + var params = { + 'roomId': document.getElementsByName("room.name")[0].value, + 'userName': document.getElementsByName("user.name")[0].value, + 'action': 'connect' + }; + console.log(params); + conn.send(JSON.stringify(params)); + }; + + conn.onmessage = function(e) { + console.log(e); + var data = JSON.parse(e.data); + + if (data.hasOwnProperty('message') && data.hasOwnProperty('from')) { + displayChatMessage(data.from.name, data.message); + } + else if (data.hasOwnProperty('message')) { + displayChatMessage(null, data.message); + } + else if (data.hasOwnProperty('type') && data.type == 'list-users' && data.hasOwnProperty('clients')) { + displayChatMessage(null, 'There are '+data.clients.length+' users connected'); + } + }; + + conn.onerror = function(e) { + console.log(e); + }; + + return false; +} + +function sendChatMessage() { + var d = new Date(); + var params = { + 'message': document.getElementsByName("message")[0].value, + 'roomId': document.getElementsByName("room.name")[0].value, + 'userName': document.getElementsByName("user.name")[0].value, + 'action': 'message', + 'timestamp': d.getTime()/1000 + }; + conn.send(JSON.stringify(params)); + + document.getElementsByName("message")[0].value = ''; + return false; +} \ No newline at end of file diff --git a/example/client.html b/example/client.html new file mode 100644 index 0000000..c494b65 --- /dev/null +++ b/example/client.html @@ -0,0 +1,38 @@ + + + + + php-chat | Example | Client 1 + + + + +
+
+

+ +

+

+ +

+

+ +

+
+
+
+ + +
+

+ +

+

+ +

+
+
+ + + + \ No newline at end of file diff --git a/example/reset.css b/example/reset.css new file mode 100644 index 0000000..5b3efe4 --- /dev/null +++ b/example/reset.css @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/example/server.php b/example/server.php new file mode 100644 index 0000000..6788bb5 --- /dev/null +++ b/example/server.php @@ -0,0 +1,5 @@ + + + + + + + + ./tests + + + + + ./vendor + + + \ No newline at end of file diff --git a/src/pmill/Chat/ConnectedClient.php b/src/pmill/Chat/ConnectedClient.php new file mode 100644 index 0000000..a71bad2 --- /dev/null +++ b/src/pmill/Chat/ConnectedClient.php @@ -0,0 +1,72 @@ +resourceId; + } + + /** + * @param mixed $resourceId + */ + public function setResourceId($resourceId) + { + $this->resourceId = $resourceId; + } + + /** + * @return ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * @param ConnectionInterface $connection + */ + public function setConnection($connection) + { + $this->connection = $connection; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + +} \ No newline at end of file diff --git a/src/pmill/Chat/Exception/ConnectedClientNotFoundException.php b/src/pmill/Chat/Exception/ConnectedClientNotFoundException.php new file mode 100644 index 0000000..16de368 --- /dev/null +++ b/src/pmill/Chat/Exception/ConnectedClientNotFoundException.php @@ -0,0 +1,6 @@ +run(); + return $server; + } + + /** + * @var array + */ + protected $rooms; + + /** + * @var array|ConnectedClient[] + */ + protected $clients; + + /** + * @var string + */ + protected $userConnectedMessageTemplate = '%s has connected'; + + /** + * @var string + */ + protected $userDisconnectedMessageTemplate = '%s has left'; + + /** + * @var string + */ + protected $userWelcomeMessageTemplate = 'Welcome %s!'; + + public function __construct() + { + $this->rooms = array(); + $this->clients = array(); + } + + /** + * @param ConnectionInterface $conn + */ + public function onOpen(ConnectionInterface $conn) + { + + } + + /** + * @param ConnectionInterface $conn + * @param string $msg + * @throws ConnectedClientNotFoundException + * @throws InvalidActionException + * @throws MissingActionException + */ + public function onMessage(ConnectionInterface $conn, $msg) + { + echo "Packet received: ".$msg.PHP_EOL; + $msg = json_decode($msg, true); + $roomId = $this->makeRoom($msg['roomId']); + + if (!isset($msg['action'])) { + throw new MissingActionException('No action specified'); + } + + switch ($msg['action']) { + case self::ACTION_USER_CONNECTED: + $userName = $msg['userName']; + $client = $this->createClient($conn, $userName); + $this->connectUserToRoom($client, $roomId); + $this->sendUserConnectedMessage($client, $roomId); + $this->sendUserWelcomeMessage($client, $roomId); + $this->sendListUsersMessage($client, $roomId); + break; + case self::ACTION_LIST_USERS: + $client = $this->findClient($conn); + $this->sendListUsersMessage($client, $roomId); + break; + case self::ACTION_MESSAGE_RECEIVED: + $msg['timestamp'] = isset($msg['timestamp']) ? $msg['timestamp'] : time(); + $client = $this->findClient($conn); + $this->sendMessage($client, $roomId, $msg['message'], $msg['timestamp']); + break; + default: throw new InvalidActionException('Invalid action: '.$msg['action']); + } + } + + /** + * @param ConnectionInterface $conn + */ + public function onClose(ConnectionInterface $conn) + { + $client = $this->findClient($conn); + + unset($this->clients[$client->getResourceId()]); + foreach ($this->rooms AS $roomId=>$connectedClients) { + if (isset($connectedClients[$client->getResourceId()])) { + $clientRoomId = $roomId; + unset($this->rooms[$roomId][$client->getResourceId()]); + } + } + + if (isset($clientRoomId)) { + $this->sendUserDisconnectedMessage($client, $clientRoomId); + } + } + + /** + * @param ConnectionInterface $conn + * @param \Exception $e + */ + public function onError(ConnectionInterface $conn, \Exception $e) + { + $conn->close(); + } + + /** + * @return array + */ + public function getRooms() + { + return $this->rooms; + } + + /** + * @param array $rooms + */ + public function setRooms($rooms) + { + $this->rooms = $rooms; + } + + /** + * @return array|ConnectedClient[] + */ + public function getClients() + { + return $this->clients; + } + + /** + * @param array|ConnectedClient[] $clients + */ + public function setClients($clients) + { + $this->clients = $clients; + } + + /** + * @param ConnectionInterface $conn + * @param $name + * @return ConnectedClient + */ + protected function createClient(ConnectionInterface $conn, $name) + { + $client = new ConnectedClient; + $client->setResourceId($conn->resourceId); + $client->setConnection($conn); + $client->setName($name); + + return $client; + } + + /** + * @param ConnectionInterface $conn + * @return ConnectedClient + * @throws ConnectedClientNotFoundException + */ + protected function findClient(ConnectionInterface $conn) + { + if (isset($this->clients[$conn->resourceId])) { + return $this->clients[$conn->resourceId]; + } + + throw new ConnectedClientNotFoundException($conn->resourceId); + } + + /** + * @param ConnectedClient $client + * @param $roomId + * @param $message + * @param $timestamp + */ + protected function sendMessage(ConnectedClient $client, $roomId, $message, $timestamp) + { + $dataPacket = array( + 'type'=>self::PACKET_TYPE_MESSAGE, + 'from'=>array( + 'name'=>$client->getName(), + ), + 'timestamp'=>$timestamp, + 'message'=>$message, + ); + + $clients = $this->findRoomClients($roomId); + $this->sendDataToClients($clients, $dataPacket); + } + + /** + * @param ConnectedClient $client + * @param $roomId + */ + protected function sendUserConnectedMessage(ConnectedClient $client, $roomId) + { + $dataPacket = array( + 'type'=>self::PACKET_TYPE_USER_CONNECTED, + 'timestamp'=>time(), + 'message'=>vsprintf($this->userConnectedMessageTemplate, array($client->getName())), + ); + + $clients = $this->findRoomClients($roomId); + unset($clients[$client->getResourceId()]); + $this->sendDataToClients($clients, $dataPacket); + } + + /** + * @param ConnectedClient $client + * @param $roomId + */ + protected function sendUserWelcomeMessage(ConnectedClient $client, $roomId) + { + $dataPacket = array( + 'type'=>self::PACKET_TYPE_USER_CONNECTED, + 'timestamp'=>time(), + 'message'=>vsprintf($this->userWelcomeMessageTemplate, array($client->getName())), + ); + + $this->sendData($client, $dataPacket); + } + + /** + * @param ConnectedClient $client + * @param $roomId + */ + protected function sendUserDisconnectedMessage(ConnectedClient $client, $roomId) + { + $dataPacket = array( + 'type'=>self::PACKET_TYPE_USER_DISCONNECTED, + 'timestamp'=>time(), + 'message'=>vsprintf($this->userDisconnectedMessageTemplate, array($client->getName())), + ); + + $clients = $this->findRoomClients($roomId); + $this->sendDataToClients($clients, $dataPacket); + } + + /** + * @param ConnectedClient $client + * @param $roomId + */ + protected function sendListUsersMessage(ConnectedClient $client, $roomId) + { + $clients = array(); + foreach ($this->findRoomClients($roomId) AS $roomClient) { + $clients[] = array( + 'name'=>$roomClient->getName(), + ); + } + + $dataPacket = array( + 'type'=>self::PACKET_TYPE_USER_LIST, + 'timestamp'=>time(), + 'clients'=>$clients, + ); + + $this->sendData($client, $dataPacket); + } + + /** + * @param ConnectedClient $client + * @param $roomId + */ + protected function connectUserToRoom(ConnectedClient $client, $roomId) + { + $this->rooms[$roomId][$client->getResourceId()] = $client; + $this->clients[$client->getResourceId()] = $client; + } + + /** + * @param $roomId + * @return array|ConnectedClient[] + */ + protected function findRoomClients($roomId) + { + return $this->rooms[$roomId]; + } + + /** + * @param ConnectedClient $client + * @param array $packet + */ + protected function sendData(ConnectedClient $client, array $packet) + { + $client->getConnection()->send(json_encode($packet)); + } + + /** + * @param array|ConnectedClient[] $clients + * @param array $packet + */ + protected function sendDataToClients(array $clients, array $packet) + { + foreach ($clients AS $client) { + $this->sendData($client, $packet); + } + } + + /** + * @param $roomId + * @return mixed + */ + protected function makeRoom($roomId) + { + if (!isset($this->rooms[$roomId])) { + $this->rooms[$roomId] = array(); + } + + return $roomId; + } + +} \ No newline at end of file diff --git a/tests/ConnectedClientTest.php b/tests/ConnectedClientTest.php new file mode 100644 index 0000000..f4e2f4a --- /dev/null +++ b/tests/ConnectedClientTest.php @@ -0,0 +1,32 @@ +setResourceId(1); + + $this->assertEquals(1, $client->getResourceId()); + } + + public function testSetName() + { + $client = new \pmill\Chat\ConnectedClient; + $client->setName('name'); + + $this->assertEquals('name', $client->getName()); + } + + public function testSetConnection() + { + $connection = $this->getMock('Ratchet\ConnectionInterface'); + + $client = new \pmill\Chat\ConnectedClient; + $client->setConnection($connection); + + $this->assertEquals($connection, $client->getConnection()); + } + +} \ No newline at end of file diff --git a/tests/MultiRoomServerTest.php b/tests/MultiRoomServerTest.php new file mode 100644 index 0000000..bdc20b6 --- /dev/null +++ b/tests/MultiRoomServerTest.php @@ -0,0 +1,251 @@ +connections = array(); + for ($i=0; $i<5; $i++) { + $connection = $this->getMockBuilder('Ratchet\ConnectionInterface') + ->setMethods(array('send','close')) + ->getMock(); + $connection->resourceId = 'connection'.$i; + + $this->connections[] = $connection; + } + } + + /** + * @expectedException pmill\Chat\Exception\MissingActionException + */ + public function testMissingAction() + { + $packet = array( + 'roomId'=>'room1', + 'userName'=>'User 1', + ); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode($packet)); + } + + /** + * @expectedException pmill\Chat\Exception\InvalidActionException + */ + public function testInvalidAction() + { + $packet = array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>'invalid-action', + ); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode($packet)); + } + + public function testCreateRoom() + { + $packet = array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode($packet)); + + $rooms = $server->getRooms(); + $this->assertArrayHasKey('room1', $rooms); + } + + public function testJoinExistingRoom() + { + $packet = array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ); + + $server = new \pmill\Chat\MultiRoomServer; + $server->setRooms(array('room1'=>array())); + $server->onMessage($this->connections[0], json_encode($packet)); + + $rooms = $server->getRooms(); + $this->assertArrayHasKey('room1', $rooms); + $this->assertArrayHasKey('connection0', $rooms['room1']); + } + + public function testCreateClient() + { + $packet = array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode($packet)); + + $clients = $server->getClients(); + $this->assertArrayHasKey('connection0', $clients); + $this->assertInstanceOf('\pmill\Chat\ConnectedClient', $clients['connection0']); + } + + public function testUserWelcomeMessage() + { + $this->connections[0] + ->expects($this->at(0)) + ->method('send') + ->with($this->callback(function($packet){ + $packet = json_decode($packet, true); + return $packet['message'] == 'Welcome User 1!'; + })); + + $packet = array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode($packet)); + } + + public function testUserConnectedListClientsMessage() + { + $this->connections[0] + ->expects($this->at(1)) + ->method('send') + ->with($this->callback(function($packet){ + $packet = json_decode($packet, true); + return $packet['clients'][0]['name'] == 'User 1'; + })); + + $packet = array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode($packet)); + } + + public function testOtherUserConnectedMessage() + { + $this->connections[0] + ->expects($this->at(2)) + ->method('send') + ->with($this->callback(function($packet){ + $packet = json_decode($packet, true); + return $packet['message'] == 'User 2 has connected'; + })); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode(array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ))); + + $server->onMessage($this->connections[1], json_encode(array( + 'roomId'=>'room1', + 'userName'=>'User 2', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ))); + } + + public function testListClientsMessage() + { + $this->connections[0] + ->expects($this->at(2)) + ->method('send') + ->with($this->callback(function($packet){ + $packet = json_decode($packet, true); + return $packet['clients'][0]['name'] == 'User 1'; + })); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode(array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ))); + + $server->onMessage($this->connections[0], json_encode(array( + 'roomId'=>'room1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_LIST_USERS, + ))); + } + + public function testSendMessage() + { + $this->connections[0] + ->expects($this->at(2)) + ->method('send') + ->with($this->callback(function($packet){ + $packet = json_decode($packet, true); + return $packet['message'] == 'test message body'; + })); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode(array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ))); + + $server->onMessage($this->connections[0], json_encode(array( + 'roomId'=>'room1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_MESSAGE_RECEIVED, + 'timestamp'=>time(), + 'message'=>'test message body', + ))); + } + + public function testDisconnectedClientIsRemovedFromRoom() + { + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode(array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ))); + + $server->onClose($this->connections[0]); + + $rooms = $server->getRooms(); + $this->assertArrayNotHasKey('connection0', $rooms['room1']); + } + + public function testDisconnectedClientMessageSent() + { + $this->connections[0] + ->expects($this->at(3)) + ->method('send') + ->with($this->callback(function($packet){ + $packet = json_decode($packet, true); + return $packet['message'] == 'User 2 has left'; + })); + + $server = new \pmill\Chat\MultiRoomServer; + $server->onMessage($this->connections[0], json_encode(array( + 'roomId'=>'room1', + 'userName'=>'User 1', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ))); + + $server->onMessage($this->connections[1], json_encode(array( + 'roomId'=>'room1', + 'userName'=>'User 2', + 'action'=>\pmill\Chat\MultiRoomServer::ACTION_USER_CONNECTED, + ))); + + $server->onClose($this->connections[1]); + } + +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..5b74df6 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,2 @@ +