├── .gitignore ├── tests └── units │ └── Sly │ └── NotificationPusher │ ├── Resources │ └── apns-certificate.pem │ ├── Model │ ├── Device.php │ ├── Message.php │ ├── BaseOptionedModel.php │ ├── BaseParameteredModel.php │ └── Push.php │ ├── Adapter │ ├── BaseAdapter.php │ ├── Gcm.php │ └── Apns.php │ └── PushManager.php ├── .atoum.bootstrap.php ├── docker-compose.yml ├── src └── Sly │ └── NotificationPusher │ ├── Model │ ├── ApnsMessage.php │ ├── GcmMessage.php │ ├── DeviceInterface.php │ ├── MessageInterface.php │ ├── ResponseInterface.php │ ├── Message.php │ ├── Device.php │ ├── BaseOptionedModel.php │ ├── BaseParameteredModel.php │ ├── Response.php │ ├── PushInterface.php │ └── Push.php │ ├── Adapter │ ├── FeedbackAdapterInterface.php │ ├── ApnsAPI.php │ ├── AdapterInterface.php │ ├── BaseAdapter.php │ ├── Gcm.php │ └── Apns.php │ ├── NotificationPusher.php │ ├── Exception │ ├── ExceptionInterface.php │ ├── PushException.php │ ├── AdapterException.php │ ├── InvalidException.php │ └── RuntimeException.php │ ├── AbstractPushService.php │ ├── Console │ ├── Application.php │ └── Command │ │ └── PushCommand.php │ ├── Collection │ ├── PushCollection.php │ ├── MessageCollection.php │ ├── ResponseCollection.php │ ├── DeviceCollection.php │ └── AbstractCollection.php │ ├── PushManager.php │ ├── GcmPushService.php │ └── ApnsPushService.php ├── docker └── php-cli │ └── Dockerfile ├── .atoum.php ├── CONTRIBUTING.md ├── np ├── Makefile ├── .github └── FUNDING.yml ├── .travis.yml ├── doc ├── installation.md ├── push-from-cli.md ├── create-an-adapter.md ├── gcm-fcm-adapter.md ├── getting-started.md ├── facades.md └── apns-adapter.md ├── LICENSE ├── composer.json ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor/ 3 | web/ 4 | .idea/ -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Resources/apns-certificate.pem: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.atoum.bootstrap.php: -------------------------------------------------------------------------------- 1 | addTestsFromDirectory(__DIR__ . '/tests/units'); 4 | 5 | $script->noCodeCoverageForNamespaces('mageekguy', 'Symfony'); 6 | $script->bootstrapFile(__DIR__ . DIRECTORY_SEPARATOR . '.atoum.bootstrap.php'); 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | If you would like to contribute, please follow the standards defined in the [PSR-1][1], [PSR-2][2] and [PSR-4][4] documents. 2 | 3 | [1]: http://www.php-fig.org/psr/psr-1/ 4 | [2]: http://www.php-fig.org/psr/psr-2/ 5 | [4]: http://www.php-fig.org/psr/psr-4/ 6 | -------------------------------------------------------------------------------- /np: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 14 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Adapter/FeedbackAdapterInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface FeedbackAdapterInterface 15 | { 16 | public function getFeedback(); 17 | } 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | php_bash: 2 | docker-compose -f docker-compose.yml exec php-cli bash -l -c "export TERM=xterm; export COLUMNS=`tput cols`; export LINES=`tput lines`; exec bash -l" 3 | 4 | atoum: 5 | docker-compose -f docker-compose.yml exec php-cli bash -c "./bin/atoum" 6 | 7 | up: 8 | docker-compose -f docker-compose.yml up -d 9 | 10 | down: 11 | docker-compose -f docker-compose.yml down --rmi local --volumes 12 | 13 | restart: down up 14 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/NotificationPusher.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher; 13 | 14 | class NotificationPusher 15 | { 16 | const VERSION = '2.0'; 17 | } 18 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Exception; 13 | 14 | /** 15 | * @author Cédric Dugat 16 | */ 17 | interface ExceptionInterface 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Hey there! 2 | # 3 | # I plan to develop a new version of NotificationPusher this year, 4 | # including many new features, and a Dockerized standalone microservice (K8s ready). 🙌🕺 5 | # The current version of this lib has been writen in 2013. 6 | # 7 | # Of course, this new version will respect all current PHP standards (DDD powered). 8 | # 9 | # Feel free to participate by giving a small contribution. 10 | # If donations allow, we'll push the project further (hosted mode, SaaS service and more). 11 | 12 | custom: ["https://paypal.me/ph3nol"] 13 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/GcmMessage.php: -------------------------------------------------------------------------------- 1 | notificationData; 19 | } 20 | 21 | /** 22 | * @param array $notificationData 23 | */ 24 | public function setNotificationData($notificationData) 25 | { 26 | $this->notificationData = $notificationData; 27 | } 28 | } -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Exception/PushException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Exception; 13 | 14 | /** 15 | * @uses \RuntimeException 16 | * @uses \Sly\NotificationPusher\Exception\ExceptionInterface 17 | * 18 | * @author Cédric Dugat 19 | */ 20 | class PushException extends \RuntimeException implements ExceptionInterface 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Exception/AdapterException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Exception; 13 | 14 | /** 15 | * @uses \RuntimeException 16 | * @uses \Sly\NotificationPusher\Exception\ExceptionInterface 17 | * 18 | * @author Cédric Dugat 19 | */ 20 | class AdapterException extends \RuntimeException implements ExceptionInterface 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Exception/InvalidException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Exception; 13 | 14 | /** 15 | * @uses \RuntimeException 16 | * @uses \Sly\NotificationPusher\Exception\ExceptionInterface 17 | * 18 | * @author Cédric Dugat 19 | */ 20 | class InvalidException extends \RuntimeException implements ExceptionInterface 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Exception; 13 | 14 | /** 15 | * @uses \RuntimeException 16 | * @uses \Sly\NotificationPusher\Exception\ExceptionInterface 17 | * 18 | * @author Cédric Dugat 19 | */ 20 | class RuntimeException extends \RuntimeException implements ExceptionInterface 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/DeviceInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Model; 13 | 14 | /** 15 | * @author Cédric Dugat 16 | */ 17 | interface DeviceInterface 18 | { 19 | /** 20 | * @return string 21 | */ 22 | public function getToken(); 23 | 24 | /** 25 | * @param string $token Token 26 | * 27 | * @return DeviceInterface 28 | */ 29 | public function setToken($token); 30 | } 31 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Model; 13 | 14 | /** 15 | * @author Cédric Dugat 16 | */ 17 | interface MessageInterface 18 | { 19 | /** 20 | * @return string 21 | */ 22 | public function getText(); 23 | 24 | /** 25 | * @param string $text Text 26 | * 27 | * @return MessageInterface 28 | */ 29 | public function setText($text); 30 | } 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | - 7.2 8 | - 7.3 9 | - 7.4 10 | 11 | env: 12 | global: 13 | - XDEBUG_MODE=coverage 14 | 15 | before_script: 16 | - composer self-update || true 17 | - composer install --dev --prefer-source 18 | - mkdir -p web/code-coverage data 19 | - wget https://scrutinizer-ci.com/ocular.phar 20 | 21 | after_script: 22 | - php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover 23 | 24 | script: 25 | - bin/atoum 26 | 27 | notifications: 28 | email: 29 | recipients: 30 | - cedric@dugat.me 31 | - seyferseed@gmail.com 32 | on_success: change 33 | on_failure: change 34 | -------------------------------------------------------------------------------- /doc/installation.md: -------------------------------------------------------------------------------- 1 | # NotificationPusher - Documentation 2 | 3 | ## Installation 4 | 5 | Use [Composer](http://getcomposer.org) to install this library. 6 | 7 | Run `composer require sly/notification-pusher` to install the latest version. 8 | 9 | ## Documentation index 10 | 11 | * Installation 12 | * [Getting started](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/getting-started.md) 13 | * [APNS adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/apns-adapter.md) 14 | * [GCM (FCM) adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/gcm-fcm-adapter.md) 15 | * [Create an adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/create-an-adapter.md) 16 | * [Push from CLI](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/push-from-cli.md) 17 | * [Facades](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/facades.md) -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/AbstractPushService.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class AbstractPushService 18 | { 19 | /** 20 | * @var string 21 | */ 22 | protected $environment; 23 | 24 | /** 25 | * @var ResponseInterface 26 | */ 27 | protected $response; 28 | 29 | /** 30 | * @param string $environment 31 | */ 32 | public function __construct($environment = PushManager::ENVIRONMENT_DEV) 33 | { 34 | $this->environment = $environment; 35 | } 36 | 37 | /** 38 | * @return ResponseInterface 39 | */ 40 | public function getResponse() 41 | { 42 | return $this->response; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Console/Application.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Console; 13 | 14 | use Sly\NotificationPusher\Console\Command\PushCommand; 15 | use Sly\NotificationPusher\NotificationPusher; 16 | use Symfony\Component\Console\Application as BaseApplication; 17 | 18 | /** 19 | * @uses \Symfony\Component\Console\Application 20 | * @author Cédric Dugat 21 | */ 22 | class Application extends BaseApplication 23 | { 24 | public function __construct() 25 | { 26 | error_reporting(-1); 27 | 28 | parent::__construct('NotificationPusher version', NotificationPusher::VERSION); 29 | 30 | $this->add(new PushCommand()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Model/Device.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Device extends Units\Test 13 | { 14 | public function testConstruct() 15 | { 16 | $this->if($object = new TestedModel('t0k3n')) 17 | ->string($object->getToken())->isEqualTo('t0k3n') 18 | ->array($object->getParameters())->isEmpty(); 19 | 20 | $this->if($object = new TestedModel('t0k3n', ['param' => 'test'])) 21 | ->string($object->getToken())->isEqualTo('t0k3n') 22 | ->when($object->setToken('t0k3ns3tt3d')) 23 | ->string($object->getToken())->isEqualTo('t0k3ns3tt3d') 24 | ->array($object->getParameters()) 25 | ->hasKey('param') 26 | ->contains('test') 27 | ->size 28 | ->isEqualTo(1); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Model/Message.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Message extends Units\Test 13 | { 14 | public function testConstruct() 15 | { 16 | $this->if($object = new TestedModel('Test')) 17 | ->string($object->getText())->isEqualTo('Test') 18 | ->array($object->getOptions())->isEmpty(); 19 | 20 | $this->if($object = new TestedModel('Test', [ 21 | 'param' => 'test', 22 | ])) 23 | ->string($object->getText())->isEqualTo('Test') 24 | ->when($object->setText('Test 2')) 25 | ->string($object->getText())->isEqualTo('Test 2') 26 | ->array($object->getOptions()) 27 | ->hasKey('param') 28 | ->contains('test') 29 | ->size 30 | ->isEqualTo(1); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Collection/PushCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Collection; 13 | 14 | use Sly\NotificationPusher\Model\PushInterface; 15 | 16 | /** 17 | * @uses \Sly\NotificationPusher\Collection\AbstractCollection 18 | * @uses \IteratorAggregate 19 | * @author Cédric Dugat 20 | */ 21 | class PushCollection extends AbstractCollection 22 | { 23 | public function __construct() 24 | { 25 | $this->coll = new \ArrayIterator(); 26 | } 27 | 28 | /** 29 | * @return \ArrayIterator 30 | */ 31 | public function getIterator() 32 | { 33 | return $this->coll; 34 | } 35 | 36 | /** 37 | * @param PushInterface $push Push 38 | */ 39 | public function add(PushInterface $push) 40 | { 41 | $this->coll[] = $push; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Collection/MessageCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Collection; 13 | 14 | use Sly\NotificationPusher\Model\MessageInterface; 15 | 16 | /** 17 | * @uses \Sly\NotificationPusher\Collection\AbstractCollection 18 | * @uses \IteratorAggregate 19 | * @author Cédric Dugat 20 | */ 21 | class MessageCollection extends AbstractCollection 22 | { 23 | public function __construct() 24 | { 25 | $this->coll = new \ArrayIterator(); 26 | } 27 | 28 | /** 29 | * @return \ArrayIterator 30 | */ 31 | public function getIterator() 32 | { 33 | return $this->coll; 34 | } 35 | 36 | /** 37 | * @param MessageInterface $message Message 38 | */ 39 | public function add(MessageInterface $message) 40 | { 41 | $this->coll[] = $message; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Cédric Dugat (cedric@dugat.me) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/ResponseInterface.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | interface ResponseInterface 17 | { 18 | /** 19 | * @param DeviceInterface $device 20 | * @param array $response 21 | */ 22 | public function addParsedResponse(DeviceInterface $device, $response); 23 | 24 | /** 25 | * @param DeviceInterface $device 26 | * @param mixed $originalResponse 27 | */ 28 | public function addOriginalResponse(DeviceInterface $device, $originalResponse); 29 | 30 | /** 31 | * @param PushInterface $push Push 32 | */ 33 | public function addPush(PushInterface $push); 34 | 35 | /** 36 | * @return array 37 | */ 38 | public function getParsedResponses(); 39 | 40 | /** 41 | * @return mixed 42 | */ 43 | public function getOriginalResponses(); 44 | 45 | /** 46 | * @return PushCollection 47 | */ 48 | public function getPushCollection(); 49 | } 50 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/Message.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Model; 13 | 14 | /** 15 | * @author Cédric Dugat 16 | */ 17 | class Message extends BaseOptionedModel implements MessageInterface 18 | { 19 | /** 20 | * @var string 21 | */ 22 | protected $text; 23 | 24 | /** 25 | * @param string $text Text 26 | * @param array $options Options 27 | */ 28 | public function __construct($text, array $options = []) 29 | { 30 | $this->text = $text; 31 | $this->options = $options; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getText() 38 | { 39 | return $this->text; 40 | } 41 | 42 | /** 43 | * @param string $text Text 44 | * 45 | * @return MessageInterface 46 | */ 47 | public function setText($text) 48 | { 49 | $this->text = $text; 50 | 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/Device.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Model; 13 | 14 | /** 15 | * @author Cédric Dugat 16 | */ 17 | class Device extends BaseParameteredModel implements DeviceInterface 18 | { 19 | /** 20 | * @var string 21 | */ 22 | private $token; 23 | 24 | /** 25 | * @param string $token Token 26 | * @param array $parameters Parameters 27 | */ 28 | public function __construct($token, array $parameters = []) 29 | { 30 | $this->token = $token; 31 | $this->parameters = $parameters; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getToken() 38 | { 39 | return $this->token; 40 | } 41 | 42 | /** 43 | * @param string $token Token 44 | * 45 | * @return DeviceInterface 46 | */ 47 | public function setToken($token) 48 | { 49 | $this->token = $token; 50 | 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Collection/ResponseCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * (c) 2013 Cédric Dugat 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Sly\NotificationPusher\Collection; 14 | 15 | /** 16 | * Response Collection. 17 | * is just a container for a response from a push service 18 | * 19 | * @uses \Sly\NotificationPusher\Collection\AbstractCollection 20 | * @uses \IteratorAggregate 21 | * @author Lukas Klinzing 22 | */ 23 | class ResponseCollection extends AbstractCollection 24 | { 25 | public function __construct() 26 | { 27 | $this->coll = new \ArrayIterator(); 28 | } 29 | 30 | /** 31 | * @return \ArrayIterator 32 | */ 33 | public function getIterator() 34 | { 35 | return $this->coll; 36 | } 37 | 38 | /** 39 | * @param string $token 40 | * @param mixed $response 41 | */ 42 | public function add($token, $response) 43 | { 44 | $this->coll[$token] = $response; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /doc/push-from-cli.md: -------------------------------------------------------------------------------- 1 | # NotificationPusher - Documentation 2 | 3 | ## Push from CLI 4 | 5 | For some reasons (tests or others), you could be happened to use pushing from CLI. 6 | 7 | To do this, use `np` script (available at root directory) this way: 8 | 9 | ``` 10 | ./np push "Your message" --option1=value1 --option2=value2 ... 11 | ``` 12 | 13 | Each options matches with adapters required and optional ones. 14 | 15 | Here is a concrete APNS adapter example: 16 | 17 | ``` 18 | ./np push apns "It's an example!" --certificate=/path/to/the/certificate.pem 19 | ``` 20 | 21 | Here is a concrete GCM adapter example: 22 | 23 | ``` 24 | ./np push gcm "It's an example!" --api-key=XXXXXXXXXX 25 | ``` 26 | 27 | ## Documentation index 28 | 29 | * [Installation](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/installation.md) 30 | * [Getting started](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/getting-started.md) 31 | * [APNS adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/apns-adapter.md) 32 | * [GCM (FCM) adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/gcm-fcm-adapter.md) 33 | * [Create an adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/create-an-adapter.md) 34 | * Push from CLI 35 | * [Facades](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/facades.md) 36 | -------------------------------------------------------------------------------- /doc/create-an-adapter.md: -------------------------------------------------------------------------------- 1 | # NotificationPusher - Documentation 2 | 3 | ## Create an adapter 4 | 5 | To create your own adapter, just create a class with taking care to extends `\Sly\NotificationPusher\Adapter\BaseAdapter`, 6 | which implicitly implements `\Sly\NotificationPusher\Adapter\AdapterInterface` which contains some required methods: 7 | 8 | * `push`: contains the adapter logic to push notifications 9 | * `supports`: return the token condition for using the adapter 10 | * `getDefaultParameters`: returns default parameters used by the adapter 11 | * `getRequiredParameters`: returns required parameters used by the adapter 12 | 13 | Feel free to observe [existent adapters](https://github.com/Ph3nol/NotificationPusher/tree/master/src/Sly/NotificationPusher/Adapter) for concrete example. 14 | 15 | ## Documentation index 16 | 17 | * [Installation](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/installation.md) 18 | * [Getting started](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/getting-started.md) 19 | * [APNS adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/apns-adapter.md) 20 | * [GCM (FCM) adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/gcm-fcm-adapter.md) 21 | * Create an adapter 22 | * [Push from CLI](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/push-from-cli.md) 23 | * [Facades](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/facades.md) 24 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Adapter/ApnsAPI.php: -------------------------------------------------------------------------------- 1 | 18 | * todo: implement with edamov/pushok 19 | */ 20 | class ApnsAPI extends BaseAdapter 21 | { 22 | 23 | /** 24 | * @param PushInterface $push Push 25 | * 26 | * @return DeviceCollection 27 | */ 28 | public function push(PushInterface $push) 29 | { 30 | // TODO: Implement push() method. 31 | } 32 | 33 | /** 34 | * @param string $token Token 35 | * 36 | * @return boolean 37 | */ 38 | public function supports($token) 39 | { 40 | // TODO: Implement supports() method. 41 | } 42 | 43 | /** 44 | * @return array 45 | */ 46 | public function getDefinedParameters() 47 | { 48 | // TODO: Implement getDefinedParameters() method. 49 | } 50 | 51 | /** 52 | * @return array 53 | */ 54 | public function getDefaultParameters() 55 | { 56 | // TODO: Implement getDefaultParameters() method. 57 | } 58 | 59 | /** 60 | * @return array 61 | */ 62 | public function getRequiredParameters() 63 | { 64 | // TODO: Implement getRequiredParameters() method. 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Collection/DeviceCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Collection; 13 | 14 | use Sly\NotificationPusher\Model\DeviceInterface; 15 | 16 | /** 17 | * @uses \Sly\NotificationPusher\Collection\AbstractCollection 18 | * @uses \IteratorAggregate 19 | * @author Cédric Dugat 20 | */ 21 | class DeviceCollection extends AbstractCollection 22 | { 23 | /** 24 | * @param array $devices Devices 25 | */ 26 | public function __construct(array $devices = []) 27 | { 28 | $this->coll = new \ArrayIterator(); 29 | 30 | foreach ($devices as $device) { 31 | $this->add($device); 32 | } 33 | } 34 | 35 | /** 36 | * @return \ArrayIterator 37 | */ 38 | public function getIterator() 39 | { 40 | return $this->coll; 41 | } 42 | 43 | /** 44 | * @param DeviceInterface $device Device 45 | */ 46 | public function add(DeviceInterface $device) 47 | { 48 | $this->coll[$device->getToken()] = $device; 49 | } 50 | 51 | /** 52 | * @return array 53 | */ 54 | public function getTokens() 55 | { 56 | $tokens = []; 57 | 58 | foreach ($this as $device) { 59 | $tokens[] = $device->getToken(); 60 | } 61 | 62 | return array_unique(array_filter($tokens)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Model/BaseOptionedModel.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class BaseOptionedModel extends Units\Test 13 | { 14 | public function testMethods() 15 | { 16 | $this->if($object = new Message('Test', ['param' => 'test'])) 17 | ->boolean($object->hasOption('param')) 18 | ->isTrue() 19 | ->string($object->getOption('param')) 20 | ->isEqualTo('test') 21 | ->boolean($object->hasOption('notExist')) 22 | ->isFalse() 23 | ->variable($object->getOption('notExist')) 24 | ->isNull() 25 | ->string($object->getOption('renotExist', '12345')) 26 | ->isEqualTo('12345') 27 | ->when($object->setOptions(['chuck' => 'norris'])) 28 | ->boolean($object->hasOption('chuck')) 29 | ->isTrue() 30 | ->string($object->getOption('chuck')) 31 | ->isEqualTo('norris') 32 | ->when($object->setOption('poney', 'powerful')) 33 | ->boolean($object->hasOption('poney')) 34 | ->isTrue() 35 | ->string($object->getOption('poney')) 36 | ->isEqualTo('powerful') 37 | ->when($object->setOption('null', null)) 38 | ->boolean($object->hasOption('null')) 39 | ->isTrue() 40 | ->variable($object->getOption('null')) 41 | ->isNull(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Model/BaseParameteredModel.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class BaseParameteredModel extends Units\Test 13 | { 14 | public function testMethods() 15 | { 16 | $this->if($object = new Device('Test', ['param' => 'test'])) 17 | ->boolean($object->hasParameter('param')) 18 | ->isTrue() 19 | ->string($object->getParameter('param')) 20 | ->isEqualTo('test') 21 | ->boolean($object->hasParameter('notExist')) 22 | ->isFalse() 23 | ->variable($object->getParameter('notExist')) 24 | ->isNull() 25 | ->string($object->getParameter('renotExist', '12345')) 26 | ->isEqualTo('12345') 27 | ->when($object->setParameters(['chuck' => 'norris'])) 28 | ->boolean($object->hasParameter('chuck')) 29 | ->isTrue() 30 | ->string($object->getParameter('chuck')) 31 | ->isEqualTo('norris') 32 | ->when($object->setParameter('poney', 'powerful')) 33 | ->boolean($object->hasParameter('poney')) 34 | ->isTrue() 35 | ->string($object->getParameter('poney')) 36 | ->isEqualTo('powerful') 37 | ->when($object->setParameter('null', null)) 38 | ->boolean($object->hasParameter('null')) 39 | ->isTrue() 40 | ->variable($object->getParameter('null')) 41 | ->isNull(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sly/notification-pusher", 3 | "description": "Standalone PHP library for easy devices notifications push.", 4 | "keywords": [ 5 | "apple", 6 | "iphone", 7 | "apns", 8 | "android", 9 | "gcm", 10 | "notification", 11 | "message", 12 | "push", 13 | "pusher" 14 | ], 15 | "homepage": "https://github.com/Ph3nol/NotificationPusher", 16 | "type": "standalone", 17 | "license": "MIT", 18 | "authors": [ 19 | { 20 | "name": "Cédric Dugat", 21 | "email": "cedric@dugat.me" 22 | }, 23 | { 24 | "name": "Contributors", 25 | "homepage": "https://github.com/Ph3nol/NotificationPusher/contributors" 26 | } 27 | ], 28 | "require": { 29 | "php": ">=5.6", 30 | "ext-ctype": "*", 31 | "symfony/options-resolver": ">=2.3,<5", 32 | "symfony/console": ">=2.3,<6", 33 | "symfony/process": ">=2.3,<5", 34 | "zendframework/zendservice-apple-apns": "~1.4", 35 | "zendframework/zendservice-google-gcm": "~2.1", 36 | "zendframework/zend-validator": "^2.12", 37 | "doctrine/inflector": "~1.1", 38 | "symfony/filesystem": ">=2.3,<5", 39 | "symfony/debug": ">=2.3,<5" 40 | }, 41 | "require-dev": { 42 | "atoum/atoum": "^3.1", 43 | "atoum/stubs": "^2.5", 44 | "symfony/var-dumper": ">=2.3,<5", 45 | "atoum/visibility-extension": "^1.3" 46 | }, 47 | "config": { 48 | "bin-dir": "bin" 49 | }, 50 | "bin": [ 51 | "np" 52 | ], 53 | "autoload": { 54 | "psr-4": { 55 | "Sly\\": "src/Sly/" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/BaseOptionedModel.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Model; 13 | 14 | /** 15 | * @author Cédric Dugat 16 | */ 17 | abstract class BaseOptionedModel 18 | { 19 | /** 20 | * @var array 21 | */ 22 | protected $options = []; 23 | 24 | /** 25 | * @return array 26 | */ 27 | public function getOptions() 28 | { 29 | return $this->options; 30 | } 31 | 32 | /** 33 | * @param string $key Key 34 | * 35 | * @return boolean 36 | */ 37 | public function hasOption($key) 38 | { 39 | return array_key_exists($key, $this->options); 40 | } 41 | 42 | /** 43 | * @param string $key Key 44 | * @param mixed $default Default 45 | * 46 | * @return mixed 47 | */ 48 | public function getOption($key, $default = null) 49 | { 50 | return $this->hasOption($key) ? $this->options[$key] : $default; 51 | } 52 | 53 | /** 54 | * @param array $options Options 55 | * 56 | * @return BaseOptionedModel 57 | */ 58 | public function setOptions($options) 59 | { 60 | $this->options = $options; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @param string $key Key 67 | * @param mixed $value Value 68 | * 69 | * @return mixed 70 | */ 71 | public function setOption($key, $value) 72 | { 73 | $this->options[$key] = $value; 74 | 75 | return $value; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Adapter/AdapterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Adapter; 13 | 14 | use Sly\NotificationPusher\Collection\DeviceCollection; 15 | use Sly\NotificationPusher\Model\PushInterface; 16 | use Sly\NotificationPusher\Model\ResponseInterface; 17 | 18 | /** 19 | * @author Cédric Dugat 20 | */ 21 | interface AdapterInterface 22 | { 23 | /** 24 | * @param PushInterface $push Push 25 | * 26 | * @return DeviceCollection 27 | */ 28 | public function push(PushInterface $push); 29 | 30 | /** 31 | * @param string $token Token 32 | * 33 | * @return boolean 34 | */ 35 | public function supports($token); 36 | 37 | /** 38 | * @return ResponseInterface 39 | */ 40 | public function getResponse(); 41 | 42 | /** 43 | * @param ResponseInterface $response 44 | */ 45 | public function setResponse(ResponseInterface $response); 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getDefinedParameters(); 51 | 52 | /** 53 | * @return array 54 | */ 55 | public function getDefaultParameters(); 56 | 57 | /** 58 | * @return array 59 | */ 60 | public function getRequiredParameters(); 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function getEnvironment(); 66 | 67 | /** 68 | * @param string $environment Environment value to set 69 | * 70 | * @return AdapterInterface 71 | */ 72 | public function setEnvironment($environment); 73 | } 74 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/BaseParameteredModel.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Model; 13 | 14 | /** 15 | * @author Cédric Dugat 16 | */ 17 | abstract class BaseParameteredModel 18 | { 19 | /** 20 | * @var array 21 | */ 22 | protected $parameters = []; 23 | 24 | /** 25 | * @return array 26 | */ 27 | public function getParameters() 28 | { 29 | return $this->parameters; 30 | } 31 | 32 | /** 33 | * @param string $key Key 34 | * 35 | * @return boolean 36 | */ 37 | public function hasParameter($key) 38 | { 39 | return array_key_exists($key, $this->parameters); 40 | } 41 | 42 | /** 43 | * @param string $key Key 44 | * @param mixed $default Default 45 | * 46 | * @return mixed 47 | */ 48 | public function getParameter($key, $default = null) 49 | { 50 | return $this->hasParameter($key) ? $this->parameters[$key] : $default; 51 | } 52 | 53 | /** 54 | * @param array $parameters Parameters 55 | * 56 | * @return BaseParameteredModel 57 | */ 58 | public function setParameters($parameters) 59 | { 60 | $this->parameters = $parameters; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @param string $key Key 67 | * @param mixed $value Value 68 | * 69 | * @return mixed 70 | */ 71 | public function setParameter($key, $value) 72 | { 73 | $this->parameters[$key] = $value; 74 | 75 | return $value; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Adapter/BaseAdapter.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class BaseAdapter extends Units\Test 13 | { 14 | public function testAdapterKey() 15 | { 16 | $this->if($this->mockGenerator()->orphanize('__construct')) 17 | ->and($this->mockClass(\Sly\NotificationPusher\Adapter\Apns::class, '\Mock')) 18 | ->and($object = new \Mock\Apns()) 19 | ->and($object->getMockController()->getAdapterKey = 'Apns') 20 | ->string($object->getAdapterKey()) 21 | ->isEqualTo('Apns') 22 | ->string((string) $object) 23 | ->isEqualTo('Apns'); 24 | } 25 | 26 | public function testEnvironment() 27 | { 28 | $this->if($this->mockGenerator()->orphanize('__construct')) 29 | ->and($this->mockClass(\Sly\NotificationPusher\Adapter\Apns::class, '\Mock')) 30 | ->and($object = new \Mock\Apns()) 31 | ->when($object->setEnvironment(PushManager::ENVIRONMENT_DEV)) 32 | ->string($object->getEnvironment()) 33 | ->isEqualTo(PushManager::ENVIRONMENT_DEV) 34 | ->boolean($object->isDevelopmentEnvironment()) 35 | ->isTrue() 36 | ->boolean($object->isProductionEnvironment()) 37 | ->isFalse() 38 | ->when($object->setEnvironment(PushManager::ENVIRONMENT_PROD)) 39 | ->string($object->getEnvironment()) 40 | ->isEqualTo(PushManager::ENVIRONMENT_PROD) 41 | ->boolean($object->isProductionEnvironment()) 42 | ->isTrue() 43 | ->boolean($object->isDevelopmentEnvironment()) 44 | ->isFalse(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/Response.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Response implements ResponseInterface 18 | { 19 | /** 20 | * @var array 21 | */ 22 | private $parsedResponses = []; 23 | 24 | /** 25 | * @var array 26 | */ 27 | private $originalResponses = []; 28 | 29 | /** 30 | * @var PushCollection 31 | */ 32 | private $pushCollection; 33 | 34 | public function __construct() 35 | { 36 | $this->pushCollection = new PushCollection(); 37 | } 38 | 39 | /** 40 | * @param DeviceInterface $device 41 | * @param array $response 42 | */ 43 | public function addParsedResponse(DeviceInterface $device, $response) 44 | { 45 | if (!is_array($response)) { 46 | throw new \InvalidArgumentException('Response must be array type'); 47 | } 48 | 49 | $this->parsedResponses[$device->getToken()] = $response; 50 | } 51 | 52 | /** 53 | * @param DeviceInterface $device 54 | * @param mixed $originalResponse 55 | */ 56 | public function addOriginalResponse(DeviceInterface $device, $originalResponse) 57 | { 58 | $this->originalResponses[$device->getToken()] = $originalResponse; 59 | } 60 | 61 | /** 62 | * @param PushInterface $push Push 63 | */ 64 | public function addPush(PushInterface $push) 65 | { 66 | $this->pushCollection->add($push); 67 | } 68 | 69 | /** 70 | * @return array 71 | */ 72 | public function getParsedResponses() 73 | { 74 | return $this->parsedResponses; 75 | } 76 | 77 | /** 78 | * @return mixed 79 | */ 80 | public function getOriginalResponses() 81 | { 82 | return $this->originalResponses; 83 | } 84 | 85 | /** 86 | * @return PushCollection 87 | */ 88 | public function getPushCollection() 89 | { 90 | return $this->pushCollection; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Collection/AbstractCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Collection; 13 | 14 | use ArrayIterator; 15 | use Countable; 16 | use IteratorAggregate; 17 | use SeekableIterator; 18 | use Sly\NotificationPusher\Model\MessageInterface; 19 | 20 | /** 21 | * @uses \IteratorAggregate 22 | * @author Cédric Dugat 23 | */ 24 | abstract class AbstractCollection implements IteratorAggregate, Countable 25 | { 26 | /** 27 | * @var ArrayIterator 28 | */ 29 | protected $coll; 30 | 31 | /** 32 | * @inheritdoc 33 | * @return ArrayIterator|SeekableIterator 34 | */ 35 | abstract public function getIterator(); 36 | 37 | /** 38 | * @param string $key Key 39 | * 40 | * @return MessageInterface|false 41 | */ 42 | public function get($key) 43 | { 44 | return isset($this->coll[$key]) ? $this->coll[$key] : false; 45 | } 46 | 47 | /** 48 | * @return integer 49 | */ 50 | public function count() 51 | { 52 | return $this->getIterator()->count(); 53 | } 54 | 55 | /** 56 | * @return boolean 57 | */ 58 | public function isEmpty() 59 | { 60 | return $this->count() === 0; 61 | } 62 | 63 | /** 64 | * Clear categories. 65 | */ 66 | public function clear() 67 | { 68 | $this->coll = new ArrayIterator(); 69 | } 70 | 71 | /** 72 | * @return mixed|null 73 | */ 74 | public function first() 75 | { 76 | $tmp = clone $this->coll; 77 | 78 | //go to the beginning 79 | $tmp->rewind(); 80 | 81 | if (!$tmp->valid()) { 82 | return null; 83 | } 84 | 85 | return $tmp->current(); 86 | } 87 | 88 | /** 89 | * @return mixed|null 90 | */ 91 | public function last() 92 | { 93 | $tmp = clone $this->coll; 94 | 95 | //go to the end 96 | $tmp->seek($tmp->count() - 1); 97 | 98 | if (!$tmp->valid()) { 99 | return null; 100 | } 101 | 102 | return $tmp->current(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/PushInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Model; 13 | 14 | use DateTime; 15 | use Sly\NotificationPusher\Adapter\AdapterInterface; 16 | use Sly\NotificationPusher\Collection\DeviceCollection; 17 | use Sly\NotificationPusher\Collection\ResponseCollection; 18 | 19 | interface PushInterface 20 | { 21 | /** 22 | * Constants define available statuses 23 | */ 24 | const STATUS_PENDING = 'pending'; 25 | const STATUS_PUSHED = 'sent'; 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getStatus(); 31 | 32 | /** 33 | * @param string $status Status 34 | * 35 | * @return PushInterface 36 | */ 37 | public function setStatus($status); 38 | 39 | /** 40 | * @return boolean 41 | */ 42 | public function isPushed(); 43 | 44 | /** 45 | * @return PushInterface 46 | */ 47 | public function pushed(); 48 | 49 | /** 50 | * @return AdapterInterface 51 | */ 52 | public function getAdapter(); 53 | 54 | /** 55 | * @param AdapterInterface $adapter Adapter 56 | * 57 | * @return PushInterface 58 | */ 59 | public function setAdapter(AdapterInterface $adapter); 60 | 61 | /** 62 | * @return MessageInterface 63 | */ 64 | public function getMessage(); 65 | 66 | /** 67 | * @param MessageInterface $message Message 68 | * 69 | * @return PushInterface 70 | */ 71 | public function setMessage(MessageInterface $message); 72 | 73 | /** 74 | * @return DeviceCollection 75 | */ 76 | public function getDevices(); 77 | 78 | /** 79 | * @param DeviceCollection $devices Devices 80 | * 81 | * @return PushInterface 82 | */ 83 | public function setDevices(DeviceCollection $devices); 84 | 85 | /** 86 | * @return ResponseCollection 87 | */ 88 | public function getResponses(); 89 | 90 | /** 91 | * @param DeviceInterface $device 92 | * @param mixed $response 93 | */ 94 | public function addResponse(DeviceInterface $device, $response); 95 | 96 | /** 97 | * @return DateTime 98 | */ 99 | public function getPushedAt(); 100 | 101 | /** 102 | * @param DateTime $pushedAt PushedAt 103 | * 104 | * @return PushInterface 105 | */ 106 | public function setPushedAt(DateTime $pushedAt); 107 | } 108 | -------------------------------------------------------------------------------- /doc/gcm-fcm-adapter.md: -------------------------------------------------------------------------------- 1 | # NotificationPusher - Documentation 2 | 3 | ## GCM (FCM) adapter 4 | 5 | [GCM](http://developer.android.com/google/gcm/gs.html) adapter is used to push notification to Google/Android devices. 6 | [FCM](https://firebase.google.com/docs/cloud-messaging/) is supported. Please see [this comment](https://github.com/Ph3nol/NotificationPusher/pull/141#issuecomment-318896948) for explanation. 7 | 8 | ##### Important 9 | Parameter `notificatinData` is mandatory for sending messagev via FCM. 10 | 11 | ### Custom notification push example 12 | 13 | ``` php 14 | 'YourApiKey', 37 | )); 38 | 39 | // Set the device(s) to push the notification to. 40 | $devices = new DeviceCollection(array( 41 | new Device('Token1'), 42 | new Device('Token2'), 43 | new Device('Token3'), 44 | )); 45 | 46 | $params = []; 47 | 48 | NOTE: if you need to pass not only data, but also notification array 49 | use key notificationData in params, like $params['notificationData'] = ['title' => 'Title text','body' => 'Body text'] 50 | OR you could use optional GcmMessage class instead of Message and 51 | use it's setter setNotificationData() 52 | 53 | // Then, create the push skel. 54 | $message = new Message('This is an example.', $params); 55 | 56 | // Finally, create and add the push to the manager, and push it! 57 | $push = new Push($gcmAdapter, $devices, $message); 58 | $pushManager->add($push); 59 | $pushManager->push(); // Returns a collection of notified devices 60 | 61 | // each response will contain also 62 | // the data of the overall delivery 63 | foreach($push->getResponses() as $token => $response) { 64 | // > $response 65 | // Array 66 | // ( 67 | // [message_id] => fake_message_id 68 | // [multicast_id] => -1 69 | // [success] => 1 70 | // [failure] => 0 71 | // [canonical_ids] => 0 72 | // ) 73 | } 74 | ``` 75 | 76 | ## Documentation index 77 | 78 | * [Installation](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/installation.md) 79 | * [Getting started](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/getting-started.md) 80 | * [APNS adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/apns-adapter.md) 81 | * GCM (FCM) adapter 82 | * [Create an adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/create-an-adapter.md) 83 | * [Push from CLI](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/push-from-cli.md) 84 | * [Facades](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/facades.md) 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NotificationPusher [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ph3nol/notificationpusher?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | 3 | Standalone PHP library for easy devices message notifications push. 4 | 5 | [![Latest Stable Version](https://img.shields.io/packagist/v/sly/notification-pusher.svg)](https://packagist.org/packages/sly/notification-pusher) 6 | [![License](https://img.shields.io/packagist/l/sly/notification-pusher.svg)](https://packagist.org/packages/sly/notification-pusher) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/sly/notification-pusher.svg)](https://packagist.org/packages/sly/notification-pusher) 8 | [![Build Status](https://secure.travis-ci.org/Ph3nol/NotificationPusher.png)](http://travis-ci.org/Ph3nol/NotificationPusher) 9 | 10 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/4f6f80c4-281a-4903-bf4c-1eb264995dbd/big.png)](https://insight.sensiolabs.com/projects/4f6f80c4-281a-4903-bf4c-1eb264995dbd) 11 | 12 | **Feel free to contribute! Thanks.** 13 | 14 | ## Contributors 15 | 16 | * [Cédric Dugat](https://github.com/Ph3nol) (Author / Lead developer) 17 | * [Oleg Abrazhaev](https://github.com/seyfer) (Lead developer) 18 | * [Community contributors](https://github.com/Ph3nol/NotificationPusher/graphs/contributors) 19 | 20 | ## Installation 21 | 22 | ``` 23 | composer require sly/notification-pusher 24 | ``` 25 | 26 | This repository uses PSR-0 autoload. 27 | After installation with [composer](https://getcomposer.org/download/) please adjust you autoloading config if needed 28 | or `include vendor/autoload.php` in your index.php. 29 | 30 | ## Requirements 31 | 32 | * PHP 5.6+ 33 | * PHP Curl and OpenSSL modules 34 | * Specific adapters requirements (like APNS certificate, GCM (FCM) API key, etc.) 35 | 36 | **WARNING** Version `v3.0` would support only php 7.0+. Please, update your composer config if needed. 37 | 38 | ## Today available adapters 39 | 40 | * APNS (Apple) 41 | * GCM (Android) and FCM (Android) 42 | 43 | ## Documentation and examples 44 | 45 | * [Installation](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/installation.md) 46 | * [Getting started](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/getting-started.md) 47 | * [APNS adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/apns-adapter.md) 48 | * [GCM (FCM) adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/gcm-fcm-adapter.md) 49 | * [Create an adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/create-an-adapter.md) 50 | * [Push from CLI](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/push-from-cli.md) 51 | * [Facades](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/facades.md) 52 | 53 | ## Todo 54 | 55 | * Rewrite tests in PHPUnit 8+. (contributions are welcome!) 56 | * Add new features (custom APNS payloads, GCM and FCM custom options, etc.) 57 | * Add new adapters (like Blackberry and Windows phones) 58 | * Write more documentation and examples! 59 | 60 | ## 1.x users 61 | 62 | Old version is still available from [1.x branch](https://github.com/Ph3nol/NotificationPusher/tree/1.x), with dedicated declared tag. 63 | 64 | 65 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/Ph3nol/notificationpusher/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 66 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Adapter/BaseAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Adapter; 13 | 14 | use Sly\NotificationPusher\Model\BaseParameteredModel; 15 | use Sly\NotificationPusher\Model\Response; 16 | use Sly\NotificationPusher\Model\ResponseInterface; 17 | use Sly\NotificationPusher\PushManager; 18 | use Symfony\Component\OptionsResolver\OptionsResolver; 19 | 20 | /** 21 | * @author Cédric Dugat 22 | */ 23 | abstract class BaseAdapter extends BaseParameteredModel implements AdapterInterface 24 | { 25 | /** 26 | * @var string 27 | */ 28 | protected $adapterKey; 29 | 30 | /** 31 | * @var string 32 | */ 33 | protected $environment; 34 | 35 | /** 36 | * @var ResponseInterface 37 | */ 38 | protected $response; 39 | 40 | /** 41 | * @param array $parameters Adapter specific parameters 42 | */ 43 | public function __construct(array $parameters = []) 44 | { 45 | $resolver = new OptionsResolver(); 46 | $resolver->setDefined($this->getDefinedParameters()); 47 | $resolver->setDefaults($this->getDefaultParameters()); 48 | $resolver->setRequired($this->getRequiredParameters()); 49 | 50 | $reflectedClass = new \ReflectionClass($this); 51 | $this->adapterKey = lcfirst($reflectedClass->getShortName()); 52 | $this->parameters = $resolver->resolve($parameters); 53 | $this->response = new Response(); 54 | } 55 | 56 | /** 57 | * @return ResponseInterface 58 | */ 59 | public function getResponse() 60 | { 61 | return $this->response; 62 | } 63 | 64 | /** 65 | * @param ResponseInterface $response 66 | */ 67 | public function setResponse(ResponseInterface $response) 68 | { 69 | $this->response = $response; 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function __toString() 76 | { 77 | return ucfirst($this->getAdapterKey()); 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getAdapterKey() 84 | { 85 | return $this->adapterKey; 86 | } 87 | 88 | /** 89 | * @return string 90 | */ 91 | public function getEnvironment() 92 | { 93 | return $this->environment; 94 | } 95 | 96 | /** 97 | * @param string $environment Environment value to set 98 | * 99 | * @return AdapterInterface 100 | */ 101 | public function setEnvironment($environment) 102 | { 103 | $this->environment = $environment; 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * @return boolean 110 | */ 111 | public function isDevelopmentEnvironment() 112 | { 113 | return (PushManager::ENVIRONMENT_DEV === $this->getEnvironment()); 114 | } 115 | 116 | /** 117 | * @return boolean 118 | */ 119 | public function isProductionEnvironment() 120 | { 121 | return (PushManager::ENVIRONMENT_PROD === $this->getEnvironment()); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /doc/getting-started.md: -------------------------------------------------------------------------------- 1 | # NotificationPusher - Documentation 2 | 3 | ## Getting started 4 | 5 | **NOTE** If you want even easier start, please check our facades 6 | * [Facades](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/facades.md) 7 | 8 | First, we are going to discover this library entities: 9 | 10 | * Models (messages, pushes, devices) 11 | * Adapters (APNS, GCM (FCM) etc.) 12 | * The Manager 13 | 14 | Here is the basic principle of a notification push: 15 | 16 | A **push** has 3 main elements: a composed **message**, some defined **devices** to notify 17 | and an **adapter** matching with these devices. 18 | The **manager** has to collect all push notifications and send them. 19 | 20 | Here is how to translate this with code (just a little not-working example): 21 | 22 | ``` php 23 | add($push); 43 | $pushManager->push(); 44 | 45 | foreach($push->getResponses() as $token => $response) { 46 | // ... 47 | } 48 | ``` 49 | 50 | ## More about the Message entity 51 | 52 | Some general options can be passed to the message entity and be used by adapters. 53 | A message pushed from APNS adapter can comport a "badge" or "sound" information which will be set with 54 | instance constructor second argument: 55 | 56 | ``` php 57 | 1, 61 | 'sound' => 'example.aiff', 62 | // ... 63 | )); 64 | ``` 65 | 66 | ## More about the Device entity 67 | 68 | The device can comport some dedicated informations that could be used by adapters. 69 | For example, APNS adapter could want to know a device badge status for incrementing it with message's one. 70 | 71 | Here is an example of this: 72 | 73 | ``` php 74 | 1, 78 | // ... 79 | )); 80 | 81 | $devices = new Sly\NotificationPusher\Collection\DeviceCollection(array( 82 | new Sly\NotificationPusher\Model\Device('Token1', array('badge' => 5)), 83 | // ... 84 | )); 85 | ``` 86 | 87 | ## Documentation index 88 | 89 | * [Installation](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/installation.md) 90 | * Getting started 91 | * [APNS adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/apns-adapter.md) 92 | * [GCM (FCM) adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/gcm-fcm-adapter.md) 93 | * [Create an adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/create-an-adapter.md) 94 | * [Push from CLI](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/push-from-cli.md) 95 | * [Facades](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/facades.md) -------------------------------------------------------------------------------- /doc/facades.md: -------------------------------------------------------------------------------- 1 | # NotificationPusher - Documentation 2 | 3 | ## Facades 4 | 5 | In order to simplify lib usage some service facades were added. 6 | They would return Response object with all information about sent pushes. 7 | Also, facades provide useful methods to filter successful and invalid tokens from responses. 8 | 9 | ## Basic usage example 10 | 11 | ### The Response 12 | 13 | ``` php 14 | $response->getParsedResponses(); 15 | $response->getOriginalResponses(); 16 | $response->getPushCollection(); 17 | ``` 18 | 19 | ### Android facade 20 | 21 | ``` php 22 | $android_api_key = 'key'; 23 | 24 | //get tokens list from your service 25 | $tokensA = ['token1', 'token2', 'token3']; 26 | 27 | //get messages 28 | $messages = [ 29 | 'hi Luc, it\'s test', 30 | 'test noty 2', 31 | ]; 32 | 33 | //maybe you want some params 34 | $params = []; 35 | 36 | //init android facade service 37 | $pushNotificationService = new GcmPushService( 38 | $android_api_key, PushManager::ENVIRONMENT_PROD 39 | ); 40 | 41 | //push! you will get a Response with parsed and original response collections 42 | //and with a push collection 43 | $response = $pushNotificationService->push($tokensA, $messages, $params); 44 | 45 | NOTE: if you need to pass not only data, but also notification array 46 | use key notificationData in params, like $params[notificationData] = [] 47 | OR you could use optional GcmMessage class instead of Message and 48 | use it's setter setNotificationData() 49 | 50 | //easily access list of successful and invalid tokens 51 | $invalidTokens = $pushNotificationService->getInvalidTokens(); 52 | $successfulTokens = $pushNotificationService->getSuccessfulTokens(); 53 | 54 | die(dump($response, $invalidTokens, $successfulTokens)); 55 | ``` 56 | 57 | ### APNS facade 58 | 59 | ``` php 60 | $certificatePath = 'cert.pem'; 61 | $passPhrase = ''; 62 | 63 | //get tokens list 64 | $tokensA = ['token1', 'token2', 'token3']; 65 | 66 | //get messages 67 | $messages = [ 68 | 'hi Luc, it\'s test', 69 | 'test noty 2', 70 | ]; 71 | 72 | //maybe you want some params 73 | $params = []; 74 | 75 | //init android facade service 76 | $pushNotificationService = new ApnsPushService( 77 | $certificatePath, $passPhrase, PushManager::ENVIRONMENT_PROD 78 | ); 79 | 80 | //push! you will get a Response with parsed and original response collections 81 | //and with a push collection 82 | $response = $pushNotificationService->push($tokensA, $messages, $params); 83 | 84 | //you could get a feedback with list of successful tokens 85 | $feedback = $pushNotificationService->feedback(); 86 | 87 | //or! 88 | 89 | //easily access list of successful and invalid tokens 90 | //WARNING! these methods would send feedback request anyway 91 | $invalidTokens = $pushNotificationService->getInvalidTokens(); 92 | $successfulTokens = $pushNotificationService->getSuccessfulTokens(); 93 | 94 | die(dump($response, $feedback, $invalidTokens, $successfulTokens)); 95 | ``` 96 | 97 | ## Documentation index 98 | 99 | * [Installation](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/installation.md) 100 | * [Getting started](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/getting-started.md) 101 | * [APNS adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/apns-adapter.md) 102 | * [GCM (FCM) adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/gcm-fcm-adapter.md) 103 | * [Create an adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/create-an-adapter.md) 104 | * [Push from CLI](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/push-from-cli.md) 105 | * Facades 106 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/PushManager.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class PushManager extends Units\Test 20 | { 21 | const APNS_TOKEN_EXAMPLE = '111db24975bb6c6b63214a8d268052aa0a965cc1e32110ab06a72b19074c2222'; 22 | 23 | public function testConstruct() 24 | { 25 | $this->if($object = new TestedModel()) 26 | ->string($object->getEnvironment()) 27 | ->isEqualTo(TestedModel::ENVIRONMENT_DEV) 28 | ->when($object = new TestedModel(TestedModel::ENVIRONMENT_PROD)) 29 | ->string($object->getEnvironment()) 30 | ->isEqualTo(TestedModel::ENVIRONMENT_PROD); 31 | } 32 | 33 | public function testCollection() 34 | { 35 | $this->if($this->mockGenerator()->orphanize('__construct')) 36 | ->and($this->mockClass(Push::class, '\Mock')) 37 | ->and($push = new \Mock\Push()) 38 | ->and($push->getMockController()->getMessage = new Message('Test')) 39 | ->and($push->getMockController()->getDevices = new DeviceCollection([new Device(self::APNS_TOKEN_EXAMPLE)])) 40 | ->and($push2 = new \Mock\Push()) 41 | ->and($push2->getMockController()->getMessage = new Message('Test 2')) 42 | ->and($push2->getMockController()->getDevices = new DeviceCollection([new Device(self::APNS_TOKEN_EXAMPLE)])) 43 | ->and($object = (new TestedModel())->getPushCollection()) 44 | ->when($object->add($push)) 45 | ->object($object) 46 | ->isInstanceOf(PushCollection::class) 47 | ->object($object->getIterator()) 48 | ->hasSize(1) 49 | ->when($object->add($push2)) 50 | ->object($object) 51 | ->isInstanceOf(PushCollection::class) 52 | ->object($object->getIterator()) 53 | ->hasSize(2) 54 | ->object($object->first()) 55 | ->isEqualTo($push) 56 | ->object($object->last()) 57 | ->isEqualTo($push2); 58 | } 59 | 60 | public function testPush() 61 | { 62 | date_default_timezone_set('UTC'); 63 | $this->if($this->mockGenerator()->orphanize('__construct')) 64 | ->and($this->mockClass(Apns::class, '\Mock')) 65 | ->and($apnsAdapter = new \Mock\Apns()) 66 | ->and($apnsAdapter->getMockController()->push = true) 67 | ->and($apnsAdapter->getMockController()->getResponse = new Response()) 68 | ->and($this->mockGenerator()->orphanize('__construct')) 69 | ->and($this->mockClass(Push::class, '\Mock')) 70 | ->and($push = new \Mock\Push()) 71 | ->and($push->getMockController()->getMessage = new Message('Test')) 72 | ->and($push->getMockController()->getDevices = new DeviceCollection([new Device(self::APNS_TOKEN_EXAMPLE)])) 73 | ->and($push->getMockController()->getAdapter = $apnsAdapter) 74 | ->and($object = new TestedModel()) 75 | ->and($object->add($push)) 76 | ->object($object->push()) 77 | ->isInstanceOf(PushCollection::class) 78 | ->hasSize(1) 79 | ->object($object->getResponse()) 80 | ->isInstanceOf(Response::class); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/PushManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher; 13 | 14 | use Sly\NotificationPusher\Adapter\AdapterInterface; 15 | use Sly\NotificationPusher\Adapter\FeedbackAdapterInterface; 16 | use Sly\NotificationPusher\Collection\PushCollection; 17 | use Sly\NotificationPusher\Exception\AdapterException; 18 | use Sly\NotificationPusher\Model\Push; 19 | use Sly\NotificationPusher\Model\PushInterface; 20 | use Sly\NotificationPusher\Model\ResponseInterface; 21 | 22 | /** 23 | * @uses \Sly\NotificationPusher\Collection\PushCollection 24 | * @author Cédric Dugat 25 | */ 26 | class PushManager 27 | { 28 | const ENVIRONMENT_DEV = 'dev'; 29 | const ENVIRONMENT_PROD = 'prod'; 30 | 31 | /** 32 | * @var string 33 | */ 34 | private $environment; 35 | 36 | /** 37 | * @var PushCollection 38 | */ 39 | private $pushCollection; 40 | 41 | /** 42 | * @var ResponseInterface 43 | */ 44 | private $response; 45 | 46 | /** 47 | * @param string $environment Environment 48 | */ 49 | public function __construct($environment = self::ENVIRONMENT_DEV) 50 | { 51 | $this->environment = $environment; 52 | $this->pushCollection = new PushCollection(); 53 | } 54 | 55 | /** 56 | * @param PushInterface $push Push 57 | */ 58 | public function add(PushInterface $push) 59 | { 60 | $this->pushCollection->add($push); 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getEnvironment() 67 | { 68 | return $this->environment; 69 | } 70 | 71 | /** 72 | * @return PushCollection 73 | */ 74 | public function push() 75 | { 76 | /** @var Push $push */ 77 | foreach ($this->pushCollection as $push) { 78 | $adapter = $push->getAdapter(); 79 | $adapter->setEnvironment($this->getEnvironment()); 80 | 81 | if ($adapter->push($push)) { 82 | $push->pushed(); 83 | } 84 | } 85 | 86 | if ($this->pushCollection && !$this->pushCollection->isEmpty()) { 87 | /** @var Push $push */ 88 | $push = $this->pushCollection->first(); 89 | $this->response = $push->getAdapter()->getResponse(); 90 | } 91 | 92 | return $this->pushCollection; 93 | } 94 | 95 | /** 96 | * @param AdapterInterface $adapter Adapter 97 | * 98 | * @return array 99 | * 100 | * @throws AdapterException When the adapter has no dedicated `getFeedback` method 101 | */ 102 | public function getFeedback(AdapterInterface $adapter) 103 | { 104 | if (!$adapter instanceof FeedbackAdapterInterface) { 105 | throw new AdapterException( 106 | sprintf( 107 | '%s adapter has no dedicated "getFeedback" method', 108 | (string) $adapter 109 | ) 110 | ); 111 | } 112 | $adapter->setEnvironment($this->getEnvironment()); 113 | 114 | return $adapter->getFeedback(); 115 | } 116 | 117 | /** 118 | * @return PushCollection 119 | */ 120 | public function getPushCollection() 121 | { 122 | return $this->pushCollection; 123 | } 124 | 125 | /** 126 | * @return ResponseInterface 127 | */ 128 | public function getResponse() 129 | { 130 | return $this->response; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/GcmPushService.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class GcmPushService extends AbstractPushService 26 | { 27 | /** 28 | * @var string 29 | */ 30 | private $apiKey = ''; 31 | 32 | /** 33 | * @param string $environment 34 | * @param string $apiKey 35 | */ 36 | public function __construct($apiKey, $environment = PushManager::ENVIRONMENT_DEV) 37 | { 38 | parent::__construct($environment); 39 | 40 | $this->apiKey = $apiKey; 41 | } 42 | 43 | /** 44 | * params keys 45 | * adapter 46 | * message 47 | * device 48 | * 49 | * @param array $tokens List of targets 50 | * @param array $notifications Message(s) to send to each token 51 | * @param array $params 52 | * @return ResponseInterface 53 | */ 54 | public function push(array $tokens = [], array $notifications = [], array $params = []) 55 | { 56 | if (!$tokens || !$notifications) { 57 | return null; 58 | } 59 | 60 | $adapterParams = []; 61 | $deviceParams = []; 62 | $messageParams = []; 63 | if (isset($params) && !empty($params)) { 64 | if (isset($params['adapter'])) { 65 | $adapterParams = $params['adapter']; 66 | } 67 | 68 | if (isset($params['device'])) { 69 | $deviceParams = $params['device']; 70 | } 71 | 72 | if (isset($params['message'])) { 73 | $messageParams = $params['message']; 74 | } 75 | 76 | //because we have now notification and data separated 77 | if (isset($params['notificationData'])) { 78 | $messageParams['notificationData'] = $params['notificationData']; 79 | } 80 | } 81 | 82 | $adapterParams['apiKey'] = $this->apiKey; 83 | 84 | if (!$this->apiKey) { 85 | throw new \RuntimeException('Android api key must be set'); 86 | } 87 | 88 | // Development one by default (without argument). 89 | $pushManager = new PushManager($this->environment); 90 | 91 | // Then declare an adapter. 92 | $gcmAdapter = new GcmAdapter($adapterParams); 93 | 94 | // Set the device(s) to push the notification to. 95 | $devices = new DeviceCollection([]); 96 | 97 | //devices 98 | foreach ($tokens as $token) { 99 | $devices->add(new Device($token, $deviceParams)); 100 | } 101 | 102 | foreach ($notifications as $notificationText) { 103 | // Then, create the push skel. 104 | $message = new Message($notificationText, $messageParams); 105 | 106 | // Finally, create and add the push to the manager, and push it! 107 | $push = new Push($gcmAdapter, $devices, $message); 108 | $pushManager->add($push); 109 | } 110 | 111 | // Returns a collection of notified devices 112 | $pushes = $pushManager->push(); 113 | 114 | $this->response = $gcmAdapter->getResponse(); 115 | 116 | return $this->response; 117 | } 118 | 119 | /** 120 | * @return array 121 | */ 122 | public function getInvalidTokens() 123 | { 124 | if (!$this->response) { 125 | return []; 126 | } 127 | 128 | $tokens = []; 129 | 130 | foreach ($this->response->getParsedResponses() as $token => $response) { 131 | if (array_key_exists('error', $response) && !array_key_exists('message_id', $response)) { 132 | $tokens[] = $token; 133 | } 134 | } 135 | 136 | return $tokens; 137 | } 138 | 139 | /** 140 | * @return array 141 | */ 142 | public function getSuccessfulTokens() 143 | { 144 | if (!$this->response) { 145 | return []; 146 | } 147 | 148 | $tokens = []; 149 | 150 | foreach ($this->response->getParsedResponses() as $token => $response) { 151 | if (!array_key_exists('error', $response) && array_key_exists('message_id', $response)) { 152 | $tokens[] = $token; 153 | } 154 | } 155 | 156 | return $tokens; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /doc/apns-adapter.md: -------------------------------------------------------------------------------- 1 | # NotificationPusher - Documentation 2 | 3 | ## APNS adapter 4 | 5 | [APNS](http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction.html) adapter is used to push notification to Apple devices. 6 | 7 | ### Basic notification push example 8 | 9 | ``` php 10 | '/path/to/your/apns-certificate.pem', 33 | )); 34 | 35 | // Set the device(s) to push the notification to. 36 | $devices = new DeviceCollection(array( 37 | new Device('Token1'), 38 | // ... 39 | )); 40 | 41 | // Then, create the push skel. 42 | $message = new Message('This is a basic example of push.'); 43 | 44 | // Finally, create and add the push to the manager, and push it! 45 | $push = new Push($apnsAdapter, $devices, $message); 46 | $pushManager->add($push); 47 | $pushManager->push(); 48 | 49 | foreach($push->getResponses() as $token => $response) { 50 | // ... 51 | } 52 | ``` 53 | 54 | ### Custom notification push example 55 | 56 | ``` php 57 | '/path/to/your/apns-certificate.pem', 80 | 'passPhrase' => 'example', 81 | )); 82 | 83 | // Set the device(s) to push the notification to. 84 | $devices = new DeviceCollection(array( 85 | new Device('Token1', array('badge' => 5)), 86 | new Device('Token2', array('badge' => 1)), 87 | new Device('Token3'), 88 | )); 89 | 90 | // Then, create the push skel. 91 | $message = new Message('This is an example.', array( 92 | 'badge' => 1, 93 | 'sound' => 'example.aiff', 94 | 95 | 'actionLocKey' => 'Action button title!', 96 | 'locKey' => 'localized key', 97 | 'locArgs' => array( 98 | 'localized args', 99 | 'localized args', 100 | 'localized args' 101 | ), 102 | 'launchImage' => 'image.jpg', 103 | 104 | 'custom' => array('custom data' => array( 105 | 'we' => 'want', 'send to app' 106 | )) 107 | )); 108 | 109 | // Finally, create and add the push to the manager, and push it! 110 | $push = new Push($apnsAdapter, $devices, $message); 111 | $pushManager->add($push); 112 | $pushManager->push(); // Returns a collection of notified devices 113 | ``` 114 | 115 | ### Feedback example 116 | 117 | The feedback service is used to list tokens of devices which not have your application anymore. 118 | 119 | ``` php 120 | '/path/to/your/apns-certificate.pem', 139 | )); 140 | 141 | $feedback = $pushManager->getFeedback($apnsAdapter); // Returns an array of Token + DateTime couples 142 | ``` 143 | 144 | * [Installation](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/installation.md) 145 | * [Getting started](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/getting-started.md) 146 | * APNS adapter 147 | * [GCM (FCM) adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/gcm-fcm-adapter.md) 148 | * [Create an adapter](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/create-an-adapter.md) 149 | * [Push from CLI](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/push-from-cli.md) 150 | * [Facades](https://github.com/Ph3nol/NotificationPusher/blob/master/doc/facades.md) -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Console/Command/PushCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Console\Command; 13 | 14 | use Doctrine\Common\Inflector\Inflector; 15 | use Exception; 16 | use Sly\NotificationPusher\Adapter\AdapterInterface; 17 | use Sly\NotificationPusher\Exception\AdapterException; 18 | use Sly\NotificationPusher\Model\Device; 19 | use Sly\NotificationPusher\Model\Message; 20 | use Sly\NotificationPusher\Model\Push; 21 | use Sly\NotificationPusher\PushManager; 22 | use Symfony\Component\Console\Command\Command; 23 | use Symfony\Component\Console\Input\InputArgument; 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Input\InputOption; 26 | use Symfony\Component\Console\Output\OutputInterface; 27 | 28 | /** 29 | * @uses \Symfony\Component\Console\Command\Command 30 | * @author Cédric Dugat 31 | */ 32 | class PushCommand extends Command 33 | { 34 | /** 35 | * @see Command 36 | */ 37 | protected function configure() 38 | { 39 | parent::configure(); 40 | 41 | $this 42 | ->setName('push') 43 | ->setDescription('Manual notification push') 44 | ->addArgument( 45 | 'adapter', 46 | InputArgument::REQUIRED, 47 | 'Adapter (apns, gcm, specific class name, ...)' 48 | ) 49 | ->addArgument( 50 | 'token', 51 | InputArgument::REQUIRED, 52 | 'Device Token or Registration ID' 53 | ) 54 | ->addArgument( 55 | 'message', 56 | InputArgument::REQUIRED, 57 | 'Message' 58 | ) 59 | ->addOption( 60 | 'certificate', 61 | null, 62 | InputOption::VALUE_OPTIONAL, 63 | 'Certificate path (for APNS adapter)' 64 | ) 65 | ->addOption( 66 | 'api-key', 67 | null, 68 | InputOption::VALUE_OPTIONAL, 69 | 'API key (for GCM adapter)' 70 | ) 71 | ->addOption( 72 | 'env', 73 | PushManager::ENVIRONMENT_DEV, 74 | InputOption::VALUE_OPTIONAL, 75 | sprintf( 76 | 'Environment (%s, %s)', 77 | PushManager::ENVIRONMENT_DEV, 78 | PushManager::ENVIRONMENT_PROD 79 | ) 80 | ); 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | protected function execute(InputInterface $input, OutputInterface $output) 87 | { 88 | $adapter = $this->getReadyAdapter($input, $output); 89 | $pushManager = new PushManager($input->getOption('env')); 90 | $message = new Message($input->getArgument('message')); 91 | $push = new Push($adapter, new Device($input->getArgument('token')), $message); 92 | $pushManager->add($push); 93 | 94 | $pushManager->push(); 95 | return 0; 96 | } 97 | 98 | /** 99 | * @param string $argument Given argument 100 | * 101 | * @return string 102 | * 103 | * @throws AdapterException When given adapter class doesn't exist 104 | */ 105 | private function getAdapterClassFromArgument($argument) 106 | { 107 | if (!class_exists($adapterClass = $argument) && 108 | !class_exists($adapterClass = '\\Sly\\NotificationPusher\\Adapter\\' . ucfirst($argument))) { 109 | throw new AdapterException( 110 | sprintf( 111 | 'Adapter class %s does not exist', 112 | $adapterClass 113 | ) 114 | ); 115 | } 116 | 117 | return $adapterClass; 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | * 123 | * @return AdapterInterface 124 | */ 125 | private function getReadyAdapter(InputInterface $input, OutputInterface $output) 126 | { 127 | $adapterClass = $this->getAdapterClassFromArgument($input->getArgument('adapter')); 128 | 129 | try { 130 | $adapter = new $adapterClass(); 131 | } catch (Exception $e) { 132 | $adapterData = []; 133 | preg_match_all('/"(.*)"/i', $e->getMessage(), $matches); 134 | 135 | foreach ($matches[1] as $match) { 136 | $optionKey = str_replace('_', '-', Inflector::tableize($match)); 137 | $option = $input->getOption($optionKey); 138 | 139 | if (!$option) { 140 | throw new AdapterException( 141 | sprintf( 142 | 'The option "%s" is needed by %s adapter', 143 | $optionKey, 144 | $adapterClass 145 | ) 146 | ); 147 | } 148 | 149 | $adapterData[$match] = $option; 150 | } 151 | 152 | $adapter = new $adapterClass($adapterData); 153 | } 154 | 155 | return $adapter; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Model/Push.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Model; 13 | 14 | use Sly\NotificationPusher\Adapter\AdapterInterface; 15 | use Sly\NotificationPusher\Collection\DeviceCollection; 16 | use Sly\NotificationPusher\Collection\ResponseCollection; 17 | use Sly\NotificationPusher\Exception\AdapterException; 18 | 19 | /** 20 | * @author Cédric Dugat 21 | */ 22 | class Push extends BaseOptionedModel implements PushInterface 23 | { 24 | 25 | /** 26 | * @var string 27 | */ 28 | private $status; 29 | 30 | /** 31 | * @var AdapterInterface 32 | */ 33 | private $adapter; 34 | 35 | /** 36 | * @var MessageInterface 37 | */ 38 | private $message; 39 | 40 | /** 41 | * @var DeviceCollection 42 | */ 43 | private $devices; 44 | 45 | /** 46 | * @var \DateTime 47 | */ 48 | private $pushedAt; 49 | 50 | /** 51 | * @var ResponseCollection 52 | */ 53 | private $responses; 54 | 55 | /** 56 | * @param AdapterInterface $adapter Adapter 57 | * @param DeviceInterface|DeviceCollection $devices Device(s) 58 | * @param MessageInterface $message Message 59 | * @param array $options Options 60 | * 61 | * Options are adapters specific ones, like Apns "badge" or "sound" option for example. 62 | * Of course, they can be more general. 63 | * 64 | * @throws AdapterException 65 | */ 66 | public function __construct(AdapterInterface $adapter, $devices, MessageInterface $message, array $options = []) 67 | { 68 | if ($devices instanceof DeviceInterface) { 69 | $devices = new DeviceCollection([$devices]); 70 | } 71 | 72 | $this->adapter = $adapter; 73 | $this->devices = $devices; 74 | $this->message = $message; 75 | $this->options = $options; 76 | $this->status = self::STATUS_PENDING; 77 | 78 | $this->checkDevicesTokens(); 79 | } 80 | 81 | /** 82 | * @throws AdapterException 83 | */ 84 | private function checkDevicesTokens() 85 | { 86 | $devices = $this->getDevices(); 87 | $adapter = $this->getAdapter(); 88 | 89 | foreach ($devices as $device) { 90 | if (false === $adapter->supports($device->getToken())) { 91 | throw new AdapterException( 92 | sprintf( 93 | 'Adapter %s does not support %s token\'s device', 94 | (string) $adapter, 95 | $device->getToken() 96 | ) 97 | ); 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * @return string 104 | */ 105 | public function getStatus() 106 | { 107 | return $this->status; 108 | } 109 | 110 | /** 111 | * @param string $status Status 112 | * 113 | * @return PushInterface 114 | */ 115 | public function setStatus($status) 116 | { 117 | $this->status = $status; 118 | 119 | return $this; 120 | } 121 | 122 | /** 123 | * @return boolean 124 | */ 125 | public function isPushed() 126 | { 127 | return (bool) (self::STATUS_PUSHED === $this->status); 128 | } 129 | 130 | /** 131 | * @return PushInterface 132 | */ 133 | public function pushed() 134 | { 135 | $this->status = self::STATUS_PUSHED; 136 | $this->pushedAt = new \DateTime(); 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * @return AdapterInterface 143 | */ 144 | public function getAdapter() 145 | { 146 | return $this->adapter; 147 | } 148 | 149 | /** 150 | * @param AdapterInterface $adapter Adapter 151 | * 152 | * @return PushInterface 153 | */ 154 | public function setAdapter(AdapterInterface $adapter) 155 | { 156 | $this->adapter = $adapter; 157 | 158 | return $this; 159 | } 160 | 161 | /** 162 | * @return MessageInterface 163 | */ 164 | public function getMessage() 165 | { 166 | return $this->message; 167 | } 168 | 169 | /** 170 | * @param MessageInterface $message Message 171 | * 172 | * @return PushInterface 173 | */ 174 | public function setMessage(MessageInterface $message) 175 | { 176 | $this->message = $message; 177 | 178 | return $this; 179 | } 180 | 181 | /** 182 | * @return DeviceCollection 183 | */ 184 | public function getDevices() 185 | { 186 | return $this->devices; 187 | } 188 | 189 | /** 190 | * @param DeviceCollection $devices Devices 191 | * 192 | * @return PushInterface 193 | */ 194 | public function setDevices(DeviceCollection $devices) 195 | { 196 | $this->devices = $devices; 197 | 198 | $this->checkDevicesTokens(); 199 | 200 | return $this; 201 | } 202 | 203 | /** 204 | * @return ResponseCollection 205 | */ 206 | public function getResponses() 207 | { 208 | if (!$this->responses) 209 | $this->responses = new ResponseCollection(); 210 | 211 | return $this->responses; 212 | } 213 | 214 | /** 215 | * @param DeviceInterface $device 216 | * @param mixed $response 217 | */ 218 | public function addResponse(DeviceInterface $device, $response) 219 | { 220 | $this->getResponses()->add($device->getToken(), $response); 221 | } 222 | 223 | /** 224 | * @return \DateTime 225 | */ 226 | public function getPushedAt() 227 | { 228 | return $this->pushedAt; 229 | } 230 | 231 | /** 232 | * @param \DateTime $pushedAt PushedAt 233 | * 234 | * @return PushInterface 235 | */ 236 | public function setPushedAt(\DateTime $pushedAt) 237 | { 238 | $this->pushedAt = $pushedAt; 239 | 240 | return $this; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/ApnsPushService.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class ApnsPushService extends AbstractPushService 24 | { 25 | /** 26 | * @var string 27 | */ 28 | private $certificatePath = ''; 29 | 30 | /** 31 | * @var string|null 32 | */ 33 | private $passPhrase = ''; 34 | 35 | /** 36 | * @var array 37 | */ 38 | private $feedback = []; 39 | 40 | /** 41 | * IOSPushNotificationService constructor. 42 | * @param string $environment 43 | * @param string $certificatePath 44 | * @param string $passPhrase 45 | */ 46 | public function __construct($certificatePath, $passPhrase = null, $environment = PushManager::ENVIRONMENT_DEV) 47 | { 48 | parent::__construct($environment); 49 | 50 | $this->certificatePath = $certificatePath; 51 | $this->passPhrase = $passPhrase; 52 | } 53 | 54 | /** 55 | * @param array $tokens List of targets 56 | * @param array $notifications Message(s) to send to each token 57 | * @param array $params 58 | * @return ResponseInterface 59 | */ 60 | public function push(array $tokens = [], array $notifications = [], array $params = []) 61 | { 62 | if (!$tokens || !$notifications) { 63 | return null; 64 | } 65 | 66 | if (!$this->certificatePath) { 67 | throw new \RuntimeException('IOS certificate path must be set'); 68 | } 69 | 70 | $fs = new Filesystem(); 71 | if (!$fs->exists($this->certificatePath) || !is_readable($this->certificatePath)) { 72 | throw new \InvalidArgumentException('Wrong or not readable certificate path'); 73 | } 74 | 75 | $adapterParams = []; 76 | $deviceParams = []; 77 | $messageParams = []; 78 | if (isset($params) && !empty($params)) { 79 | if (isset($params['adapter'])) { 80 | $adapterParams = $params['adapter']; 81 | } 82 | 83 | if (isset($params['device'])) { 84 | $deviceParams = $params['device']; 85 | } 86 | 87 | if (isset($params['message'])) { 88 | $messageParams = $params['message']; 89 | } 90 | } 91 | 92 | $adapterParams['certificate'] = $this->certificatePath; 93 | $adapterParams['passPhrase'] = $this->passPhrase; 94 | 95 | // Development one by default (without argument). 96 | $pushManager = new PushManager($this->environment); 97 | 98 | // Then declare an adapter. 99 | $apnsAdapter = new ApnsAdapter($adapterParams); 100 | 101 | // Set the device(s) to push the notification to. 102 | $devices = new DeviceCollection([]); 103 | 104 | //devices 105 | foreach ($tokens as $token) { 106 | $devices->add(new Device($token, $deviceParams)); 107 | } 108 | 109 | foreach ($notifications as $notificationText) { 110 | // Then, create the push skel. 111 | $message = new Message($notificationText, $messageParams); 112 | 113 | // Finally, create and add the push to the manager, and push it! 114 | $push = new Push($apnsAdapter, $devices, $message); 115 | $pushManager->add($push); 116 | } 117 | 118 | // Returns a collection of notified devices 119 | $pushes = $pushManager->push(); 120 | 121 | $this->response = $apnsAdapter->getResponse(); 122 | $this->feedback = []; 123 | 124 | return $this->response; 125 | } 126 | 127 | /** 128 | * Use feedback to get not registered tokens from last send 129 | * and remove them from your DB 130 | * 131 | * @return array 132 | */ 133 | public function feedback() 134 | { 135 | $adapterParams = []; 136 | $adapterParams['certificate'] = $this->certificatePath; 137 | $adapterParams['passPhrase'] = $this->passPhrase; 138 | 139 | // Development one by default (without argument). 140 | /** @var PushManager $pushManager */ 141 | $pushManager = new PushManager($this->environment); 142 | 143 | // Then declare an adapter. 144 | $apnsAdapter = new ApnsAdapter($adapterParams); 145 | 146 | $this->feedback = $pushManager->getFeedback($apnsAdapter); 147 | 148 | return $this->feedback; 149 | } 150 | 151 | /** 152 | * The Apple Push Notification service includes a feedback service to give you information 153 | * about failed remote notifications. When a remote notification cannot be delivered 154 | * because the intended app does not exist on the device, 155 | * the feedback service adds that device’s token to its list. 156 | * 157 | * @return array 158 | */ 159 | public function getFeedback() 160 | { 161 | return $this->feedback; 162 | } 163 | 164 | /** 165 | * @return array 166 | */ 167 | public function getInvalidTokens() 168 | { 169 | if (!$this->response) { 170 | return []; 171 | } 172 | 173 | if (!$this->feedback) { 174 | $this->feedback = $this->feedback(); 175 | } 176 | 177 | $feedbackTokens = array_keys($this->feedback); 178 | 179 | //all bad 180 | if ($feedbackTokens) { 181 | return $feedbackTokens; 182 | } 183 | 184 | return []; 185 | } 186 | 187 | /** 188 | * @return array 189 | */ 190 | public function getSuccessfulTokens() 191 | { 192 | if (!$this->response) { 193 | return []; 194 | } 195 | 196 | if (!$this->feedback) { 197 | $this->feedback = $this->feedback(); 198 | } 199 | 200 | $feedbackTokens = array_keys($this->feedback); 201 | $sentTokens = array_keys($this->response->getParsedResponses()); 202 | 203 | //all bad 204 | if (!$feedbackTokens) { 205 | return $sentTokens; 206 | } 207 | 208 | $tokens = array_diff($sentTokens, $feedbackTokens); 209 | 210 | return $tokens; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Model/Push.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Push extends Units\Test 20 | { 21 | const APNS_TOKEN_EXAMPLE = '111db24975bb6c6b63214a8d268052aa0a965cc1e32110ab06a72b19074c2222'; 22 | const GCM_TOKEN_EXAMPLE = 'AAA91bG9ISdL94D55C69NplFlxicy0iFUFTyWh3AAdMfP9npH5r_JQFTo27xpX1jfqGf-aSe6xZAsfWRefjazJpqFt03Isanv-Fi97020EKLye0ApTkHsw_0tJJzgA2Js0NsG1jLWsiJf63YSF8ropAcRp4BSxVBBB'; 23 | 24 | public function testConstructWithOneDevice() 25 | { 26 | $this->if($this->mockClass(AdapterInterface::class, '\Mock')) 27 | ->and($adapter = new \Mock\AdapterInterface()) 28 | ->and($devices = new DeviceModel('Token1')) 29 | ->and($message = new MessageModel('Test')) 30 | ->and($object = new TestedModel($adapter, $devices, $message)) 31 | ->object($object->getDevices()) 32 | ->isInstanceOf(DeviceCollection::class) 33 | ->integer($object->getDevices()->count()) 34 | ->isEqualTo(1) 35 | ->array($object->getOptions()) 36 | ->isEmpty(); 37 | } 38 | 39 | public function testConstructWithManyDevicesAndOptions() 40 | { 41 | $this->if($this->mockClass(AdapterInterface::class, '\Mock')) 42 | ->and($adapter = new \Mock\AdapterInterface()) 43 | ->and($devices = new DeviceCollection([new DeviceModel('Token1'), new DeviceModel('Token2'), 44 | new DeviceModel('Token3')])) 45 | ->and($message = new MessageModel('Test')) 46 | ->and($object = new TestedModel($adapter, $devices, $message, ['param' => 'test'])) 47 | ->object($object->getDevices()) 48 | ->isInstanceOf(DeviceCollection::class) 49 | ->integer($object->getDevices()->count()) 50 | ->isEqualTo(3) 51 | ->array($object->getOptions()) 52 | ->hasKey('param') 53 | ->contains('test') 54 | ->size 55 | ->isEqualTo(1); 56 | } 57 | 58 | public function testStatus() 59 | { 60 | date_default_timezone_set('UTC'); 61 | $this->if($this->mockClass(AdapterInterface::class, '\Mock')) 62 | ->and($adapter = new \Mock\AdapterInterface()) 63 | ->and($devices = new DeviceCollection([new DeviceModel('Token1'), new DeviceModel('Token2'), 64 | new DeviceModel('Token3')])) 65 | ->and($message = new MessageModel('Test')) 66 | ->and($object = new TestedModel($adapter, $devices, $message)) 67 | ->string($object->getStatus()) 68 | ->isEqualTo(TestedModel::STATUS_PENDING) 69 | ->boolean($object->isPushed()) 70 | ->isFalse() 71 | ->when($object->pushed()) 72 | ->and($dt = new \DateTime()) 73 | ->string($object->getStatus()) 74 | ->isEqualTo(TestedModel::STATUS_PUSHED) 75 | ->boolean($object->isPushed()) 76 | ->isTrue() 77 | ->dateTime($object->getPushedAt()) 78 | ->hasDate($dt->format("Y"), $dt->format("m"), $dt->format('d')) 79 | ->when($object->setStatus(TestedModel::STATUS_PENDING)) 80 | ->string($object->getStatus()) 81 | ->isEqualTo(TestedModel::STATUS_PENDING) 82 | ->boolean($object->isPushed()) 83 | ->isFalse() 84 | ->when($fDt = new \DateTime('2013-01-01')) 85 | ->and($object->setPushedAt($fDt)) 86 | ->dateTime($object->getPushedAt()) 87 | ->isIdenticalTo($fDt); 88 | } 89 | 90 | public function testDevicesTokensCheck() 91 | { 92 | $this->if($this->mockGenerator()->orphanize('__construct')) 93 | ->and($this->mockClass(Apns::class, '\Mock')) 94 | ->and($this->mockGenerator()->orphanize('__construct')) 95 | ->and($this->mockClass(Gcm::class, '\Mock')) 96 | ->and($apnsAdapter = new \mock\Apns()) 97 | ->and($gcmAdapter = new \mock\Gcm()) 98 | ->and($badDevice = new DeviceModel('BadToken')) 99 | ->and($message = new MessageModel('Test')) 100 | ->exception(function () use ($apnsAdapter, $badDevice, $message) { 101 | $object = new TestedModel($apnsAdapter, $badDevice, $message); 102 | }) 103 | ->isInstanceOf(AdapterException::class) 104 | ->when($goodDevice = new DeviceModel(self::APNS_TOKEN_EXAMPLE)) 105 | ->object($object = new TestedModel($apnsAdapter, $goodDevice, $message)); 106 | } 107 | 108 | public function testAdapter() 109 | { 110 | $this->if($this->mockGenerator()->orphanize('__construct')) 111 | ->and($this->mockClass(Apns::class, '\Mock')) 112 | ->and($this->mockGenerator()->orphanize('__construct')) 113 | ->and($this->mockClass(Gcm::class, '\Mock')) 114 | ->and($apnsAdapter = new \mock\Apns()) 115 | ->and($gcmAdapter = new \mock\Gcm()) 116 | ->and($devices = new DeviceModel(self::APNS_TOKEN_EXAMPLE)) 117 | ->and($message = new MessageModel('Test')) 118 | ->and($object = new TestedModel($apnsAdapter, $devices, $message)) 119 | ->object($object->getAdapter()) 120 | ->isInstanceOf(Apns::class) 121 | ->when($object->setAdapter($gcmAdapter)) 122 | ->and($object->setDevices(new DeviceCollection([new DeviceModel(self::GCM_TOKEN_EXAMPLE)]))) 123 | ->object($object->getAdapter()) 124 | ->isInstanceOf(Gcm::class); 125 | } 126 | 127 | public function testMessage() 128 | { 129 | $this->if($this->mockClass(AdapterInterface::class, '\Mock')) 130 | ->and($adapter = new \Mock\AdapterInterface()) 131 | ->and($devices = new DeviceCollection([new DeviceModel('Token1'), new DeviceModel('Token2'), 132 | new DeviceModel('Token3')])) 133 | ->and($message = new MessageModel('Test')) 134 | ->and($object = new TestedModel($adapter, $devices, $message)) 135 | ->object($object->getMessage()) 136 | ->isInstanceOf(MessageModel::class) 137 | ->string($object->getMessage()->getText()) 138 | ->isEqualTo('Test') 139 | ->when($object->setMessage(new MessageModel('Test 2'))) 140 | ->object($object->getMessage()) 141 | ->isInstanceOf(MessageModel::class) 142 | ->string($object->getMessage()->getText()) 143 | ->isEqualTo('Test 2'); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Adapter/Gcm.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Adapter; 13 | 14 | use InvalidArgumentException; 15 | use Sly\NotificationPusher\Collection\DeviceCollection; 16 | use Sly\NotificationPusher\Exception\PushException; 17 | use Sly\NotificationPusher\Model\BaseOptionedModel; 18 | use Sly\NotificationPusher\Model\DeviceInterface; 19 | use Sly\NotificationPusher\Model\GcmMessage; 20 | use Sly\NotificationPusher\Model\MessageInterface; 21 | use Sly\NotificationPusher\Model\PushInterface; 22 | use Zend\Http\Client as HttpClient; 23 | use Zend\Http\Client\Adapter\Socket as HttpSocketAdapter; 24 | use ZendService\Google\Exception\InvalidArgumentException as ZendInvalidArgumentException; 25 | use ZendService\Google\Exception\RuntimeException as ServiceRuntimeException; 26 | use ZendService\Google\Gcm\Client as ServiceClient; 27 | use ZendService\Google\Gcm\Message as ServiceMessage; 28 | use ZendService\Google\Gcm\Response; 29 | 30 | /** 31 | * @uses \Sly\NotificationPusher\Adapter\BaseAdapter 32 | * 33 | * @author Cédric Dugat 34 | */ 35 | class Gcm extends BaseAdapter 36 | { 37 | /** 38 | * @var HttpClient 39 | */ 40 | private $httpClient; 41 | 42 | /** 43 | * @var ServiceClient 44 | */ 45 | private $openedClient; 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function supports($token) 51 | { 52 | return is_string($token) && $token !== ''; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | * 58 | * @throws PushException 59 | */ 60 | public function push(PushInterface $push) 61 | { 62 | $client = $this->getOpenedClient(); 63 | $pushedDevices = new DeviceCollection(); 64 | $tokens = array_chunk($push->getDevices()->getTokens(), 100); 65 | 66 | foreach ($tokens as $tokensRange) { 67 | $message = $this->getServiceMessageFromOrigin($tokensRange, $push->getMessage()); 68 | 69 | try { 70 | 71 | /** @var Response $response */ 72 | $response = $client->send($message); 73 | $responseResults = $response->getResults(); 74 | 75 | foreach ($tokensRange as $token) { 76 | /** @var DeviceInterface $device */ 77 | $device = $push->getDevices()->get($token); 78 | 79 | // map the overall response object 80 | // into a per device response 81 | $tokenResponse = []; 82 | if (isset($responseResults[$token]) && is_array($responseResults[$token])) { 83 | $tokenResponse = $responseResults[$token]; 84 | } 85 | 86 | $responseData = $response->getResponse(); 87 | if ($responseData && is_array($responseData)) { 88 | $tokenResponse = array_merge( 89 | $tokenResponse, 90 | array_diff_key($responseData, ['results' => true]) 91 | ); 92 | } 93 | 94 | $push->addResponse($device, $tokenResponse); 95 | 96 | $pushedDevices->add($device); 97 | 98 | $this->response->addOriginalResponse($device, $response); 99 | $this->response->addParsedResponse($device, $tokenResponse); 100 | } 101 | } catch (ServiceRuntimeException $e) { 102 | throw new PushException($e->getMessage()); 103 | } 104 | } 105 | 106 | return $pushedDevices; 107 | } 108 | 109 | /** 110 | * Get opened client. 111 | * 112 | * @return ServiceClient 113 | */ 114 | public function getOpenedClient() 115 | { 116 | if (!isset($this->openedClient)) { 117 | $this->openedClient = new ServiceClient(); 118 | $this->openedClient->setApiKey($this->getParameter('apiKey')); 119 | 120 | $newClient = new HttpClient( 121 | null, 122 | [ 123 | 'adapter' => 'Zend\Http\Client\Adapter\Socket', 124 | 'sslverifypeer' => false, 125 | ] 126 | ); 127 | 128 | $this->openedClient->setHttpClient($newClient); 129 | } 130 | 131 | return $this->openedClient; 132 | } 133 | 134 | /** 135 | * Get service message from origin. 136 | * 137 | * @param array $tokens Tokens 138 | * @param BaseOptionedModel|MessageInterface $message Message 139 | * 140 | * @return ServiceMessage 141 | * @throws ZendInvalidArgumentException 142 | */ 143 | public function getServiceMessageFromOrigin(array $tokens, BaseOptionedModel $message) 144 | { 145 | $data = $message->getOptions(); 146 | $data['message'] = $message->getText(); 147 | 148 | $serviceMessage = new ServiceMessage(); 149 | $serviceMessage->setRegistrationIds($tokens); 150 | 151 | if (isset($data['notificationData']) && !empty($data['notificationData'])) { 152 | $serviceMessage->setNotification($data['notificationData']); 153 | unset($data['notificationData']); 154 | } 155 | 156 | if ($message instanceof GcmMessage) { 157 | $serviceMessage->setNotification($message->getNotificationData()); 158 | } 159 | 160 | $serviceMessage->setData($data); 161 | 162 | $serviceMessage->setCollapseKey($this->getParameter('collapseKey')); 163 | $serviceMessage->setPriority($this->getParameter('priority', 'normal')); 164 | $serviceMessage->setRestrictedPackageName($this->getParameter('restrictedPackageName')); 165 | $serviceMessage->setDelayWhileIdle($this->getParameter('delayWhileIdle', false)); 166 | $serviceMessage->setTimeToLive($this->getParameter('ttl', 600)); 167 | $serviceMessage->setDryRun($this->getParameter('dryRun', false)); 168 | 169 | return $serviceMessage; 170 | } 171 | 172 | /** 173 | * {@inheritdoc} 174 | */ 175 | public function getDefinedParameters() 176 | { 177 | return [ 178 | 'collapseKey', 179 | 'priority', 180 | 'delayWhileIdle', 181 | 'ttl', 182 | 'restrictedPackageName', 183 | 'dryRun', 184 | ]; 185 | } 186 | 187 | /** 188 | * {@inheritdoc} 189 | */ 190 | public function getDefaultParameters() 191 | { 192 | return []; 193 | } 194 | 195 | /** 196 | * {@inheritdoc} 197 | */ 198 | public function getRequiredParameters() 199 | { 200 | return ['apiKey']; 201 | } 202 | 203 | /** 204 | * Get the current Zend Http Client instance. 205 | * 206 | * @return HttpClient 207 | */ 208 | public function getHttpClient() 209 | { 210 | return $this->httpClient; 211 | } 212 | 213 | /** 214 | * Overrides the default Http Client. 215 | * 216 | * @param HttpClient $client 217 | */ 218 | public function setHttpClient(HttpClient $client) 219 | { 220 | $this->httpClient = $client; 221 | } 222 | 223 | /** 224 | * Send custom parameters to the Http Adapter without overriding the Http Client. 225 | * 226 | * @param array $config 227 | * 228 | * @throws InvalidArgumentException 229 | */ 230 | public function setAdapterParameters(array $config = []) 231 | { 232 | if (!is_array($config) || empty($config)) { 233 | throw new InvalidArgumentException('$config must be an associative array with at least 1 item.'); 234 | } 235 | 236 | if ($this->httpClient === null) { 237 | $this->httpClient = new HttpClient(); 238 | $this->httpClient->setAdapter(new HttpSocketAdapter()); 239 | } 240 | 241 | $this->httpClient->getAdapter()->setOptions($config); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Adapter/Gcm.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class Gcm extends Units\Test 24 | { 25 | const GCM_TOKEN_EXAMPLE = 'AAA91bG9ISdL94D55C69NplFlxicy0iFUFTyWh3AAdMfP9npH5r_JQFTo27xpX1jfqGf-aSe6xZAsfWRefjazJpqFt03Isanv-Fi97020EKLye0ApTkHsw_0tJJzgA2Js0NsG1jLWsiJf63YSF8ropAcRp4BSxVBBB'; 26 | // The format of GCM tokens apparently have changed, this string looks similar to new format: 27 | const ALT_GCM_TOKEN_EXAMPLE = 'AAA91bG9ISd:L94D55C69NplFlxicy0iFUFTyWh3AAdMfP9npH5r_JQFTo27xpX1jfqGf-aSe6xZAsfWRefjazJpqFt03Isanv-Fi97020EKLye0ApTkHsw_0tJJzgA2Js0NsG1jLWsiJf63YSF8ropA'; 28 | 29 | public function testConstruct() 30 | { 31 | $this 32 | ->exception(function () { 33 | $object = new TestedModel(); 34 | }) 35 | ->isInstanceOf(MissingOptionsException::class) 36 | ->message 37 | ->contains('apiKey') 38 | ->when($this->mockGenerator()->orphanize('__construct')) 39 | ->and($this->mockClass(TestedModel::class, '\Mock')) 40 | ->and($object = new \Mock\Gcm()) 41 | ->and($object->setParameters(['apiKey' => 'test'])) 42 | ->array($object->getParameters()) 43 | ->isNotEmpty() 44 | ->hasSize(1) 45 | ->string($object->getParameter('apiKey')) 46 | ->isEqualTo('test'); 47 | } 48 | 49 | public function testSupports() 50 | { 51 | $this->if($this->mockGenerator()->orphanize('__construct')) 52 | ->and($this->mockClass(TestedModel::class, '\Mock')) 53 | ->and($object = new \Mock\Gcm()) 54 | ->boolean($object->supports('')) // Test empty string 55 | ->isFalse() 56 | ->boolean($object->supports(2)) // Test a number 57 | ->isFalse() 58 | ->boolean($object->supports([])) // Test an array 59 | ->isFalse() 60 | ->boolean($object->supports(json_decode('{}'))) // Tests an object 61 | ->isFalse() 62 | ->boolean($object->supports(self::GCM_TOKEN_EXAMPLE)) 63 | ->isTrue() 64 | ->boolean($object->supports(self::ALT_GCM_TOKEN_EXAMPLE)) 65 | ->isTrue(); 66 | } 67 | 68 | public function testDefinedParameters() 69 | { 70 | $this->if($this->mockGenerator()->orphanize('__construct')) 71 | ->and($this->mockClass(TestedModel::class, '\Mock')) 72 | ->and($object = new \Mock\Gcm()) 73 | ->array($definedParameters = $object->getDefinedParameters()) 74 | ->isNotEmpty() 75 | ->containsValues([ 76 | 'collapseKey', 77 | 'delayWhileIdle', 78 | 'ttl', 79 | 'restrictedPackageName', 80 | 'dryRun', 81 | ]); 82 | } 83 | 84 | public function testDefaultParameters() 85 | { 86 | $this->if($this->mockGenerator()->orphanize('__construct')) 87 | ->and($this->mockClass(TestedModel::class, '\Mock')) 88 | ->and($object = new \Mock\Gcm()) 89 | ->array($defaultParameters = $object->getDefaultParameters()) 90 | ->isEmpty(); 91 | } 92 | 93 | public function testRequiredParameters() 94 | { 95 | $this->if($this->mockGenerator()->orphanize('__construct')) 96 | ->and($this->mockClass(TestedModel::class, '\Mock')) 97 | ->and($object = new \Mock\Gcm()) 98 | ->array($requiredParameters = $object->getRequiredParameters()) 99 | ->isNotEmpty() 100 | ->contains('apiKey'); 101 | } 102 | 103 | public function testGetOpenedClient() 104 | { 105 | $this->if($this->mockGenerator()->orphanize('__construct')) 106 | ->and($this->mockClass(TestedModel::class, '\Mock')) 107 | ->and($object = new \Mock\Gcm()) 108 | ->and($this->mockGenerator()->orphanize('__construct')) 109 | ->and($this->mockGenerator()->orphanize('open')) 110 | ->and($this->mockClass(ZendServiceClient::class, '\Mock\ZendService')) 111 | ->and($serviceClient = new \Mock\ZendService\Client()) 112 | ->and($object->getMockController()->getParameters = []) 113 | ->exception(function () use ($object) { 114 | $object->getOpenedClient(new ZendServiceClient()); 115 | }) 116 | ->isInstanceOf(InvalidArgumentException::class) 117 | ->message 118 | ->contains('The api key must be a string and not empty') 119 | ->when($object = new TestedModel(['apiKey' => 'test'])) 120 | ->and($object->getOpenedClient($serviceClient)); 121 | } 122 | 123 | public function testGetServiceMessageFromOrigin() 124 | { 125 | $this->if($this->mockGenerator()->orphanize('__construct')) 126 | ->and($this->mockClass(TestedModel::class, '\Mock')) 127 | ->and($object = new \Mock\Gcm()) 128 | ->and($this->mockGenerator()->orphanize('__construct')) 129 | ->and($this->mockClass(Message::class, '\Mock')) 130 | ->and($message = new \Mock\Message()) 131 | ->and($message->getMockController()->getOptions = [ 132 | 'param' => 'test', 133 | 'notificationData' => ['some' => 'foobar'], 134 | ]) 135 | ->and($message->getMockController()->getText = 'Test') 136 | ->object($originalMessage = $object->getServiceMessageFromOrigin([self::GCM_TOKEN_EXAMPLE], $message)) 137 | ->isInstanceOf(ZendServiceMessage::class) 138 | ->array($originalMessage->getData()) 139 | ->notHasKey('notificationData') 140 | ->array($originalMessage->getNotification()) 141 | ->hasKey('some') 142 | ->contains('foobar'); 143 | } 144 | 145 | public function testGcmMessageUse() 146 | { 147 | $this->if($this->mockGenerator()->orphanize('__construct')) 148 | ->and($this->mockClass(TestedModel::class, '\Mock')) 149 | ->and($object = new \Mock\Gcm()) 150 | ->and($this->mockGenerator()->orphanize('__construct')) 151 | ->and($this->mockClass(GcmMessage::class, '\Mock')) 152 | ->and($message = new \Mock\GcmMessage()) 153 | ->and($message->getMockController()->getNotificationData = [ 154 | 'some' => 'foobar', 155 | ]) 156 | ->and($message->getMockController()->getText = 'Test') 157 | ->object($originalMessage = $object->getServiceMessageFromOrigin([self::GCM_TOKEN_EXAMPLE], $message)) 158 | ->isInstanceOf(ZendServiceMessage::class) 159 | ->array($originalMessage->getData()) 160 | ->notHasKey('notificationData') 161 | ->array($originalMessage->getNotification()) 162 | ->hasKey('some') 163 | ->contains('foobar'); 164 | } 165 | 166 | public function testPush() 167 | { 168 | $this->if($this->mockGenerator()->orphanize('__construct')) 169 | ->and($this->mockClass(TestedModel::class, '\Mock')) 170 | ->and($object = new \Mock\Gcm()) 171 | ->and($object->setResponse(new Response())) 172 | ->and($this->mockClass(ZendResponseAlias::class, '\Mock\ZendService')) 173 | ->and($serviceResponse = new \Mock\ZendService\Response()) 174 | ->and($this->mockGenerator()->orphanize('__construct')) 175 | ->and($this->mockGenerator()->orphanize('open')) 176 | ->and($this->mockGenerator()->orphanize('send')) 177 | ->and($this->mockClass(ZendServiceClient::class, '\Mock\ZendService')) 178 | ->and($serviceClient = new \Mock\ZendService\Message()) 179 | ->and($serviceClient->getMockController()->send = new $serviceResponse) 180 | ->and($this->mockGenerator()->orphanize('__construct')) 181 | ->and($this->mockClass(Push::class, '\Mock')) 182 | ->and($push = new \Mock\Push()) 183 | ->and($push->getMockController()->getMessage = new Message('Test')) 184 | ->and($push->getMockController()->getDevices = new DeviceCollection([new Device(self::GCM_TOKEN_EXAMPLE)])) 185 | ->and($object->getMockController()->getServiceMessageFromOrigin = new ZendServiceMessage()) 186 | ->and($object->getMockController()->getOpenedClient = $serviceClient) 187 | ->object($object->push($push)) 188 | ->isInstanceOf(DeviceCollection::class) 189 | ->hasSize(1); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/Sly/NotificationPusher/Adapter/Apns.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Sly\NotificationPusher\Adapter; 13 | 14 | use Sly\NotificationPusher\Collection\DeviceCollection; 15 | use Sly\NotificationPusher\Exception\AdapterException; 16 | use Sly\NotificationPusher\Exception\PushException; 17 | use Sly\NotificationPusher\Model\BaseOptionedModel; 18 | use Sly\NotificationPusher\Model\DeviceInterface; 19 | use Sly\NotificationPusher\Model\MessageInterface; 20 | use Sly\NotificationPusher\Model\PushInterface; 21 | use ZendService\Apple\Apns\Client\AbstractClient as ServiceAbstractClient; 22 | use ZendService\Apple\Apns\Client\Feedback as ServiceFeedbackClient; 23 | use ZendService\Apple\Apns\Client\Message as ServiceClient; 24 | use ZendService\Apple\Apns\Message as ServiceMessage; 25 | use ZendService\Apple\Apns\Message\Alert as ServiceAlert; 26 | use ZendService\Apple\Apns\Response\Feedback; 27 | use ZendService\Apple\Apns\Response\Message as ServiceResponse; 28 | 29 | /** 30 | * @uses \Sly\NotificationPusher\Adapter\BaseAdapter 31 | * 32 | * @author Cédric Dugat 33 | */ 34 | class Apns extends BaseAdapter implements FeedbackAdapterInterface 35 | { 36 | 37 | /** 38 | * @var ServiceClient 39 | */ 40 | private $openedClient; 41 | 42 | /** 43 | * @var ServiceFeedbackClient 44 | */ 45 | private $feedbackClient; 46 | 47 | /** 48 | * {@inheritdoc} 49 | * 50 | * @throws AdapterException 51 | */ 52 | public function __construct(array $parameters = []) 53 | { 54 | parent::__construct($parameters); 55 | 56 | $cert = $this->getParameter('certificate'); 57 | 58 | if (false === file_exists($cert)) { 59 | throw new AdapterException(sprintf('Certificate %s does not exist', $cert)); 60 | } 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | * 66 | * @throws PushException 67 | */ 68 | public function push(PushInterface $push) 69 | { 70 | $client = $this->getOpenedServiceClient(); 71 | 72 | $pushedDevices = new DeviceCollection(); 73 | 74 | foreach ($push->getDevices() as $device) { 75 | $message = $this->getServiceMessageFromOrigin($device, $push->getMessage()); 76 | 77 | try { 78 | /** @var ServiceResponse $response */ 79 | $response = $client->send($message); 80 | 81 | $responseArr = [ 82 | 'id' => $response->getId(), 83 | 'token' => $response->getCode(), 84 | ]; 85 | $push->addResponse($device, $responseArr); 86 | 87 | if (ServiceResponse::RESULT_OK === $response->getCode()) { 88 | $pushedDevices->add($device); 89 | } else { 90 | $client->close(); 91 | unset($this->openedClient, $client); 92 | // Assign returned new client to the in-scope/in-use $client variable 93 | $client = $this->getOpenedServiceClient(); 94 | } 95 | 96 | $this->response->addOriginalResponse($device, $response); 97 | $this->response->addParsedResponse($device, $responseArr); 98 | } catch (\RuntimeException $e) { 99 | throw new PushException($e->getMessage()); 100 | } 101 | } 102 | 103 | return $pushedDevices; 104 | } 105 | 106 | /** 107 | * @return array 108 | */ 109 | public function getFeedback() 110 | { 111 | $client = $this->getOpenedFeedbackClient(); 112 | $responses = []; 113 | $serviceResponses = $client->feedback(); 114 | 115 | /** @var Feedback $response */ 116 | foreach ($serviceResponses as $response) { 117 | $responses[$response->getToken()] = new \DateTime(date('c', $response->getTime())); 118 | } 119 | 120 | return $responses; 121 | } 122 | 123 | /** 124 | * @param ServiceAbstractClient|null $client Client 125 | * 126 | * @return ServiceAbstractClient 127 | */ 128 | public function getOpenedClient(ServiceAbstractClient $client = null) 129 | { 130 | if (!$client) { 131 | $client = new ServiceClient(); 132 | } 133 | 134 | $client->open( 135 | $this->isProductionEnvironment() ? ServiceClient::PRODUCTION_URI : ServiceClient::SANDBOX_URI, 136 | $this->getParameter('certificate'), 137 | $this->getParameter('passPhrase') 138 | ); 139 | 140 | return $client; 141 | } 142 | 143 | /** 144 | * @return ServiceClient 145 | */ 146 | protected function getOpenedServiceClient() 147 | { 148 | if (!isset($this->openedClient)) { 149 | $this->openedClient = $this->getOpenedClient(new ServiceClient()); 150 | } 151 | 152 | return $this->openedClient; 153 | } 154 | 155 | /** 156 | * @return ServiceFeedbackClient 157 | */ 158 | private function getOpenedFeedbackClient() 159 | { 160 | if (!isset($this->feedbackClient)) { 161 | $this->feedbackClient = $this->getOpenedClient(new ServiceFeedbackClient()); 162 | } 163 | 164 | return $this->feedbackClient; 165 | } 166 | 167 | /** 168 | * @param DeviceInterface $device Device 169 | * @param BaseOptionedModel|MessageInterface $message Message 170 | * 171 | * @return ServiceMessage 172 | */ 173 | public function getServiceMessageFromOrigin(DeviceInterface $device, BaseOptionedModel $message) 174 | { 175 | $badge = ($message->hasOption('badge')) 176 | ? (int) ($message->getOption('badge') + $device->getParameter('badge', 0)) 177 | : false; 178 | 179 | $sound = $message->getOption('sound'); 180 | $contentAvailable = $message->getOption('content-available'); 181 | $mutableContent = $message->getOption('mutable-content'); 182 | $category = $message->getOption('category'); 183 | $urlArgs = $message->getOption('urlArgs'); 184 | $expire = $message->getOption('expire'); 185 | 186 | $alert = new ServiceAlert( 187 | $message->getText(), 188 | $message->getOption('actionLocKey'), 189 | $message->getOption('locKey'), 190 | $message->getOption('locArgs'), 191 | $message->getOption('launchImage'), 192 | $message->getOption('title'), 193 | $message->getOption('titleLocKey'), 194 | $message->getOption('titleLocArgs') 195 | ); 196 | if ($actionLocKey = $message->getOption('actionLocKey')) { 197 | $alert->setActionLocKey($actionLocKey); 198 | } 199 | if ($locKey = $message->getOption('locKey')) { 200 | $alert->setLocKey($locKey); 201 | } 202 | if ($locArgs = $message->getOption('locArgs')) { 203 | $alert->setLocArgs($locArgs); 204 | } 205 | if ($launchImage = $message->getOption('launchImage')) { 206 | $alert->setLaunchImage($launchImage); 207 | } 208 | if ($title = $message->getOption('title')) { 209 | $alert->setTitle($title); 210 | } 211 | if ($titleLocKey = $message->getOption('titleLocKey')) { 212 | $alert->setTitleLocKey($titleLocKey); 213 | } 214 | if ($titleLocArgs = $message->getOption('titleLocArgs')) { 215 | $alert->setTitleLocArgs($titleLocArgs); 216 | } 217 | 218 | $serviceMessage = new ServiceMessage(); 219 | $serviceMessage->setId(sha1($device->getToken() . $message->getText())); 220 | $serviceMessage->setAlert($alert); 221 | $serviceMessage->setToken($device->getToken()); 222 | if (false !== $badge) { 223 | $serviceMessage->setBadge($badge); 224 | } 225 | $serviceMessage->setCustom($message->getOption('custom', [])); 226 | 227 | if (null !== $sound) { 228 | $serviceMessage->setSound($sound); 229 | } 230 | 231 | if (null !== $contentAvailable) { 232 | $serviceMessage->setContentAvailable($contentAvailable); 233 | } 234 | 235 | if (null !== $mutableContent) { 236 | $serviceMessage->setMutableContent($mutableContent); 237 | } 238 | 239 | if (null !== $category) { 240 | $serviceMessage->setCategory($category); 241 | } 242 | 243 | if (null !== $urlArgs) { 244 | $serviceMessage->setUrlArgs($urlArgs); 245 | } 246 | 247 | if (null !== $expire) { 248 | $serviceMessage->setExpire($expire); 249 | } 250 | 251 | return $serviceMessage; 252 | } 253 | 254 | /** 255 | * {@inheritdoc} 256 | */ 257 | public function supports($token) 258 | { 259 | return ctype_xdigit($token); 260 | } 261 | 262 | /** 263 | * {@inheritdoc} 264 | */ 265 | public function getDefinedParameters() 266 | { 267 | return []; 268 | } 269 | 270 | /** 271 | * {@inheritdoc} 272 | */ 273 | public function getDefaultParameters() 274 | { 275 | return ['passPhrase' => null]; 276 | } 277 | 278 | /** 279 | * {@inheritdoc} 280 | */ 281 | public function getRequiredParameters() 282 | { 283 | return ['certificate']; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /tests/units/Sly/NotificationPusher/Adapter/Apns.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Apns extends Units\Test 25 | { 26 | const APNS_TOKEN_EXAMPLE_64 = '111db24975bb6c6b63214a8d268052aa0a965cc1e32110ab06a72b19074c2222'; 27 | const APNS_TOKEN_EXAMPLE_65 = '111db24975bb6c6b63214a8d268052aa0a965cc1e32110ab06a72b19074c22225'; 28 | 29 | public function testConstruct() 30 | { 31 | $this 32 | ->exception(static function () { 33 | $object = new TestedModel(); 34 | }) 35 | ->isInstanceOf(MissingOptionsException::class) 36 | ->message 37 | ->contains('certificate') 38 | ->exception(static function () { 39 | $object = new TestedModel(['certificate' => 'absent.pem']); 40 | }) 41 | ->isInstanceOf(AdapterException::class) 42 | ->message 43 | ->contains('does not exist') 44 | ->when($this->mockGenerator()->orphanize('__construct')) 45 | ->and($this->mockClass(TestedModel::class, '\Mock')) 46 | ->and($object = new \Mock\Apns()) 47 | ->and($object->setParameters(['certificate' => 'test.pem', 'passPhrase' => 'test'])) 48 | ->and($object->setResponse(new Response())) 49 | ->array($object->getParameters()) 50 | ->isNotEmpty() 51 | ->hasSize(2) 52 | ->string($object->getParameter('certificate')) 53 | ->isEqualTo('test.pem'); 54 | } 55 | 56 | public function testSupports() 57 | { 58 | $this->if($this->mockGenerator()->orphanize('__construct')) 59 | ->and($this->mockClass(TestedModel::class, '\Mock')) 60 | ->and($object = new \Mock\Apns()) 61 | ->boolean($object->supports('wrongToken')) 62 | ->isFalse() 63 | ->boolean($object->supports(self::APNS_TOKEN_EXAMPLE_64)) 64 | ->isTrue() 65 | ->boolean($object->supports(self::APNS_TOKEN_EXAMPLE_65)) 66 | ->isTrue(); 67 | } 68 | 69 | public function testDefinedParameters() 70 | { 71 | $this->if($this->mockGenerator()->orphanize('__construct')) 72 | ->and($this->mockClass(TestedModel::class, '\Mock')) 73 | ->and($object = new \Mock\Apns()) 74 | ->array($defaultParameters = $object->getDefinedParameters()) 75 | ->isEmpty(); 76 | } 77 | 78 | public function testDefaultParameters() 79 | { 80 | $this->if($this->mockGenerator()->orphanize('__construct')) 81 | ->and($this->mockClass(TestedModel::class, '\Mock')) 82 | ->and($object = new \Mock\Apns()) 83 | ->array($defaultParameters = $object->getDefaultParameters()) 84 | ->isNotEmpty() 85 | ->hasKey('passPhrase') 86 | ->variable($defaultParameters['passPhrase']) 87 | ->isNull(); 88 | } 89 | 90 | public function testRequiredParameters() 91 | { 92 | $this->if($this->mockGenerator()->orphanize('__construct')) 93 | ->and($this->mockClass(TestedModel::class, '\Mock')) 94 | ->and($object = new \Mock\Apns()) 95 | ->array($requiredParameters = $object->getRequiredParameters()) 96 | ->isNotEmpty() 97 | ->contains('certificate'); 98 | } 99 | 100 | public function testGetOpenedClient() 101 | { 102 | $this->if($this->mockGenerator()->orphanize('__construct')) 103 | ->and($this->mockClass(TestedModel::class, '\Mock')) 104 | ->and($object = new \Mock\Apns()) 105 | ->and($this->mockGenerator()->orphanize('__construct')) 106 | ->and($this->mockGenerator()->orphanize('open')) 107 | ->and($this->mockClass(ZendClientMessage::class, '\Mock\ZendService')) 108 | ->and($serviceClient = new \Mock\ZendService\Message()) 109 | ->and($object->getMockController()->getParameters = []) 110 | ->exception(static function () use ($object) { 111 | $object->getOpenedClient(new ZendClientMessage()); 112 | }) 113 | ->isInstanceOf(InvalidArgumentException::class) 114 | ->message 115 | ->contains('Certificate must be a valid path to a APNS certificate') 116 | ->when($object = new TestedModel(['certificate' => __DIR__ . '/../Resources/apns-certificate.pem'])) 117 | ->and($object->getOpenedClient($serviceClient)); 118 | } 119 | 120 | public function testGetServiceMessageFromOrigin() 121 | { 122 | $this->if($this->mockGenerator()->orphanize('__construct')) 123 | ->and($this->mockClass(TestedModel::class, '\Mock')) 124 | ->and($object = new \Mock\Apns()) 125 | ->and($this->mockGenerator()->orphanize('__construct')) 126 | ->and($this->mockClass(Device::class, '\Mock')) 127 | ->and($device = new \Mock\Device()) 128 | ->and($device->getMockController()->getToken = self::APNS_TOKEN_EXAMPLE_64) 129 | ->and($this->mockGenerator()->orphanize('__construct')) 130 | ->and($this->mockClass(Message::class, '\Mock')) 131 | ->and($message = new \Mock\Message()) 132 | ->and($message->getMockController()->getText = 'Test') 133 | ->object($object->getServiceMessageFromOrigin($device, $message)) 134 | ->isInstanceOf(ZendServiceMessage::class); 135 | } 136 | 137 | public function testPush() 138 | { 139 | $this->if($this->mockGenerator()->orphanize('__construct') 140 | ->makeVisible('getOpenedServiceClient') 141 | ->generate(TestedModel::class, '\Mock', 'Apns')) 142 | ->and($object = new \Mock\Apns()) 143 | ->and($object->setResponse(new Response())) 144 | ->and($this->mockClass(ZendResponseMessage::class, '\Mock\ZendService', 'Response')) 145 | ->and($serviceResponse = new \Mock\ZendService\Response()) 146 | ->and($serviceResponse->getMockController()->getCode = ZendResponseMessage::RESULT_OK) 147 | ->and($serviceResponse->getMockController()->getId = 0) 148 | ->and($this->mockGenerator()->orphanize('__construct') 149 | ->orphanize('open') 150 | ->orphanize('send') 151 | ->generate(ZendClientMessage::class, '\Mock\ZendService')) 152 | ->and($serviceClient = new \Mock\ZendService\Message()) 153 | ->and($serviceClient->getMockController()->send = $serviceResponse) 154 | ->and($this->mockGenerator()->orphanize('__construct')) 155 | ->and($this->mockClass(Push::class, '\Mock')) 156 | ->and($push = new \Mock\Push()) 157 | ->and($push->getMockController()->getMessage = new Message('Test')) 158 | ->and($push->getMockController()->getDevices = new DeviceCollection( 159 | [new Device(self::APNS_TOKEN_EXAMPLE_64)] 160 | )) 161 | ->and($object->getMockController()->getServiceMessageFromOrigin = new ZendServiceMessage()) 162 | ->and($object->getMockController()->getOpenedClient = $serviceClient) 163 | ->and($this->calling($object)->getOpenedServiceClient = $serviceClient) 164 | ->object($result = $object->push($push)) 165 | ->isInstanceOf(DeviceCollection::class) 166 | ->boolean($result->count() == 1) 167 | ->isTrue(); 168 | } 169 | 170 | public function testCountIsEmpty() 171 | { 172 | $this->if($dcoll = new DeviceCollection()) 173 | ->boolean($dcoll->isEmpty()) 174 | ->isTrue(); 175 | } 176 | 177 | public function testFeedback() 178 | { 179 | $this->if($this->mockGenerator()->orphanize('__construct')) 180 | ->and($this->mockClass(TestedModel::class, '\Mock')) 181 | ->and($object = new \Mock\Apns()) 182 | ->and($this->mockClass(ZendResponseMessage::class, '\Mock\ZendService', 'Response')) 183 | ->and($serviceResponse = new \Mock\ZendService\Response()) 184 | ->and($this->mockGenerator()->orphanize('__construct')) 185 | ->and($this->mockGenerator()->orphanize('open')) 186 | ->and($this->mockGenerator()->orphanize('send')) 187 | ->and($this->mockClass(Feedback::class, '\Mock\ZendService')) 188 | ->and($serviceClient = new \Mock\ZendService\Feedback()) 189 | ->and($serviceClient->getMockController()->feedback = $serviceResponse) 190 | ->and($object->getMockController()->getServiceMessageFromOrigin = new ZendServiceMessage()) 191 | ->and($object->getMockController()->getOpenedClient = $serviceClient) 192 | ->array($object->getFeedback()) 193 | ->isEmpty(); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "e5760089754b557cdb9a3912f539c70b", 8 | "packages": [ 9 | { 10 | "name": "container-interop/container-interop", 11 | "version": "1.2.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/container-interop/container-interop.git", 15 | "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", 20 | "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "psr/container": "^1.0" 25 | }, 26 | "type": "library", 27 | "autoload": { 28 | "psr-4": { 29 | "Interop\\Container\\": "src/Interop/Container/" 30 | } 31 | }, 32 | "notification-url": "https://packagist.org/downloads/", 33 | "license": [ 34 | "MIT" 35 | ], 36 | "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", 37 | "homepage": "https://github.com/container-interop/container-interop", 38 | "abandoned": "psr/container", 39 | "time": "2017-02-14T19:40:03+00:00" 40 | }, 41 | { 42 | "name": "doctrine/inflector", 43 | "version": "v1.1.0", 44 | "source": { 45 | "type": "git", 46 | "url": "https://github.com/doctrine/inflector.git", 47 | "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" 48 | }, 49 | "dist": { 50 | "type": "zip", 51 | "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", 52 | "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", 53 | "shasum": "" 54 | }, 55 | "require": { 56 | "php": ">=5.3.2" 57 | }, 58 | "require-dev": { 59 | "phpunit/phpunit": "4.*" 60 | }, 61 | "type": "library", 62 | "extra": { 63 | "branch-alias": { 64 | "dev-master": "1.1.x-dev" 65 | } 66 | }, 67 | "autoload": { 68 | "psr-0": { 69 | "Doctrine\\Common\\Inflector\\": "lib/" 70 | } 71 | }, 72 | "notification-url": "https://packagist.org/downloads/", 73 | "license": [ 74 | "MIT" 75 | ], 76 | "authors": [ 77 | { 78 | "name": "Roman Borschel", 79 | "email": "roman@code-factory.org" 80 | }, 81 | { 82 | "name": "Benjamin Eberlei", 83 | "email": "kontakt@beberlei.de" 84 | }, 85 | { 86 | "name": "Guilherme Blanco", 87 | "email": "guilhermeblanco@gmail.com" 88 | }, 89 | { 90 | "name": "Jonathan Wage", 91 | "email": "jonwage@gmail.com" 92 | }, 93 | { 94 | "name": "Johannes Schmitt", 95 | "email": "schmittjoh@gmail.com" 96 | } 97 | ], 98 | "description": "Common String Manipulations with regard to casing and singular/plural rules.", 99 | "homepage": "http://www.doctrine-project.org", 100 | "keywords": [ 101 | "inflection", 102 | "pluralize", 103 | "singularize", 104 | "string" 105 | ], 106 | "time": "2015-11-06T14:35:42+00:00" 107 | }, 108 | { 109 | "name": "psr/container", 110 | "version": "1.0.0", 111 | "source": { 112 | "type": "git", 113 | "url": "https://github.com/php-fig/container.git", 114 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" 115 | }, 116 | "dist": { 117 | "type": "zip", 118 | "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 119 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 120 | "shasum": "" 121 | }, 122 | "require": { 123 | "php": ">=5.3.0" 124 | }, 125 | "type": "library", 126 | "extra": { 127 | "branch-alias": { 128 | "dev-master": "1.0.x-dev" 129 | } 130 | }, 131 | "autoload": { 132 | "psr-4": { 133 | "Psr\\Container\\": "src/" 134 | } 135 | }, 136 | "notification-url": "https://packagist.org/downloads/", 137 | "license": [ 138 | "MIT" 139 | ], 140 | "authors": [ 141 | { 142 | "name": "PHP-FIG", 143 | "homepage": "http://www.php-fig.org/" 144 | } 145 | ], 146 | "description": "Common Container Interface (PHP FIG PSR-11)", 147 | "homepage": "https://github.com/php-fig/container", 148 | "keywords": [ 149 | "PSR-11", 150 | "container", 151 | "container-interface", 152 | "container-interop", 153 | "psr" 154 | ], 155 | "time": "2017-02-14T16:28:37+00:00" 156 | }, 157 | { 158 | "name": "psr/log", 159 | "version": "1.1.3", 160 | "source": { 161 | "type": "git", 162 | "url": "https://github.com/php-fig/log.git", 163 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" 164 | }, 165 | "dist": { 166 | "type": "zip", 167 | "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", 168 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", 169 | "shasum": "" 170 | }, 171 | "require": { 172 | "php": ">=5.3.0" 173 | }, 174 | "type": "library", 175 | "extra": { 176 | "branch-alias": { 177 | "dev-master": "1.1.x-dev" 178 | } 179 | }, 180 | "autoload": { 181 | "psr-4": { 182 | "Psr\\Log\\": "Psr/Log/" 183 | } 184 | }, 185 | "notification-url": "https://packagist.org/downloads/", 186 | "license": [ 187 | "MIT" 188 | ], 189 | "authors": [ 190 | { 191 | "name": "PHP-FIG", 192 | "homepage": "http://www.php-fig.org/" 193 | } 194 | ], 195 | "description": "Common interface for logging libraries", 196 | "homepage": "https://github.com/php-fig/log", 197 | "keywords": [ 198 | "log", 199 | "psr", 200 | "psr-3" 201 | ], 202 | "time": "2020-03-23T09:12:05+00:00" 203 | }, 204 | { 205 | "name": "symfony/console", 206 | "version": "v3.4.0", 207 | "source": { 208 | "type": "git", 209 | "url": "https://github.com/symfony/console.git", 210 | "reference": "9468ad3fba3a5e1f0dc12a96e50e84cddb923cf0" 211 | }, 212 | "dist": { 213 | "type": "zip", 214 | "url": "https://api.github.com/repos/symfony/console/zipball/9468ad3fba3a5e1f0dc12a96e50e84cddb923cf0", 215 | "reference": "9468ad3fba3a5e1f0dc12a96e50e84cddb923cf0", 216 | "shasum": "" 217 | }, 218 | "require": { 219 | "php": "^5.5.9|>=7.0.8", 220 | "symfony/debug": "~2.8|~3.0|~4.0", 221 | "symfony/polyfill-mbstring": "~1.0" 222 | }, 223 | "conflict": { 224 | "symfony/dependency-injection": "<3.4", 225 | "symfony/process": "<3.3" 226 | }, 227 | "require-dev": { 228 | "psr/log": "~1.0", 229 | "symfony/config": "~3.3|~4.0", 230 | "symfony/dependency-injection": "~3.4|~4.0", 231 | "symfony/event-dispatcher": "~2.8|~3.0|~4.0", 232 | "symfony/lock": "~3.4|~4.0", 233 | "symfony/process": "~3.3|~4.0" 234 | }, 235 | "suggest": { 236 | "psr/log": "For using the console logger", 237 | "symfony/event-dispatcher": "", 238 | "symfony/lock": "", 239 | "symfony/process": "" 240 | }, 241 | "type": "library", 242 | "extra": { 243 | "branch-alias": { 244 | "dev-master": "3.4-dev" 245 | } 246 | }, 247 | "autoload": { 248 | "psr-4": { 249 | "Symfony\\Component\\Console\\": "" 250 | }, 251 | "exclude-from-classmap": [ 252 | "/Tests/" 253 | ] 254 | }, 255 | "notification-url": "https://packagist.org/downloads/", 256 | "license": [ 257 | "MIT" 258 | ], 259 | "authors": [ 260 | { 261 | "name": "Fabien Potencier", 262 | "email": "fabien@symfony.com" 263 | }, 264 | { 265 | "name": "Symfony Community", 266 | "homepage": "https://symfony.com/contributors" 267 | } 268 | ], 269 | "description": "Symfony Console Component", 270 | "homepage": "https://symfony.com", 271 | "time": "2017-11-29T13:28:14+00:00" 272 | }, 273 | { 274 | "name": "symfony/debug", 275 | "version": "v3.4.0", 276 | "source": { 277 | "type": "git", 278 | "url": "https://github.com/symfony/debug.git", 279 | "reference": "fb2001e5d85f95d8b6ab94ae3be5d2672df128fd" 280 | }, 281 | "dist": { 282 | "type": "zip", 283 | "url": "https://api.github.com/repos/symfony/debug/zipball/fb2001e5d85f95d8b6ab94ae3be5d2672df128fd", 284 | "reference": "fb2001e5d85f95d8b6ab94ae3be5d2672df128fd", 285 | "shasum": "" 286 | }, 287 | "require": { 288 | "php": "^5.5.9|>=7.0.8", 289 | "psr/log": "~1.0" 290 | }, 291 | "conflict": { 292 | "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" 293 | }, 294 | "require-dev": { 295 | "symfony/http-kernel": "~2.8|~3.0|~4.0" 296 | }, 297 | "type": "library", 298 | "extra": { 299 | "branch-alias": { 300 | "dev-master": "3.4-dev" 301 | } 302 | }, 303 | "autoload": { 304 | "psr-4": { 305 | "Symfony\\Component\\Debug\\": "" 306 | }, 307 | "exclude-from-classmap": [ 308 | "/Tests/" 309 | ] 310 | }, 311 | "notification-url": "https://packagist.org/downloads/", 312 | "license": [ 313 | "MIT" 314 | ], 315 | "authors": [ 316 | { 317 | "name": "Fabien Potencier", 318 | "email": "fabien@symfony.com" 319 | }, 320 | { 321 | "name": "Symfony Community", 322 | "homepage": "https://symfony.com/contributors" 323 | } 324 | ], 325 | "description": "Symfony Debug Component", 326 | "homepage": "https://symfony.com", 327 | "time": "2017-11-21T09:01:46+00:00" 328 | }, 329 | { 330 | "name": "symfony/filesystem", 331 | "version": "v3.4.0", 332 | "source": { 333 | "type": "git", 334 | "url": "https://github.com/symfony/filesystem.git", 335 | "reference": "de56eee71e0a128d8c54ccc1909cdefd574bad0f" 336 | }, 337 | "dist": { 338 | "type": "zip", 339 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/de56eee71e0a128d8c54ccc1909cdefd574bad0f", 340 | "reference": "de56eee71e0a128d8c54ccc1909cdefd574bad0f", 341 | "shasum": "" 342 | }, 343 | "require": { 344 | "php": "^5.5.9|>=7.0.8" 345 | }, 346 | "type": "library", 347 | "extra": { 348 | "branch-alias": { 349 | "dev-master": "3.4-dev" 350 | } 351 | }, 352 | "autoload": { 353 | "psr-4": { 354 | "Symfony\\Component\\Filesystem\\": "" 355 | }, 356 | "exclude-from-classmap": [ 357 | "/Tests/" 358 | ] 359 | }, 360 | "notification-url": "https://packagist.org/downloads/", 361 | "license": [ 362 | "MIT" 363 | ], 364 | "authors": [ 365 | { 366 | "name": "Fabien Potencier", 367 | "email": "fabien@symfony.com" 368 | }, 369 | { 370 | "name": "Symfony Community", 371 | "homepage": "https://symfony.com/contributors" 372 | } 373 | ], 374 | "description": "Symfony Filesystem Component", 375 | "homepage": "https://symfony.com", 376 | "time": "2017-11-19T18:59:05+00:00" 377 | }, 378 | { 379 | "name": "symfony/options-resolver", 380 | "version": "v3.4.0", 381 | "source": { 382 | "type": "git", 383 | "url": "https://github.com/symfony/options-resolver.git", 384 | "reference": "08748edfe6982f4d878cc42b8325b19a276fb1cf" 385 | }, 386 | "dist": { 387 | "type": "zip", 388 | "url": "https://api.github.com/repos/symfony/options-resolver/zipball/08748edfe6982f4d878cc42b8325b19a276fb1cf", 389 | "reference": "08748edfe6982f4d878cc42b8325b19a276fb1cf", 390 | "shasum": "" 391 | }, 392 | "require": { 393 | "php": "^5.5.9|>=7.0.8" 394 | }, 395 | "type": "library", 396 | "extra": { 397 | "branch-alias": { 398 | "dev-master": "3.4-dev" 399 | } 400 | }, 401 | "autoload": { 402 | "psr-4": { 403 | "Symfony\\Component\\OptionsResolver\\": "" 404 | }, 405 | "exclude-from-classmap": [ 406 | "/Tests/" 407 | ] 408 | }, 409 | "notification-url": "https://packagist.org/downloads/", 410 | "license": [ 411 | "MIT" 412 | ], 413 | "authors": [ 414 | { 415 | "name": "Fabien Potencier", 416 | "email": "fabien@symfony.com" 417 | }, 418 | { 419 | "name": "Symfony Community", 420 | "homepage": "https://symfony.com/contributors" 421 | } 422 | ], 423 | "description": "Symfony OptionsResolver Component", 424 | "homepage": "https://symfony.com", 425 | "keywords": [ 426 | "config", 427 | "configuration", 428 | "options" 429 | ], 430 | "time": "2017-11-05T16:10:10+00:00" 431 | }, 432 | { 433 | "name": "symfony/polyfill-mbstring", 434 | "version": "v1.17.0", 435 | "source": { 436 | "type": "git", 437 | "url": "https://github.com/symfony/polyfill-mbstring.git", 438 | "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" 439 | }, 440 | "dist": { 441 | "type": "zip", 442 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", 443 | "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", 444 | "shasum": "" 445 | }, 446 | "require": { 447 | "php": ">=5.3.3" 448 | }, 449 | "suggest": { 450 | "ext-mbstring": "For best performance" 451 | }, 452 | "type": "library", 453 | "extra": { 454 | "branch-alias": { 455 | "dev-master": "1.17-dev" 456 | } 457 | }, 458 | "autoload": { 459 | "psr-4": { 460 | "Symfony\\Polyfill\\Mbstring\\": "" 461 | }, 462 | "files": [ 463 | "bootstrap.php" 464 | ] 465 | }, 466 | "notification-url": "https://packagist.org/downloads/", 467 | "license": [ 468 | "MIT" 469 | ], 470 | "authors": [ 471 | { 472 | "name": "Nicolas Grekas", 473 | "email": "p@tchwork.com" 474 | }, 475 | { 476 | "name": "Symfony Community", 477 | "homepage": "https://symfony.com/contributors" 478 | } 479 | ], 480 | "description": "Symfony polyfill for the Mbstring extension", 481 | "homepage": "https://symfony.com", 482 | "keywords": [ 483 | "compatibility", 484 | "mbstring", 485 | "polyfill", 486 | "portable", 487 | "shim" 488 | ], 489 | "time": "2020-05-12T16:47:27+00:00" 490 | }, 491 | { 492 | "name": "symfony/process", 493 | "version": "v3.4.0", 494 | "source": { 495 | "type": "git", 496 | "url": "https://github.com/symfony/process.git", 497 | "reference": "db25e810fd5e124085e3777257d0cf4ae533d0ea" 498 | }, 499 | "dist": { 500 | "type": "zip", 501 | "url": "https://api.github.com/repos/symfony/process/zipball/db25e810fd5e124085e3777257d0cf4ae533d0ea", 502 | "reference": "db25e810fd5e124085e3777257d0cf4ae533d0ea", 503 | "shasum": "" 504 | }, 505 | "require": { 506 | "php": "^5.5.9|>=7.0.8" 507 | }, 508 | "type": "library", 509 | "extra": { 510 | "branch-alias": { 511 | "dev-master": "3.4-dev" 512 | } 513 | }, 514 | "autoload": { 515 | "psr-4": { 516 | "Symfony\\Component\\Process\\": "" 517 | }, 518 | "exclude-from-classmap": [ 519 | "/Tests/" 520 | ] 521 | }, 522 | "notification-url": "https://packagist.org/downloads/", 523 | "license": [ 524 | "MIT" 525 | ], 526 | "authors": [ 527 | { 528 | "name": "Fabien Potencier", 529 | "email": "fabien@symfony.com" 530 | }, 531 | { 532 | "name": "Symfony Community", 533 | "homepage": "https://symfony.com/contributors" 534 | } 535 | ], 536 | "description": "Symfony Process Component", 537 | "homepage": "https://symfony.com", 538 | "time": "2017-11-22T12:18:49+00:00" 539 | }, 540 | { 541 | "name": "zendframework/zend-escaper", 542 | "version": "2.6.1", 543 | "source": { 544 | "type": "git", 545 | "url": "https://github.com/zendframework/zend-escaper.git", 546 | "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f" 547 | }, 548 | "dist": { 549 | "type": "zip", 550 | "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", 551 | "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", 552 | "shasum": "" 553 | }, 554 | "require": { 555 | "php": "^5.6 || ^7.0" 556 | }, 557 | "require-dev": { 558 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", 559 | "zendframework/zend-coding-standard": "~1.0.0" 560 | }, 561 | "type": "library", 562 | "extra": { 563 | "branch-alias": { 564 | "dev-master": "2.6.x-dev", 565 | "dev-develop": "2.7.x-dev" 566 | } 567 | }, 568 | "autoload": { 569 | "psr-4": { 570 | "Zend\\Escaper\\": "src/" 571 | } 572 | }, 573 | "notification-url": "https://packagist.org/downloads/", 574 | "license": [ 575 | "BSD-3-Clause" 576 | ], 577 | "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", 578 | "keywords": [ 579 | "ZendFramework", 580 | "escaper", 581 | "zf" 582 | ], 583 | "abandoned": "laminas/laminas-escaper", 584 | "time": "2019-09-05T20:03:20+00:00" 585 | }, 586 | { 587 | "name": "zendframework/zend-http", 588 | "version": "2.11.2", 589 | "source": { 590 | "type": "git", 591 | "url": "https://github.com/zendframework/zend-http.git", 592 | "reference": "e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a" 593 | }, 594 | "dist": { 595 | "type": "zip", 596 | "url": "https://api.github.com/repos/zendframework/zend-http/zipball/e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a", 597 | "reference": "e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a", 598 | "shasum": "" 599 | }, 600 | "require": { 601 | "php": "^5.6 || ^7.0", 602 | "zendframework/zend-loader": "^2.5.1", 603 | "zendframework/zend-stdlib": "^3.2.1", 604 | "zendframework/zend-uri": "^2.5.2", 605 | "zendframework/zend-validator": "^2.10.1" 606 | }, 607 | "require-dev": { 608 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3", 609 | "zendframework/zend-coding-standard": "~1.0.0", 610 | "zendframework/zend-config": "^3.1 || ^2.6" 611 | }, 612 | "suggest": { 613 | "paragonie/certainty": "For automated management of cacert.pem" 614 | }, 615 | "type": "library", 616 | "extra": { 617 | "branch-alias": { 618 | "dev-master": "2.11.x-dev", 619 | "dev-develop": "2.12.x-dev" 620 | } 621 | }, 622 | "autoload": { 623 | "psr-4": { 624 | "Zend\\Http\\": "src/" 625 | } 626 | }, 627 | "notification-url": "https://packagist.org/downloads/", 628 | "license": [ 629 | "BSD-3-Clause" 630 | ], 631 | "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", 632 | "keywords": [ 633 | "ZendFramework", 634 | "http", 635 | "http client", 636 | "zend", 637 | "zf" 638 | ], 639 | "abandoned": "laminas/laminas-http", 640 | "time": "2019-12-30T20:47:33+00:00" 641 | }, 642 | { 643 | "name": "zendframework/zend-json", 644 | "version": "3.1.2", 645 | "source": { 646 | "type": "git", 647 | "url": "https://github.com/zendframework/zend-json.git", 648 | "reference": "e9ddb1192d93fe7fff846ac895249c39db75132b" 649 | }, 650 | "dist": { 651 | "type": "zip", 652 | "url": "https://api.github.com/repos/zendframework/zend-json/zipball/e9ddb1192d93fe7fff846ac895249c39db75132b", 653 | "reference": "e9ddb1192d93fe7fff846ac895249c39db75132b", 654 | "shasum": "" 655 | }, 656 | "require": { 657 | "php": "^5.6 || ^7.0" 658 | }, 659 | "require-dev": { 660 | "phpunit/phpunit": "^5.7.23 || ^6.4.3", 661 | "zendframework/zend-coding-standard": "~1.0.0", 662 | "zendframework/zend-stdlib": "^2.7.7 || ^3.1" 663 | }, 664 | "suggest": { 665 | "zendframework/zend-json-server": "For implementing JSON-RPC servers", 666 | "zendframework/zend-xml2json": "For converting XML documents to JSON" 667 | }, 668 | "type": "library", 669 | "extra": { 670 | "branch-alias": { 671 | "dev-master": "3.1.x-dev", 672 | "dev-develop": "3.2.x-dev" 673 | } 674 | }, 675 | "autoload": { 676 | "psr-4": { 677 | "Zend\\Json\\": "src/" 678 | } 679 | }, 680 | "notification-url": "https://packagist.org/downloads/", 681 | "license": [ 682 | "BSD-3-Clause" 683 | ], 684 | "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", 685 | "keywords": [ 686 | "ZendFramework", 687 | "json", 688 | "zf" 689 | ], 690 | "abandoned": "laminas/laminas-json", 691 | "time": "2019-10-09T13:56:13+00:00" 692 | }, 693 | { 694 | "name": "zendframework/zend-loader", 695 | "version": "2.6.1", 696 | "source": { 697 | "type": "git", 698 | "url": "https://github.com/zendframework/zend-loader.git", 699 | "reference": "91da574d29b58547385b2298c020b257310898c6" 700 | }, 701 | "dist": { 702 | "type": "zip", 703 | "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/91da574d29b58547385b2298c020b257310898c6", 704 | "reference": "91da574d29b58547385b2298c020b257310898c6", 705 | "shasum": "" 706 | }, 707 | "require": { 708 | "php": "^5.6 || ^7.0" 709 | }, 710 | "require-dev": { 711 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", 712 | "zendframework/zend-coding-standard": "~1.0.0" 713 | }, 714 | "type": "library", 715 | "extra": { 716 | "branch-alias": { 717 | "dev-master": "2.6.x-dev", 718 | "dev-develop": "2.7.x-dev" 719 | } 720 | }, 721 | "autoload": { 722 | "psr-4": { 723 | "Zend\\Loader\\": "src/" 724 | } 725 | }, 726 | "notification-url": "https://packagist.org/downloads/", 727 | "license": [ 728 | "BSD-3-Clause" 729 | ], 730 | "description": "Autoloading and plugin loading strategies", 731 | "keywords": [ 732 | "ZendFramework", 733 | "loader", 734 | "zf" 735 | ], 736 | "abandoned": "laminas/laminas-loader", 737 | "time": "2019-09-04T19:38:14+00:00" 738 | }, 739 | { 740 | "name": "zendframework/zend-stdlib", 741 | "version": "3.2.1", 742 | "source": { 743 | "type": "git", 744 | "url": "https://github.com/zendframework/zend-stdlib.git", 745 | "reference": "66536006722aff9e62d1b331025089b7ec71c065" 746 | }, 747 | "dist": { 748 | "type": "zip", 749 | "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/66536006722aff9e62d1b331025089b7ec71c065", 750 | "reference": "66536006722aff9e62d1b331025089b7ec71c065", 751 | "shasum": "" 752 | }, 753 | "require": { 754 | "php": "^5.6 || ^7.0" 755 | }, 756 | "require-dev": { 757 | "phpbench/phpbench": "^0.13", 758 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", 759 | "zendframework/zend-coding-standard": "~1.0.0" 760 | }, 761 | "type": "library", 762 | "extra": { 763 | "branch-alias": { 764 | "dev-master": "3.2.x-dev", 765 | "dev-develop": "3.3.x-dev" 766 | } 767 | }, 768 | "autoload": { 769 | "psr-4": { 770 | "Zend\\Stdlib\\": "src/" 771 | } 772 | }, 773 | "notification-url": "https://packagist.org/downloads/", 774 | "license": [ 775 | "BSD-3-Clause" 776 | ], 777 | "description": "SPL extensions, array utilities, error handlers, and more", 778 | "keywords": [ 779 | "ZendFramework", 780 | "stdlib", 781 | "zf" 782 | ], 783 | "abandoned": "laminas/laminas-stdlib", 784 | "time": "2018-08-28T21:34:05+00:00" 785 | }, 786 | { 787 | "name": "zendframework/zend-uri", 788 | "version": "2.7.1", 789 | "source": { 790 | "type": "git", 791 | "url": "https://github.com/zendframework/zend-uri.git", 792 | "reference": "bfc4a5b9a309711e968d7c72afae4ac50c650083" 793 | }, 794 | "dist": { 795 | "type": "zip", 796 | "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/bfc4a5b9a309711e968d7c72afae4ac50c650083", 797 | "reference": "bfc4a5b9a309711e968d7c72afae4ac50c650083", 798 | "shasum": "" 799 | }, 800 | "require": { 801 | "php": "^5.6 || ^7.0", 802 | "zendframework/zend-escaper": "^2.5", 803 | "zendframework/zend-validator": "^2.10" 804 | }, 805 | "require-dev": { 806 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", 807 | "zendframework/zend-coding-standard": "~1.0.0" 808 | }, 809 | "type": "library", 810 | "extra": { 811 | "branch-alias": { 812 | "dev-master": "2.7.x-dev", 813 | "dev-develop": "2.8.x-dev" 814 | } 815 | }, 816 | "autoload": { 817 | "psr-4": { 818 | "Zend\\Uri\\": "src/" 819 | } 820 | }, 821 | "notification-url": "https://packagist.org/downloads/", 822 | "license": [ 823 | "BSD-3-Clause" 824 | ], 825 | "description": "A component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", 826 | "keywords": [ 827 | "ZendFramework", 828 | "uri", 829 | "zf" 830 | ], 831 | "abandoned": "laminas/laminas-uri", 832 | "time": "2019-10-07T13:35:33+00:00" 833 | }, 834 | { 835 | "name": "zendframework/zend-validator", 836 | "version": "2.12.2", 837 | "source": { 838 | "type": "git", 839 | "url": "https://github.com/zendframework/zend-validator.git", 840 | "reference": "fd24920c2afcf2a70d11f67c3457f8f509453a62" 841 | }, 842 | "dist": { 843 | "type": "zip", 844 | "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/fd24920c2afcf2a70d11f67c3457f8f509453a62", 845 | "reference": "fd24920c2afcf2a70d11f67c3457f8f509453a62", 846 | "shasum": "" 847 | }, 848 | "require": { 849 | "container-interop/container-interop": "^1.1", 850 | "php": "^5.6 || ^7.0", 851 | "zendframework/zend-stdlib": "^3.2.1" 852 | }, 853 | "require-dev": { 854 | "phpunit/phpunit": "^6.0.8 || ^5.7.15", 855 | "psr/http-message": "^1.0", 856 | "zendframework/zend-cache": "^2.6.1", 857 | "zendframework/zend-coding-standard": "~1.0.0", 858 | "zendframework/zend-config": "^2.6", 859 | "zendframework/zend-db": "^2.7", 860 | "zendframework/zend-filter": "^2.6", 861 | "zendframework/zend-http": "^2.5.4", 862 | "zendframework/zend-i18n": "^2.6", 863 | "zendframework/zend-math": "^2.6", 864 | "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", 865 | "zendframework/zend-session": "^2.8", 866 | "zendframework/zend-uri": "^2.5" 867 | }, 868 | "suggest": { 869 | "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", 870 | "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", 871 | "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", 872 | "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", 873 | "zendframework/zend-i18n-resources": "Translations of validator messages", 874 | "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", 875 | "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", 876 | "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", 877 | "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" 878 | }, 879 | "type": "library", 880 | "extra": { 881 | "branch-alias": { 882 | "dev-master": "2.12.x-dev", 883 | "dev-develop": "2.13.x-dev" 884 | }, 885 | "zf": { 886 | "component": "Zend\\Validator", 887 | "config-provider": "Zend\\Validator\\ConfigProvider" 888 | } 889 | }, 890 | "autoload": { 891 | "psr-4": { 892 | "Zend\\Validator\\": "src/" 893 | } 894 | }, 895 | "notification-url": "https://packagist.org/downloads/", 896 | "license": [ 897 | "BSD-3-Clause" 898 | ], 899 | "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", 900 | "keywords": [ 901 | "ZendFramework", 902 | "validator", 903 | "zf" 904 | ], 905 | "abandoned": "laminas/laminas-validator", 906 | "time": "2019-10-29T08:33:25+00:00" 907 | }, 908 | { 909 | "name": "zendframework/zendservice-apple-apns", 910 | "version": "1.4.1", 911 | "source": { 912 | "type": "git", 913 | "url": "https://github.com/zendframework/ZendService_Apple_Apns.git", 914 | "reference": "a8919519edf9ac4658e7f61cb39c4dfe65b5bd49" 915 | }, 916 | "dist": { 917 | "type": "zip", 918 | "url": "https://api.github.com/repos/zendframework/ZendService_Apple_Apns/zipball/a8919519edf9ac4658e7f61cb39c4dfe65b5bd49", 919 | "reference": "a8919519edf9ac4658e7f61cb39c4dfe65b5bd49", 920 | "shasum": "" 921 | }, 922 | "require": { 923 | "php": "^5.6 || ^7.0", 924 | "zendframework/zend-json": "^2.0 || ^3.0" 925 | }, 926 | "require-dev": { 927 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.5", 928 | "zendframework/zend-coding-standard": "~1.0.0" 929 | }, 930 | "type": "library", 931 | "extra": { 932 | "branch-alias": { 933 | "dev-master": "1.4.x-dev", 934 | "dev-develop": "1.5.x-dev" 935 | } 936 | }, 937 | "autoload": { 938 | "psr-4": { 939 | "ZendService\\Apple\\": "src/" 940 | } 941 | }, 942 | "notification-url": "https://packagist.org/downloads/", 943 | "license": [ 944 | "BSD-3-Clause" 945 | ], 946 | "description": "OOP Zend Framework wrapper for Apple Push Notification Service", 947 | "keywords": [ 948 | "ZendFramework", 949 | "apns", 950 | "apple", 951 | "notification", 952 | "push", 953 | "zf" 954 | ], 955 | "abandoned": true, 956 | "time": "2019-03-14T17:18:26+00:00" 957 | }, 958 | { 959 | "name": "zendframework/zendservice-google-gcm", 960 | "version": "2.1.1", 961 | "source": { 962 | "type": "git", 963 | "url": "https://github.com/zendframework/ZendService_Google_Gcm.git", 964 | "reference": "141ac74b4a76656dac48bb97c7be4fc336d075ae" 965 | }, 966 | "dist": { 967 | "type": "zip", 968 | "url": "https://api.github.com/repos/zendframework/ZendService_Google_Gcm/zipball/141ac74b4a76656dac48bb97c7be4fc336d075ae", 969 | "reference": "141ac74b4a76656dac48bb97c7be4fc336d075ae", 970 | "shasum": "" 971 | }, 972 | "require": { 973 | "php": "^5.6 || ^7.0", 974 | "zendframework/zend-http": "^2.0", 975 | "zendframework/zend-json": "^2.0 || ^3.0" 976 | }, 977 | "require-dev": { 978 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.5", 979 | "zendframework/zend-coding-standard": "~1.0.0" 980 | }, 981 | "type": "library", 982 | "extra": { 983 | "branch-alias": { 984 | "dev-master": "2.1.x-dev", 985 | "dev-develop": "2.2.x-dev" 986 | } 987 | }, 988 | "autoload": { 989 | "psr-4": { 990 | "ZendService\\Google\\": "src/" 991 | } 992 | }, 993 | "notification-url": "https://packagist.org/downloads/", 994 | "license": [ 995 | "BSD-3-Clause" 996 | ], 997 | "description": "OOP wrapper for Google Cloud Messaging", 998 | "keywords": [ 999 | "ZendFramework", 1000 | "gcm", 1001 | "google", 1002 | "notification", 1003 | "push", 1004 | "zf" 1005 | ], 1006 | "abandoned": true, 1007 | "time": "2019-02-07T18:01:00+00:00" 1008 | } 1009 | ], 1010 | "packages-dev": [ 1011 | { 1012 | "name": "atoum/atoum", 1013 | "version": "3.4.2", 1014 | "source": { 1015 | "type": "git", 1016 | "url": "https://github.com/atoum/atoum.git", 1017 | "reference": "e90606b89e62c5c18c5d02596078edf55f35b3c3" 1018 | }, 1019 | "dist": { 1020 | "type": "zip", 1021 | "url": "https://api.github.com/repos/atoum/atoum/zipball/e90606b89e62c5c18c5d02596078edf55f35b3c3", 1022 | "reference": "e90606b89e62c5c18c5d02596078edf55f35b3c3", 1023 | "shasum": "" 1024 | }, 1025 | "require": { 1026 | "ext-hash": "*", 1027 | "ext-json": "*", 1028 | "ext-tokenizer": "*", 1029 | "ext-xml": "*", 1030 | "php": "^5.6.0 || ^7.0.0 <7.5.0" 1031 | }, 1032 | "replace": { 1033 | "mageekguy/atoum": "*" 1034 | }, 1035 | "require-dev": { 1036 | "friendsofphp/php-cs-fixer": "^2" 1037 | }, 1038 | "suggest": { 1039 | "atoum/stubs": "Provides IDE support (like autocompletion) for atoum", 1040 | "ext-mbstring": "Provides support for UTF-8 strings", 1041 | "ext-xdebug": "Provides code coverage report (>= 2.3)" 1042 | }, 1043 | "bin": [ 1044 | "bin/atoum" 1045 | ], 1046 | "type": "library", 1047 | "extra": { 1048 | "branch-alias": { 1049 | "dev-master": "3.x-dev" 1050 | } 1051 | }, 1052 | "autoload": { 1053 | "classmap": [ 1054 | "classes/" 1055 | ] 1056 | }, 1057 | "notification-url": "https://packagist.org/downloads/", 1058 | "license": [ 1059 | "BSD-3-Clause" 1060 | ], 1061 | "authors": [ 1062 | { 1063 | "name": "Frédéric Hardy", 1064 | "email": "frederic.hardy@atoum.org", 1065 | "homepage": "http://blog.mageekbox.net" 1066 | }, 1067 | { 1068 | "name": "François Dussert", 1069 | "email": "francois.dussert@atoum.org" 1070 | }, 1071 | { 1072 | "name": "Gérald Croes", 1073 | "email": "gerald.croes@atoum.org" 1074 | }, 1075 | { 1076 | "name": "Julien Bianchi", 1077 | "email": "julien.bianchi@atoum.org" 1078 | }, 1079 | { 1080 | "name": "Ludovic Fleury", 1081 | "email": "ludovic.fleury@atoum.org" 1082 | } 1083 | ], 1084 | "description": "Simple modern and intuitive unit testing framework for PHP 5.3+", 1085 | "homepage": "http://www.atoum.org", 1086 | "keywords": [ 1087 | "TDD", 1088 | "atoum", 1089 | "test", 1090 | "unit testing" 1091 | ], 1092 | "time": "2020-03-04T10:29:09+00:00" 1093 | }, 1094 | { 1095 | "name": "atoum/stubs", 1096 | "version": "2.6.0", 1097 | "source": { 1098 | "type": "git", 1099 | "url": "https://github.com/atoum/stubs.git", 1100 | "reference": "df8b73b0358de7283ecba91d8f4a9683f583993d" 1101 | }, 1102 | "dist": { 1103 | "type": "zip", 1104 | "url": "https://api.github.com/repos/atoum/stubs/zipball/df8b73b0358de7283ecba91d8f4a9683f583993d", 1105 | "reference": "df8b73b0358de7283ecba91d8f4a9683f583993d", 1106 | "shasum": "" 1107 | }, 1108 | "suggest": { 1109 | "atoum/atoum": "Include atoum in your projet dependencies" 1110 | }, 1111 | "type": "library", 1112 | "extra": { 1113 | "branch-alias": { 1114 | "dev-master": "2.x-dev" 1115 | } 1116 | }, 1117 | "notification-url": "https://packagist.org/downloads/", 1118 | "license": [ 1119 | "BSD-3-Clause" 1120 | ], 1121 | "authors": [ 1122 | { 1123 | "name": "Julien Bianchi", 1124 | "email": "julien.bianchi@atoum.org" 1125 | }, 1126 | { 1127 | "name": "Ludovic Fleury", 1128 | "email": "ludovic.fleury@atoum.org" 1129 | } 1130 | ], 1131 | "description": "Stubs for atoum, the simple modern and intuitive unit testing framework for PHP 5.3+", 1132 | "homepage": "http://www.atoum.org", 1133 | "keywords": [ 1134 | "TDD", 1135 | "atoum", 1136 | "test", 1137 | "unit testing" 1138 | ], 1139 | "time": "2018-01-29T22:41:37+00:00" 1140 | }, 1141 | { 1142 | "name": "atoum/visibility-extension", 1143 | "version": "1.3.0", 1144 | "source": { 1145 | "type": "git", 1146 | "url": "https://github.com/atoum/visibility-extension.git", 1147 | "reference": "88f2a7f5b607c70d47674b38d6cb7d445faa993c" 1148 | }, 1149 | "dist": { 1150 | "type": "zip", 1151 | "url": "https://api.github.com/repos/atoum/visibility-extension/zipball/88f2a7f5b607c70d47674b38d6cb7d445faa993c", 1152 | "reference": "88f2a7f5b607c70d47674b38d6cb7d445faa993c", 1153 | "shasum": "" 1154 | }, 1155 | "require": { 1156 | "atoum/atoum": "^2.9 | ^3.0" 1157 | }, 1158 | "type": "library", 1159 | "autoload": { 1160 | "psr-4": { 1161 | "mageekguy\\atoum\\visibility\\": "classes" 1162 | }, 1163 | "files": [ 1164 | "configuration.php" 1165 | ] 1166 | }, 1167 | "notification-url": "https://packagist.org/downloads/", 1168 | "license": [ 1169 | "BSD-3-Clause" 1170 | ], 1171 | "authors": [ 1172 | { 1173 | "name": "jubianchi", 1174 | "email": "contact@jubianchi.fr" 1175 | } 1176 | ], 1177 | "description": "atoum extension to loosen method visibility", 1178 | "homepage": "http://www.atoum.org", 1179 | "keywords": [ 1180 | "TDD", 1181 | "atoum", 1182 | "atoum-extension", 1183 | "test", 1184 | "unit testing", 1185 | "visibility" 1186 | ], 1187 | "time": "2017-02-24T13:25:41+00:00" 1188 | }, 1189 | { 1190 | "name": "symfony/var-dumper", 1191 | "version": "v3.4.0", 1192 | "source": { 1193 | "type": "git", 1194 | "url": "https://github.com/symfony/var-dumper.git", 1195 | "reference": "ec650a975a8e04e0c114d35eab732981243db3a2" 1196 | }, 1197 | "dist": { 1198 | "type": "zip", 1199 | "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ec650a975a8e04e0c114d35eab732981243db3a2", 1200 | "reference": "ec650a975a8e04e0c114d35eab732981243db3a2", 1201 | "shasum": "" 1202 | }, 1203 | "require": { 1204 | "php": "^5.5.9|>=7.0.8", 1205 | "symfony/polyfill-mbstring": "~1.0" 1206 | }, 1207 | "conflict": { 1208 | "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" 1209 | }, 1210 | "require-dev": { 1211 | "ext-iconv": "*", 1212 | "twig/twig": "~1.34|~2.4" 1213 | }, 1214 | "suggest": { 1215 | "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", 1216 | "ext-intl": "To show region name in time zone dump", 1217 | "ext-symfony_debug": "" 1218 | }, 1219 | "type": "library", 1220 | "extra": { 1221 | "branch-alias": { 1222 | "dev-master": "3.4-dev" 1223 | } 1224 | }, 1225 | "autoload": { 1226 | "files": [ 1227 | "Resources/functions/dump.php" 1228 | ], 1229 | "psr-4": { 1230 | "Symfony\\Component\\VarDumper\\": "" 1231 | }, 1232 | "exclude-from-classmap": [ 1233 | "/Tests/" 1234 | ] 1235 | }, 1236 | "notification-url": "https://packagist.org/downloads/", 1237 | "license": [ 1238 | "MIT" 1239 | ], 1240 | "authors": [ 1241 | { 1242 | "name": "Nicolas Grekas", 1243 | "email": "p@tchwork.com" 1244 | }, 1245 | { 1246 | "name": "Symfony Community", 1247 | "homepage": "https://symfony.com/contributors" 1248 | } 1249 | ], 1250 | "description": "Symfony mechanism for exploring and dumping PHP variables", 1251 | "homepage": "https://symfony.com", 1252 | "keywords": [ 1253 | "debug", 1254 | "dump" 1255 | ], 1256 | "time": "2017-11-30T14:59:23+00:00" 1257 | } 1258 | ], 1259 | "aliases": [], 1260 | "minimum-stability": "stable", 1261 | "stability-flags": [], 1262 | "prefer-stable": false, 1263 | "prefer-lowest": false, 1264 | "platform": { 1265 | "php": ">=5.6", 1266 | "ext-ctype": "*" 1267 | }, 1268 | "platform-dev": [], 1269 | "plugin-api-version": "1.1.0" 1270 | } 1271 | --------------------------------------------------------------------------------