From 67e0df26884a0940defb7e5c37ebda78ece756d8 Mon Sep 17 00:00:00 2001 From: Anton Tuyakhov Date: Sat, 7 Jul 2018 09:54:30 +0200 Subject: [PATCH] Introduce database notifications --- .gitignore | 4 +- README.md | 43 +- composer.json | 4 +- composer.lock | 1755 -------------------- src/NotifiableTrait.php | 16 + src/{ => behaviors}/NotifiableBehavior.php | 11 +- src/behaviors/ReadableBehavior.php | 64 + src/channels/ActiveRecordChannel.php | 45 + src/messages/AbstractMessage.php | 4 +- src/messages/DatabaseMessage.php | 11 + src/migrations/notifications_table.php | 35 + src/models/Notification.php | 58 + tests/ActiveRecordChannelTest.php | 54 + tests/MailChannelTest.php | 15 +- tests/NotifierTest.php | 10 +- tests/TwilioChannelTest.php | 10 +- 16 files changed, 351 insertions(+), 1788 deletions(-) delete mode 100644 composer.lock rename src/{ => behaviors}/NotifiableBehavior.php (88%) create mode 100644 src/behaviors/ReadableBehavior.php create mode 100644 src/channels/ActiveRecordChannel.php create mode 100644 src/messages/DatabaseMessage.php create mode 100644 src/migrations/notifications_table.php create mode 100644 src/models/Notification.php create mode 100644 tests/ActiveRecordChannelTest.php diff --git a/.gitignore b/.gitignore index 97e6293..ebe2b01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea vendor -composer.phar \ No newline at end of file +composer.phar +.DS_Store +composer.lock \ No newline at end of file diff --git a/README.md b/README.md index 55245c5..e79b303 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ Notifier is often used as an application component and configured in the applica 'accountSid' => '...', 'authToken' => '...', 'from' => '+1234567890' + ], + 'database' => [ + 'class' => '\tuyakhov\notifications\channels\ActiveRecordChannel' ] ], ], @@ -117,4 +120,42 @@ You may use the NotifiableInterface and NotifiableTrait on any of your models: return $this->email; } } - ``` \ No newline at end of file + ``` + +#### Database notifications + +The `database` notification channel stores the notification information in a database table. +You can query the table to display the notifications in your application's user interface. But, before you can do that, you will need to create a database table to hold your notifications. To do this, you can use the migration that comes with this extension: +``` +yii migrate --migrationPath=@vendor/tuyakhov/yii2-notifications/src/migrations +``` + +**Accessing The Notifications** +Once notifications are stored in the database, you need a convenient way to access them from your notifiable entities. The `NotifiableTrait`, which comes with this extension, includes a notifications relationship that returns the notifications for the entity. +To fetch notifications, you may access this method like any other `ActiveRecord` relationship. +```php +$model = User::findOne(1); +foreach($model->notifications as $notification) { + echo $notification->subject; +} +``` +If you want to retrieve only the "unread" notifications, you may use the `unreadNotifications` relationship. +```php +$model = User::findOne(1); +foreach($model->unreadNotifications as $notification) { + echo $notification->subject; +} +``` +**Marking Notifications As Read** +Typically, you will want to mark a notification as "read" when a user views it. The `ReadableBehavior` in `Notification` model provides a `markAsRead` method, which updates the read_at column on the notification's database record: +```php +$model = User::findOne(1); +foreach($model->unreadNotifications as $notification) { + $notification->markAsRead(); + + // the following methods are also available + $notification->markAsUnread(); + $notification->isUnread(); + $notification->isRead(); +} +``` \ No newline at end of file diff --git a/composer.json b/composer.json index ccfea59..e7d27ae 100644 --- a/composer.json +++ b/composer.json @@ -19,11 +19,11 @@ }, "require": { "php": ">=5.4.0", - "yiisoft/yii2": "*", + "yiisoft/yii2": "~2.0.13", "yiisoft/yii2-httpclient": "^2.0" }, "require-dev": { - "phpunit/phpunit": "5.5.*" + "phpunit/phpunit": "*" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock deleted file mode 100644 index bf800cd..0000000 --- a/composer.lock +++ /dev/null @@ -1,1755 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "0305e5c68af0372ecbbaeb38438e711f", - "content-hash": "226354d8a87391061ff01c3c479b9dbb", - "packages": [ - { - "name": "bower-asset/jquery", - "version": "2.2.4", - "source": { - "type": "git", - "url": "https://github.com/jquery/jquery-dist.git", - "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72", - "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72", - "shasum": "" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "dist/jquery.js", - "bower-asset-ignore": [ - "package.json" - ] - }, - "license": [ - "MIT" - ], - "keywords": [ - "browser", - "javascript", - "jquery", - "library" - ] - }, - { - "name": "bower-asset/jquery.inputmask", - "version": "3.2.7", - "source": { - "type": "git", - "url": "https://github.com/RobinHerbots/Inputmask.git", - "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", - "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", - "shasum": "" - }, - "require": { - "bower-asset/jquery": ">=1.7" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": [ - "./dist/inputmask/inputmask.js" - ], - "bower-asset-ignore": [ - "**/*", - "!dist/*", - "!dist/inputmask/*", - "!dist/min/*", - "!dist/min/inputmask/*", - "!extra/bindings/*", - "!extra/dependencyLibs/*", - "!extra/phone-codes/*" - ] - }, - "license": [ - "http://opensource.org/licenses/mit-license.php" - ], - "description": "jquery.inputmask is a jquery plugin which create an input mask.", - "keywords": [ - "form", - "input", - "inputmask", - "jquery", - "mask", - "plugins" - ] - }, - { - "name": "bower-asset/punycode", - "version": "v1.3.2", - "source": { - "type": "git", - "url": "https://github.com/bestiejs/punycode.js.git", - "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", - "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", - "shasum": "" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "punycode.js", - "bower-asset-ignore": [ - "coverage", - "tests", - ".*", - "component.json", - "Gruntfile.js", - "node_modules", - "package.json" - ] - } - }, - { - "name": "bower-asset/yii2-pjax", - "version": "v2.0.6", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/jquery-pjax.git", - "reference": "60728da6ade5879e807a49ce59ef9a72039b8978" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", - "reference": "60728da6ade5879e807a49ce59ef9a72039b8978", - "shasum": "" - }, - "require": { - "bower-asset/jquery": ">=1.8" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "./jquery.pjax.js", - "bower-asset-ignore": [ - ".travis.yml", - "Gemfile", - "Gemfile.lock", - "CONTRIBUTING.md", - "vendor/", - "script/", - "test/" - ] - }, - "license": [ - "MIT" - ] - }, - { - "name": "cebe/markdown", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/cebe/markdown.git", - "reference": "c30eb5e01fe021cc5bba2f9ee0eeef96d4931166" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cebe/markdown/zipball/c30eb5e01fe021cc5bba2f9ee0eeef96d4931166", - "reference": "c30eb5e01fe021cc5bba2f9ee0eeef96d4931166", - "shasum": "" - }, - "require": { - "lib-pcre": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "cebe/indent": "*", - "facebook/xhprof": "*@dev", - "phpunit/phpunit": "4.1.*" - }, - "bin": [ - "bin/markdown" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "cebe\\markdown\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Creator" - } - ], - "description": "A super fast, highly extensible markdown parser for PHP", - "homepage": "https://github.com/cebe/markdown#readme", - "keywords": [ - "extensible", - "fast", - "gfm", - "markdown", - "markdown-extra" - ], - "time": "2016-09-14 20:40:20" - }, - { - "name": "ezyang/htmlpurifier", - "version": "v4.8.0", - "source": { - "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", - "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", - "shasum": "" - }, - "require": { - "php": ">=5.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "HTMLPurifier": "library/" - }, - "files": [ - "library/HTMLPurifier.composer.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL" - ], - "authors": [ - { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" - } - ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", - "keywords": [ - "html" - ], - "time": "2016-07-16 12:58:58" - }, - { - "name": "yiisoft/yii2", - "version": "2.0.10", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "5bfcb7a6dfa9771e2248eb8c4448613330f343ff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/5bfcb7a6dfa9771e2248eb8c4448613330f343ff", - "reference": "5bfcb7a6dfa9771e2248eb8c4448613330f343ff", - "shasum": "" - }, - "require": { - "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", - "bower-asset/jquery.inputmask": "~3.2.2", - "bower-asset/punycode": "1.3.*", - "bower-asset/yii2-pjax": "~2.0.1", - "cebe/markdown": "~1.0.0 | ~1.1.0", - "ext-ctype": "*", - "ext-mbstring": "*", - "ezyang/htmlpurifier": "~4.6", - "lib-pcre": "*", - "php": ">=5.4.0", - "yiisoft/yii2-composer": "~2.0.4" - }, - "bin": [ - "yii" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/", - "role": "Founder and project lead" - }, - { - "name": "Alexander Makarov", - "email": "sam@rmcreative.ru", - "homepage": "http://rmcreative.ru/", - "role": "Core framework development" - }, - { - "name": "Maurizio Domba", - "homepage": "http://mdomba.info/", - "role": "Core framework development" - }, - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Core framework development" - }, - { - "name": "Timur Ruziev", - "email": "resurtm@gmail.com", - "homepage": "http://resurtm.com/", - "role": "Core framework development" - }, - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com", - "role": "Core framework development" - }, - { - "name": "Dmitry Naumenko", - "email": "d.naumenko.a@gmail.com", - "role": "Core framework development" - } - ], - "description": "Yii PHP Framework Version 2", - "homepage": "http://www.yiiframework.com/", - "keywords": [ - "framework", - "yii2" - ], - "time": "2016-10-20 12:02:50" - }, - { - "name": "yiisoft/yii2-composer", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7452fd908a5023b8bb5ea1b123a174ca080de464", - "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "yii\\composer\\Plugin", - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\composer\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - } - ], - "description": "The composer plugin for Yii extension installer", - "keywords": [ - "composer", - "extension installer", - "yii2" - ], - "time": "2016-02-06 00:49:24" - }, - { - "name": "yiisoft/yii2-httpclient", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-httpclient.git", - "reference": "05056e8f13fdee09c252686cea62f0889ecab8b7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-httpclient/zipball/05056e8f13fdee09c252686cea62f0889ecab8b7", - "reference": "05056e8f13fdee09c252686cea62f0889ecab8b7", - "shasum": "" - }, - "require": { - "yiisoft/yii2": "~2.0.0" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\httpclient\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com" - } - ], - "description": "HTTP client extension for the Yii framework", - "keywords": [ - "curl", - "http", - "httpclient", - "yii2" - ], - "time": "2016-08-04 13:29:03" - } - ], - "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2015-06-14 21:17:01" - }, - { - "name": "myclabs/deep-copy", - "version": "1.5.4", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/ea74994a3dc7f8d2f65a06009348f2d63c81e61f", - "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2016-09-16 13:37:59" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "1.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2015-12-27 11:43:31" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "shasum": "" - }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", - "webmozart/assert": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30 07:12:33" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "0.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", - "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", - "shasum": "" - }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "time": "2016-06-10 07:14:17" - }, - { - "name": "phpspec/prophecy", - "version": "v1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2016-06-07 08:13:47" - }, - { - "name": "phpunit/php-code-coverage", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3", - "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "^1.4.2", - "sebastian/code-unit-reverse-lookup": "~1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "~1.0|~2.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "^5.4" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.4.0", - "ext-xmlwriter": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2016-07-26 14:39:29" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2015-06-21 13:08:43" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21 13:50:34" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4|~5" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2016-05-12 18:03:57" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2015-09-15 10:49:45" - }, - { - "name": "phpunit/phpunit", - "version": "5.5.7", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3f67cee782c9abfaee5e32fd2f57cdd54bc257ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3f67cee782c9abfaee5e32fd2f57cdd54bc257ba", - "reference": "3f67cee782c9abfaee5e32fd2f57cdd54bc257ba", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "^4.0.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "~1.1", - "sebastian/diff": "~1.2", - "sebastian/environment": "^1.3 || ^2.0", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/object-enumerator": "~1.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", - "symfony/yaml": "~2.1|~3.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" - }, - "require-dev": { - "ext-pdo": "*" - }, - "suggest": { - "ext-tidy": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.5.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2016-10-03 13:04:15" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/238d7a2723bce689c79eeac9c7d5e1d623bb9dc2", - "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2" - }, - "conflict": { - "phpunit/phpunit": "<5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.4" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2016-10-09 07:01:45" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13 06:45:14" - }, - { - "name": "sebastian/comparator", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2015-07-26 15:48:44" - }, - { - "name": "sebastian/diff", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2015-12-08 07:14:41" - }, - { - "name": "sebastian/environment", - "version": "1.3.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2016-08-18 05:49:44" - }, - { - "name": "sebastian/exporter", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2016-06-17 09:04:28" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12 03:26:01" - }, - { - "name": "sebastian/object-enumerator", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", - "shasum": "" - }, - "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-01-28 13:25:10" - }, - { - "name": "sebastian/recursion-context", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" - }, - { - "name": "sebastian/resource-operations", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" - }, - { - "name": "sebastian/version", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-02-04 12:56:52" - }, - { - "name": "symfony/yaml", - "version": "v3.1.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/368b9738d4033c8b93454cb0dbd45d305135a6d3", - "reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2016-09-25 08:27:07" - }, - { - "name": "webmozart/assert", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "bb2d123231c095735130cc8f6d31385a44c7b308" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308", - "reference": "bb2d123231c095735130cc8f6d31385a44c7b308", - "shasum": "" - }, - "require": { - "php": "^5.3.3|^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2016-08-09 15:02:57" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=5.4.0" - }, - "platform-dev": [] -} diff --git a/src/NotifiableTrait.php b/src/NotifiableTrait.php index 5413aaf..efe52aa 100644 --- a/src/NotifiableTrait.php +++ b/src/NotifiableTrait.php @@ -6,6 +6,8 @@ namespace tuyakhov\notifications; +use tuyakhov\notifications\models\Notification; +use yii\db\BaseActiveRecord; use yii\helpers\Inflector; trait NotifiableTrait @@ -59,6 +61,20 @@ public function routeNotificationFor($channel) return $this->email; case 'twilio': return $this->phone_number; + case 'database': + return [get_class($this), $this->id]; } } + + public function getNotifications() + { + /** @var $this BaseActiveRecord */ + return $this->hasMany(Notification::className(), ['notifiable_id' => 'id']) + ->addOnCondition(['notifiable_type' => get_class($this)]); + } + + public function getUnreadNotifications() + { + return $this->getNotifications()->addOnCondition(['read_at' => null]); + } } \ No newline at end of file diff --git a/src/NotifiableBehavior.php b/src/behaviors/NotifiableBehavior.php similarity index 88% rename from src/NotifiableBehavior.php rename to src/behaviors/NotifiableBehavior.php index c0e4826..4d37d42 100644 --- a/src/NotifiableBehavior.php +++ b/src/behaviors/NotifiableBehavior.php @@ -3,9 +3,12 @@ * @copyright Anton Tuyakhov */ -namespace tuyakhov\notifications; +namespace tuyakhov\notifications\behaviors; +use tuyakhov\notifications\NotifiableInterface; +use tuyakhov\notifications\NotificationInterface; +use tuyakhov\notifications\Notifier; use yii\base\Behavior; use yii\base\Event; use yii\di\Instance; @@ -19,6 +22,9 @@ class NotifiableBehavior extends Behavior */ public $notifier = 'notifier'; + /** + * @throws \yii\base\InvalidConfigException + */ public function init() { parent::init(); @@ -62,8 +68,7 @@ public function detach() /** * Handles the event using public properties. * @param Event $event - * @throws \InvalidArgumentException - * @throws \RuntimeException + * @throws \yii\base\InvalidConfigException */ public function handle(Event $event) { diff --git a/src/behaviors/ReadableBehavior.php b/src/behaviors/ReadableBehavior.php new file mode 100644 index 0000000..666d47d --- /dev/null +++ b/src/behaviors/ReadableBehavior.php @@ -0,0 +1,64 @@ + + */ + +namespace tuyakhov\notifications\behaviors; + + +use yii\base\Behavior; +use yii\db\ActiveRecordInterface; + +class ReadableBehavior extends Behavior +{ + + public $readAttribute = 'read_at'; + + /** + * Mark the notification as read. + * @throws \Exception + * @throws \Throwable + */ + public function markAsRead() + { + /** @var ActiveRecordInterface $model */ + $model = $this->owner; + if (is_null($model->{$this->readAttribute})) { + $model->update(false, [$this->readAttribute => date('Y-m-d H:i:s')]); + } + } + + /** + * Mark the notification as unread. + * @return void + * @throws \Exception + * @throws \Throwable + */ + public function markAsUnread() + { + /** @var ActiveRecordInterface $model */ + $model = $this->owner; + if (!is_null($model->{$this->readAttribute})) { + $model->update(false, [$this->readAttribute => null]); + } + } + + /** + * Determine if a notification has been read. + * + * @return bool + */ + public function isRead() + { + return $this->owner->{$this->readAttribute} !== null; + } + /** + * Determine if a notification has not been read. + * + * @return bool + */ + public function isUnread() + { + return $this->owner->{$this->readAttribute} === null; + } +} \ No newline at end of file diff --git a/src/channels/ActiveRecordChannel.php b/src/channels/ActiveRecordChannel.php new file mode 100644 index 0000000..dab7873 --- /dev/null +++ b/src/channels/ActiveRecordChannel.php @@ -0,0 +1,45 @@ + + */ + +namespace tuyakhov\notifications\channels; + + +use tuyakhov\notifications\messages\DatabaseMessage; +use tuyakhov\notifications\NotifiableInterface; +use tuyakhov\notifications\NotificationInterface; +use yii\base\Component; +use yii\db\ActiveRecordInterface; +use yii\di\Instance; + +class ActiveRecordChannel extends Component implements ChannelInterface +{ + /** + * @var ActiveRecordInterface|string + */ + public $model = 'tuyakhov\notifications\models\DatabaseNotification'; + + /** + * @throws \yii\base\InvalidConfigException + */ + public function init() + { + parent::init(); + $this->model = Instance::ensure($this->model, 'yii\db\ActiveRecordInterface'); + } + + public function send(NotifiableInterface $recipient, NotificationInterface $notification) + { + /** @var DatabaseMessage $message */ + $message = $notification->exportFor('database'); + list($notifiableType, $notifiableId) = $recipient->routeNotificationFor('database'); + $this->model->insert(true, [ + 'level' => $message->level, + 'subject' => $message->subject, + 'body' => $message->body, + 'notifiable_type' => $notifiableType, + 'notifiable_id' => $notifiableId, + ]); + } +} \ No newline at end of file diff --git a/src/messages/AbstractMessage.php b/src/messages/AbstractMessage.php index f5dbf10..03fd233 100644 --- a/src/messages/AbstractMessage.php +++ b/src/messages/AbstractMessage.php @@ -5,9 +5,9 @@ namespace tuyakhov\notifications\messages; -use yii\base\Object; +use yii\base\BaseObject; -abstract class AbstractMessage extends Object +abstract class AbstractMessage extends BaseObject { /** * The "level" of the notification (info, success, error). diff --git a/src/messages/DatabaseMessage.php b/src/messages/DatabaseMessage.php new file mode 100644 index 0000000..81b4ab5 --- /dev/null +++ b/src/messages/DatabaseMessage.php @@ -0,0 +1,11 @@ + + */ + +namespace tuyakhov\notifications\messages; + + +class DatabaseMessage extends AbstractMessage +{ +} \ No newline at end of file diff --git a/src/migrations/notifications_table.php b/src/migrations/notifications_table.php new file mode 100644 index 0000000..4bba6d7 --- /dev/null +++ b/src/migrations/notifications_table.php @@ -0,0 +1,35 @@ + + */ + +namespace tuyakhov\notifications\migrations; + + +use yii\db\Migration; + +class notifications_table extends Migration +{ + public function up() + { + $this->createTable('notification', [ + 'id' => $this->primaryKey(), + 'level' => $this->string(), + 'notifiable_type' => $this->string(), + 'notifiable_id' => $this->integer()->unsigned(), + 'subject' => $this->string(), + 'body' => $this->text(), + 'read_at' => $this->timestamp()->null(), + 'created_at' => $this->timestamp(), + 'updated_at' => $this->timestamp()->null(), + ]); + $this->createIndex('notifiable', 'notification', ['notifiable_type', 'notifiable_id']); + } + + public function down() + { + $this->dropIndex('notifiable', 'notification'); + $this->dropTable('notification'); + } + +} \ No newline at end of file diff --git a/src/models/Notification.php b/src/models/Notification.php new file mode 100644 index 0000000..ce0bec8 --- /dev/null +++ b/src/models/Notification.php @@ -0,0 +1,58 @@ + TimestampBehavior::className(), + 'value' => new Expression('NOW()'), + ], + ReadableBehavior::className() + ]; + } + + public function getNotifiable() + { + return $this->hasOne($this->notifiable_type, ['id' => 'notifiable_id']); + } +} \ No newline at end of file diff --git a/tests/ActiveRecordChannelTest.php b/tests/ActiveRecordChannelTest.php new file mode 100644 index 0000000..4d2f53c --- /dev/null +++ b/tests/ActiveRecordChannelTest.php @@ -0,0 +1,54 @@ + + */ + +namespace tuyakhov\notifications\tests; + + +use tuyakhov\notifications\messages\DatabaseMessage; + +class ActiveRecordChannelTest extends TestCase +{ + public function testSend() + { + $recipient = $this->createMock('tuyakhov\notifications\NotifiableInterface'); + $recipient->expects($this->once()) + ->method('routeNotificationFor') + ->with('database') + ->willReturn(['yii\base\DynamicModel', 123]); + + $message = \Yii::createObject([ + 'class' => DatabaseMessage::className(), + 'level' => 'debug', + 'subject' => 'It', + 'body' => 'Works', + ]); + $notificationModel = $this->createMock('yii\db\ActiveRecordInterface'); + $notificationModel->method('insert'); + $notificationModel->expects($this->once()) + ->method('insert') + ->with(true, [ + 'level' => $message->level, + 'subject' => $message->subject, + 'body' => $message->body, + 'notifiable_type' => 'yii\base\DynamicModel', + 'notifiable_id' => 123, + ]) + ->willReturnSelf(); + + + $channel = \Yii::createObject([ + 'class' => 'tuyakhov\notifications\channels\ActiveRecordChannel', + 'model' => $notificationModel, + ]); + + $notification = $this->createMock('tuyakhov\notifications\NotificationInterface'); + $notification->expects($this->once()) + ->method('exportFor') + ->with('database') + ->willReturn($message); + $channel->send($recipient, $notification); + + } +} \ No newline at end of file diff --git a/tests/MailChannelTest.php b/tests/MailChannelTest.php index 65a881d..bf3ec11 100644 --- a/tests/MailChannelTest.php +++ b/tests/MailChannelTest.php @@ -8,28 +8,25 @@ use tuyakhov\notifications\channels\MailChannel; use tuyakhov\notifications\messages\MailMessage; -use tuyakhov\notifications\NotifiableInterface; -use tuyakhov\notifications\NotificationInterface; -use yii\mail\MailerInterface; -use yii\mail\MessageInterface; class MailChannelTest extends TestCase { public function testSend() { - $recipient = $this->createMock(NotifiableInterface::class); + $recipient = $this->createMock('tuyakhov\notifications\NotifiableInterface'); $recipient->expects($this->once()) ->method('routeNotificationFor') ->with('mail') ->willReturn('test@test.com'); - $mailer = $this->createMock(MailerInterface::class); - $message = $this->createMock(MessageInterface::class); + $mailer = $this->createMock('yii\mail\MailerInterface'); + $message = $this->createMock('yii\mail\MessageInterface'); $message->method('send'); $message->expects($this->once())->method('setTo')->with('test@test.com')->willReturnSelf(); $message->expects($this->once())->method('setFrom')->with('test@admin.com')->willReturnSelf(); - + $message->expects($this->once())->method('setSubject')->willReturnSelf(); + $mailer->expects($this->once()) ->method('compose') ->with(['html' => 'testView'], ['name' => 'Test Name']) @@ -41,7 +38,7 @@ public function testSend() 'from' => 'test@admin.com' ]); - $notification = $this->createMock(NotificationInterface::class); + $notification = $this->createMock('tuyakhov\notifications\NotificationInterface'); $notification->expects($this->once()) ->method('exportFor') ->with('mail') diff --git a/tests/NotifierTest.php b/tests/NotifierTest.php index dd6589f..0278197 100644 --- a/tests/NotifierTest.php +++ b/tests/NotifierTest.php @@ -4,10 +4,6 @@ */ namespace tuyakhov\notifications\tests; -use tuyakhov\notifications\channels\ChannelInterface; -use tuyakhov\notifications\NotifiableInterface; -use tuyakhov\notifications\NotificationChannelInterface; -use tuyakhov\notifications\NotificationInterface; use tuyakhov\notifications\Notifier; class NotifierTest extends TestCase @@ -23,7 +19,7 @@ protected function setUp() $this->notifier = \Yii::createObject([ 'class' => Notifier::className(), 'channels' => [ - 'mockChannel' => $this->createMock(ChannelInterface::class) + 'mockChannel' => $this->createMock('tuyakhov\notifications\channels\ChannelInterface') ] ]); } @@ -31,10 +27,10 @@ protected function setUp() public function testSend() { - $notification = $this->createMock(NotificationInterface::class); + $notification = $this->createMock('tuyakhov\notifications\NotificationInterface'); $notification->method('broadcastOn')->willReturn(['mockChannel']); - $recipient = $this->createMock(NotifiableInterface::class); + $recipient = $this->createMock('tuyakhov\notifications\NotifiableInterface'); $recipient->method('shouldReceiveNotification')->willReturn(true); $recipient->method('viaChannels')->willReturn(['mockChannel']); diff --git a/tests/TwilioChannelTest.php b/tests/TwilioChannelTest.php index b75adaa..a84314f 100644 --- a/tests/TwilioChannelTest.php +++ b/tests/TwilioChannelTest.php @@ -6,23 +6,17 @@ namespace tuyakhov\notifications\tests; -use tuyakhov\notifications\channels\MailChannel; use tuyakhov\notifications\channels\TwilioChannel; -use tuyakhov\notifications\messages\MailMessage; use tuyakhov\notifications\messages\SmsMessage; -use tuyakhov\notifications\NotifiableInterface; -use tuyakhov\notifications\NotificationInterface; use yii\httpclient\Client; use yii\httpclient\Request; -use yii\mail\MailerInterface; -use yii\mail\MessageInterface; class TwilioChannelTest extends TestCase { public function testSend() { - $recipient = $this->createMock(NotifiableInterface::class); + $recipient = $this->createMock('tuyakhov\notifications\NotifiableInterface'); $recipient->expects($this->once()) ->method('routeNotificationFor') ->with('sms') @@ -43,7 +37,7 @@ public function testSend() 'httpClient' => $client ]); - $notification = $this->createMock(NotificationInterface::class); + $notification = $this->createMock('tuyakhov\notifications\NotificationInterface'); $notification->expects($this->once()) ->method('exportFor') ->with('sms')