├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Tricki │ └── Notification │ │ ├── Facade.php │ │ ├── Models │ │ ├── AbstractEloquent.php │ │ ├── Notification.php │ │ └── NotificationUser.php │ │ ├── Notification.php │ │ └── NotificationServiceProvider.php ├── config │ ├── .gitkeep │ └── config.php └── migrations │ ├── .gitkeep │ ├── 2015_01_24_124832_create_notifications_tables.php │ └── 2015_02_08_132023_make_read_at_nullable.php └── tests └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install --prefer-source --no-interaction --dev 12 | 13 | script: phpunit 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas 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 10 | furnished 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel 4 Notification 2 | ====== 3 | 4 | A basic starting point for a flexible user notification system in Laravel 4. 5 | 6 | It is easily extendable with new notification types and leaves rendering completely up to you. 7 | 8 | This package only provides an extendable notification system without any controllers or views 9 | since they are often very use case specific. 10 | 11 | I'm open to ideas for extending this package. 12 | 13 | ## Installation 14 | 15 | ### 1. Install with Composer 16 | 17 | ```bash 18 | composer require tricki/laravel-notification:@dev 19 | ``` 20 | 21 | This will update `composer.json` and install it into the `vendor/` directory. 22 | 23 | (See the [Packagist website](https://packagist.org/packages/tricki/laravel-notification) for a list of available version numbers and 24 | development releases.) 25 | 26 | ### 2. Add to Providers in `config/app.php` 27 | 28 | ```php 29 | 'providers' => [ 30 | // ... 31 | 'Tricki\Notification\NotificationServiceProvider', 32 | ], 33 | ``` 34 | 35 | This registers the package with Laravel and automatically creates an alias called 36 | `Notification`. 37 | 38 | ### 3. Publishing config 39 | 40 | If your models are namespaced you will have to declare this in the package configuration. 41 | 42 | Publish the package configuration using Artisan: 43 | 44 | ```bash 45 | php artisan config:publish tricki/laravel-notification 46 | ``` 47 | 48 | Set the `namespace` property of the newly created `app/config/packages/tricki/laravel-notification/config.php` 49 | to the namespace of your notification models. 50 | 51 | #### Example 52 | 53 | ```php 54 | 'namespace' => '\MyApp\Models\' 55 | ``` 56 | 57 | ### 4. Executing migration 58 | 59 | ```bash 60 | php artisan migrate --package="tricki/laravel-notification" 61 | ``` 62 | 63 | ### 5. Adding relationship to User 64 | 65 | Extend your User model with the following relationship: 66 | 67 | ```php 68 | 69 | public function notifications() 70 | { 71 | return $this->hasMany('\Tricki\Notification\Models\NotificationUser'); 72 | } 73 | 74 | ``` 75 | 76 | ## Usage 77 | 78 | ### 1. Define notification models 79 | 80 | You will need separate models for each type of notification. Some examples would 81 | be `PostLikedNotification` or `CommentPostedNotification`. 82 | 83 | These models define the unique behavior of each notification type like it's actions 84 | and rendering. 85 | 86 | A minimal notification model looks like this: 87 | 88 | ```php 89 | Remember to add the namespace of your notification models to this package's `config.php`. 103 | 104 | ### 2. Create a notification 105 | 106 | Notifications can be created using `Notification::create`. 107 | 108 | The function takes 5 parameters: 109 | 110 | * **$type** string 111 | The notification type (see [Define notification models](#1-define-notification-models)) 112 | * **$sender** Model 113 | The object that initiated the notification (a user, a group, a web service etc.) 114 | * **$object** Model | NULL 115 | An object that was changed (a post that has been liked). 116 | * **$users** array | Collection | User 117 | The user(s) which should receive this notification. 118 | * **$data** mixed | NULL 119 | Any additional data you want to attach. This will be serialized into the database. 120 | 121 | ### 3. Retrieving a user's notifications 122 | 123 | You can get a collection of notifications sent to a user using the `notifications` relationship, 124 | which will return a collection of your notification models. 125 | 126 | You can easily get a collection of all notifications sent to a user: 127 | 128 | ```php 129 | $user = User::find($id); 130 | $notifications = $user->notifications; 131 | ``` 132 | 133 | You can also only get read or unread notifications using the `read` and `unread` scopes respectively: 134 | 135 | ```php 136 | $readNotifications = $user->notifications()->read()->get(); 137 | $unreadNotifications = $user->notifications()->unread()->get(); 138 | ``` 139 | 140 | Since the notifications are instances of your own models you can easily have different behavior or 141 | output for each notification type. 142 | 143 | #### Example: 144 | 145 | ```php 146 | 168 | ``` 169 | 170 | ```html 171 | // notifications.blade.php 172 | 173 | 178 | ``` 179 | 180 | This could output: 181 | ```html 182 | 186 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tricki/laravel-notification", 3 | "description": "A basic user notification package for Laravel 4", 4 | "keywords": ["laravel", "notification"], 5 | "homepage": "https://github.com/tricki/Notification", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Thomas Rickenbach", 10 | "email": "thomasrickenbach@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4.0", 15 | "illuminate/support": "4.2.*" 16 | }, 17 | "autoload": { 18 | "classmap": [ 19 | "src/migrations" 20 | ], 21 | "psr-0": { 22 | "Tricki\\Notification\\": "src/" 23 | } 24 | }, 25 | "minimum-stability": "stable" 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Tricki/Notification/Facade.php: -------------------------------------------------------------------------------- 1 | isSuperType) 28 | { 29 | return $this->newInstance(); 30 | } 31 | else 32 | { 33 | if (!isset($attributes[$this->typeField])) 34 | { 35 | throw new \DomainException($this->typeField . ' not present in the records of a Super Model'); 36 | } 37 | else 38 | { 39 | $class = $this->getClass($attributes[$this->typeField]); 40 | return new $class; 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Create a new model instance requested by the builder. 47 | * 48 | * @param array $attributes 49 | * @return Illuminate\Database\Eloquent\Model 50 | */ 51 | public function newFromBuilder($attributes = array()) 52 | { 53 | $m = $this->mapData((array) $attributes)->newInstance(array(), true); 54 | $m->setRawAttributes((array) $attributes, true); 55 | return $m; 56 | } 57 | 58 | /** 59 | * Get a new query builder for the model's table. 60 | * 61 | * @return Reposed\Builder 62 | */ 63 | public function newRawQuery() 64 | { 65 | $builder = $this->newEloquentBuilder($this->newBaseQueryBuilder()); 66 | 67 | // Once we have the query builders, we will set the model instances 68 | // so the builder can easily access any information it may need 69 | // from the model while it is constructing and executing various 70 | // queries against it. 71 | $builder->setModel($this)->with($this->with); 72 | return $builder; 73 | } 74 | 75 | /** 76 | * Get a new query builder for the model. 77 | * set any type of scope you want on this builder in a child class, and it'll 78 | * keep applying the scope on any read-queries on this model 79 | * 80 | * @return Reposed\Builder 81 | */ 82 | public function newQuery($excludeDeleted = true) 83 | { 84 | $builder = parent::newQuery($excludeDeleted); 85 | 86 | if ($this->isSubType()) 87 | { 88 | $builder->where($this->typeField, $this->getClass($this->typeField)); 89 | } 90 | 91 | return $builder; 92 | } 93 | 94 | protected function isSubType() 95 | { 96 | return $this->isSubType; 97 | } 98 | 99 | protected function getClass($type) 100 | { 101 | return get_class($this); 102 | } 103 | 104 | protected function getType() 105 | { 106 | return get_class($this); 107 | } 108 | 109 | /** 110 | * Save the model to the database. 111 | * 112 | * @return bool 113 | */ 114 | public function save(array $options = array()) 115 | { 116 | if ($this->isSubType()) 117 | { 118 | $this->attributes[$this->typeField] = $this->getType(); 119 | } 120 | 121 | return parent::save($options); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/Tricki/Notification/Models/Notification.php: -------------------------------------------------------------------------------- 1 | hasMany('Tricki\Notification\Models\NotificationUser', 'notification_id'); 33 | } 34 | 35 | public function newPivot(\Eloquent $parent, array $attributes, $table, $exists) 36 | { 37 | return new NotificationUser($parent, $attributes, $table, $exists); 38 | } 39 | 40 | public function sender() 41 | { 42 | return $this->morphTo(); 43 | } 44 | 45 | public function object() 46 | { 47 | return $this->morphTo(); 48 | } 49 | 50 | public function scopeUnread($query) 51 | { 52 | return $query->where('read_at', NULL); 53 | } 54 | 55 | public function scopeRead($query) 56 | { 57 | return $query->whereNotNull('read_at'); 58 | } 59 | 60 | protected function isSubType() 61 | { 62 | return get_class() !== get_class($this); 63 | } 64 | 65 | protected function getClass($type) 66 | { 67 | return \Notification::getClass($type); 68 | } 69 | 70 | protected function getType() 71 | { 72 | return static::$type; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/Tricki/Notification/Models/NotificationUser.php: -------------------------------------------------------------------------------- 1 | table; 37 | } 38 | parent::__construct($parent, $attributes, $table, $exists); 39 | } 40 | 41 | public static function boot() 42 | { 43 | parent::boot(); 44 | 45 | static::created(function($model) 46 | { 47 | $responses = Event::fire('notification::assigned', array($model)); 48 | }); 49 | 50 | static::saving(function($model) 51 | { 52 | $model->updateTimestamps(); 53 | }); 54 | } 55 | 56 | public function user() 57 | { 58 | return $this->belongsTo(Config::get('auth.model')); 59 | } 60 | 61 | public function notification() 62 | { 63 | return $this->belongsTo('Tricki\Notification\Models\Notification'); 64 | } 65 | 66 | public function scopeUnread($query) 67 | { 68 | return $query->where('notification_user.read_at', NULL); 69 | } 70 | 71 | public function scopeRead($query) 72 | { 73 | return $query->whereNotNull('notification_user.read_at'); 74 | } 75 | 76 | /** 77 | * Mark the user notification as read 78 | * 79 | * @return \Tricki\Notification\Models\NotificationUser 80 | */ 81 | public function setRead() 82 | { 83 | $this->read_at = new \DateTime(); 84 | $this->save(); 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Mark the user notification as unread 91 | * 92 | * @return \Tricki\Notification\Models\NotificationUser 93 | */ 94 | public function setUnread() 95 | { 96 | $this->read_at = NULL; 97 | $this->save(); 98 | 99 | return $this; 100 | } 101 | 102 | public function render() 103 | { 104 | return $this->notification->render(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/Tricki/Notification/Notification.php: -------------------------------------------------------------------------------- 1 | getClass($type); 43 | $notification = new $class(); 44 | 45 | if ($data) 46 | { 47 | $notification->data = $data; 48 | } 49 | $notification->sender()->associate($sender); 50 | if ($object) 51 | { 52 | $notification->object()->associate($object); 53 | } 54 | $notification->save(); 55 | 56 | $notification_users = array(); 57 | foreach ($users as $user) 58 | { 59 | $notification_user = new Models\NotificationUser($notification); 60 | $notification_user->user_id = $user->id; 61 | $notification_users[] = $notification_user; 62 | } 63 | $notification->users()->saveMany($notification_users); 64 | 65 | return $notification; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/Tricki/Notification/NotificationServiceProvider.php: -------------------------------------------------------------------------------- 1 | package('tricki/laravel-notification'); 25 | } 26 | 27 | /** 28 | * Register the service provider. 29 | * 30 | * @return void 31 | */ 32 | public function register() 33 | { 34 | $this->app->singleton('notification', function() 35 | { 36 | return new Notification; 37 | }); 38 | $this->app->booting(function() 39 | { 40 | $loader = \Illuminate\Foundation\AliasLoader::getInstance(); 41 | $loader->alias('Notification', 'Tricki\Notification\Facade'); 42 | }); 43 | } 44 | 45 | /** 46 | * Get the services provided by the provider. 47 | * 48 | * @return array 49 | */ 50 | public function provides() 51 | { 52 | return array('notification'); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/config/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tricki/laravel-notification/b8b72273e01faaf15f91cc528b4643ceaffd4709/src/config/.gitkeep -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | '' 5 | ); -------------------------------------------------------------------------------- /src/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tricki/laravel-notification/b8b72273e01faaf15f91cc528b4643ceaffd4709/src/migrations/.gitkeep -------------------------------------------------------------------------------- /src/migrations/2015_01_24_124832_create_notifications_tables.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | switch(Config::get('notification::type_format')) { 19 | case 'integer': 20 | $table->integer('type')->unsigned(); 21 | break; 22 | default: 23 | case 'class': 24 | $table->string('type'); 25 | } 26 | $table->morphs('sender'); 27 | $table->morphs('object'); 28 | $table->text('data')->nullable(); 29 | $table->timestamps(); 30 | }); 31 | 32 | Schema::create('notification_user', function(Blueprint $table) 33 | { 34 | $table->increments('id'); 35 | $table->integer('notification_id'); 36 | $table->integer('user_id'); 37 | $table->timestamp('read_at'); 38 | $table->timestamps(); 39 | $table->softDeletes(); 40 | }); 41 | } 42 | 43 | /** 44 | * Reverse the migrations. 45 | * 46 | * @return void 47 | */ 48 | public function down() 49 | { 50 | Schema::drop('notifications'); 51 | Schema::drop('notification_user'); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/migrations/2015_02_08_132023_make_read_at_nullable.php: -------------------------------------------------------------------------------- 1 |