├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Providers │ └── SchedulableServiceProvider.php ├── Scopes │ └── SchedulableScope.php └── Traits │ └── Schedulable.php └── tests ├── Database ├── Migration │ └── 2020_10_05_184903_create_posts_table.php └── SchedulableMigrationTest.php ├── Models ├── PostTest.php └── PostTestCustomColumn.php ├── OrchestraTestCase.php ├── SchedulableScopeTest.php └── SchedulableTraitTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /composer.lock -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Neelkanth Kaushik 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Laravel Schedulable Logo](https://github.com/neelkanthk/repo_logos/blob/master/LaravelSchedulable_small.png?raw=true) 2 | 3 | ![](https://img.shields.io/github/v/release/neelkanthk/laravel-schedulable?style=for-the-badge) 4 | ![](https://img.shields.io/packagist/php-v/neelkanthk/laravel-schedulable.svg?style=for-the-badge) 5 | ![](https://img.shields.io/badge/Laravel-%3E%3D6.0-red?style=for-the-badge) 6 | ![](https://img.shields.io/badge/Tests-Passing-green?style=for-the-badge) 7 | ![](https://img.shields.io/github/issues/neelkanthk/laravel-schedulable?style=for-the-badge) 8 | ![](https://img.shields.io/github/license/neelkanthk/laravel-schedulable?style=for-the-badge) 9 | 10 | # Laravel Schedulable [![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fneelkanthk%2Flaravel-schedulable)](https://twitter.com/intent/tweet?text=Laravel%20Schedulable:&url=https%3A%2F%2Fgithub.com%2Fneelkanthk%2Flaravel-schedulable) 11 | 12 | ## Schedule and Unschedule any eloquent model elegantly without cron job. 13 | 14 | ## Salient Features: 15 | 16 | 1. __Turn any Eloquent Model into a schedulable__ one by using ```Schedulable``` trait in the model. 17 | 18 | 2. __Schedule Models to a time in future__ and they will be returned in query results at specified date and time. 19 | 20 | 3. __Reschedule__ and __Unschedule__ at any time using simple methods. 21 | 22 | 4. Hook into the model's life cycle via __custom model events__ provided by the package. 23 | 24 | 5. __Override the default column name__ and use your own custom column name. 25 | 26 | 27 | __*Some example use cases when this package can be useful:*__ 28 | 29 | 1. A Blog type application which allows bloggers to schedule their post to go public on a future date and time. 30 | 31 | 2. An E-commerce website where the items in the inventory can be added at any time from the admin panel but they can be scheduled to be made available to the customers at a particular date and time. 32 | 33 | ## Minimum Requirements 34 | 35 | 1. Laravel 6.0 36 | 2. PHP 7.2 37 | 38 | ## Installation 39 | 40 | ```bash 41 | composer require neelkanthk/laravel-schedulable 42 | ``` 43 | 44 | ## Usage 45 | 46 | #### 1. Create a migration to add ```schedule_at``` column in any table using package's ```scheduleAt();``` method which creates a column with name ```schedule_at```. 47 | 48 | #### *NOTE:* If you want to use any other column name then simply use the ```$table->timestamp('column_name');``` method as shown below in examples. 49 | 50 | ```php 51 | use Illuminate\Database\Migrations\Migration; 52 | use Illuminate\Database\Schema\Blueprint; 53 | use Illuminate\Support\Facades\Schema; 54 | 55 | class AddScheduleAtColumnInPosts extends Migration 56 | { 57 | /** 58 | * Run the migrations. 59 | * 60 | * @return void 61 | */ 62 | public function up() 63 | { 64 | Schema::table('posts', function (Blueprint $table) { 65 | $table->scheduleAt(); //Using default schedule_at column 66 | //or 67 | $table->timestamp('publish_at', 0)->nullable(); //Using custom column name 68 | }); 69 | } 70 | 71 | /** 72 | * Reverse the migrations. 73 | * 74 | * @return void 75 | */ 76 | public function down() 77 | { 78 | Schema::table('posts', function (Blueprint $table) { 79 | $table->dropColumn('schedule_at'); //Using default schedule_at column 80 | //or 81 | $table->dropColumn('publish_at'); //Using custom column name 82 | }); 83 | } 84 | } 85 | ``` 86 | 87 | #### 2. Use the ```Neelkanth\Laravel\Schedulable\Traits\Schedulable``` trait in any Model. 88 | 89 | #### *NOTE:* If you have used a custom column name in the migration then you have to specify that column in the Model as shown below. 90 | 91 | ```php 92 | use Illuminate\Database\Eloquent\Model; 93 | use Neelkanth\Laravel\Schedulable\Traits\Schedulable; 94 | 95 | class Post extends Model 96 | { 97 | use Schedulable; 98 | 99 | const SCHEDULE_AT = "publish_at"; //Specify the custom column name 100 | } 101 | ``` 102 | 103 | ## Usage 104 | 105 | ### 1. Scheduling a model 106 | 107 | ```php 108 | $scheduleAt = Carbon::now()->addDays(10); //Carbon is just an example. You can pass any object which is implementing DateTimeInterface. 109 | $post = new App\Post(); 110 | //Add values to other attributes 111 | $post->scheduleWithoutSaving($scheduleAt); // Modifies the schedule_at attribute and returns the current model object without saving it. 112 | $post->schedule($scheduleAt); //Saves the model in the database and returns boolean true or false 113 | ``` 114 | 115 | ### 2. Unscheduling a model 116 | 117 | ```php 118 | $post = App\Post::find(1); 119 | $post->unscheduleWithoutSaving(); // Modifies the schedule_at attribute and returns the current model object without saving it. 120 | $post->unschedule(); //Saves the model in the database and returns boolean true or false 121 | ``` 122 | 123 | ### 3. Events and Observers 124 | 125 | The package provides four model events and Observer methods which the developers can use to hook in the model's lifecycle. 126 | 127 | The ```schedule()``` method fires two events namely ```scheduling``` before saving the model and ```scheduled``` after saving the model. 128 | 129 | The ```unschedule()``` method fires two events namely ```unscheduling``` before saving the model and ```unscheduled``` after saving the model. 130 | 131 | The above events can be caught in the Observer class as follows: 132 | 133 | ```php 134 | namespace App\Observers; 135 | 136 | use App\Post; 137 | 138 | class PostObserver 139 | { 140 | public function scheduling(Post $post) 141 | { 142 | // 143 | } 144 | 145 | public function scheduled(Post $post) 146 | { 147 | // 148 | } 149 | 150 | public function unscheduling(Post $post) 151 | { 152 | // 153 | } 154 | 155 | public function unscheduled(Post $post) 156 | { 157 | // 158 | } 159 | } 160 | ``` 161 | 162 | ### 4. Fetching data using queries 163 | 164 | We will assume below posts table as reference to the following examples: 165 | 166 | | id | title | created_at | updated_at | schedule_at | 167 | |----|--------------|---------------------|------------|---------------------| 168 | | 1 | Toy Story 1 | 2020-06-01 12:15:00 | NULL | NULL | 169 | | 2 | Toy Story 2 | 2020-08-02 16:10:12 | NULL | 2020-08-10 10:10:00 | 170 | | 3 | Toy Story 3 | 2020-10-10 10:00:10 | NULL | 2021-12-20 00:00:00 | 171 | | 4 | Terminator 2 | 2020-10-11 00:00:00 | NULL | 2021-11-12 15:10:17 | 172 | 173 | __For the following examples, Suppose the current timestamp is 2020-10-18 00:00:00.__ 174 | 175 | #### 1. Default 176 | 177 | By default all those models are fetched in which the ```schedule_at``` column is having ```NULL``` value or a timestamp less than or equal to the current timestamp. 178 | 179 | So a eloquent query 180 | ```php 181 | $posts = App\Post::get(); 182 | ``` 183 | will return Toy Story 1 and Toy Story 2 184 | 185 | 186 | #### 2. Retrieving scheduled models in addition to the normal. 187 | 188 | To retrieve scheduled models in addition to the normal models, use the ```withScheduled()``` method. 189 | 190 | ```php 191 | $posts = App\Post::withScheduled()->get(); 192 | ``` 193 | 194 | The above query will return all the four rows in the above table. 195 | 196 | #### 3. Retrieving only scheduled models without normal. 197 | 198 | To retrieve only scheduled models use the ```onlyScheduled()``` method. 199 | 200 | ```php 201 | $posts = App\Post::onlyScheduled()->get(); 202 | ``` 203 | 204 | The above query will return Toy Story 3 and Terminator 2. 205 | 206 | #### 4. Do not apply any functionality provided by ```Schedulable```. 207 | 208 | In some cases you may not want to apply the ```Schedulable``` trait at all. In those cases use the ```withoutGlobalScope()``` method in your query. 209 | 210 | ```php 211 | use Neelkanth\Laravel\Schedulable\Scopes\SchedulableScope; 212 | 213 | $posts = App\Post::withoutGlobalScope(SchedulableScope::class)->get(); 214 | ``` 215 | 216 | ## A general use case example. 217 | 218 | ``` 219 | // routes/web.php 220 | use Illuminate\Support\Facades\Route; 221 | use Neelkanth\Laravel\Schedulable\Scopes\SchedulableScope; 222 | 223 | /* 224 | |-------------------------------------------------------------------------- 225 | | Web Routes 226 | |-------------------------------------------------------------------------- 227 | | 228 | | Here is where you can register web routes for your application. These 229 | | routes are loaded by the RouteServiceProvider within a group which 230 | | contains the "web" middleware group. Now create something great! 231 | | 232 | */ 233 | 234 | Route::get('/schedule/post', function () { 235 | $post = new App\Post(); 236 | $post->title = "My scheduled post"; 237 | $scheduleAt = Carbon\Carbon::now()->addDays(10); 238 | $post->schedule($scheduleAt); 239 | return $post; //The scheduled post's ID is 1 240 | }); 241 | 242 | 243 | Route::get('/unschedule/post', function () { 244 | 245 | // To unschedule a post you have to fetch the scheduled post first. 246 | // But becuase the Schedulable trait is used in App\Post model it will not a fetch a post whose schedule_at column value is in future. 247 | 248 | $post = App\Post::find(1); //This will return null for a scheduled post whose id is 1. 249 | 250 | //To retreive a scheduled post you can use any of the two methods given below. 251 | 252 | $post = App\Post::withScheduled()->find(1); //1. Using withScheduled() [Recommended] 253 | 254 | $post = App\Post::withoutGlobalScope(SchedulableScope::class)->find(1); //2. Using withoutGlobalScope() 255 | 256 | $post->unschedule(); 257 | }); 258 | ``` 259 | 260 | 261 | ## Contributing 262 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 263 | 264 | ## Security 265 | If you discover any security-related issues, please email me.neelkanth@gmail.com instead of using the issue tracker. 266 | 267 | ## Credits 268 | 269 | - [Neelkanth Kaushik](https://github.com/username) 270 | - [All Contributors](../../contributors) 271 | 272 | ## License 273 | [MIT](https://choosealicense.com/licenses/mit/) -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neelkanthk/laravel-schedulable", 3 | "description": "A Laravel package to add scheduling capability in Eloquent models.", 4 | "type": "laravel-package", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Neelkanth Kaushik", 9 | "email": "me.neelkanth@gmail.com" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "require": { 14 | "php": ">=7.2", 15 | "laravel/framework": ">=6.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Neelkanth\\Laravel\\Schedulable\\": "src" 20 | } 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^9.0", 24 | "orchestra/testbench": "^6.0|^7.0", 25 | "doctrine/dbal": "^4.0@dev" 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Neelkanth\\Laravel\\Schedulable\\Tests\\": "tests" 30 | } 31 | }, 32 | "extra": { 33 | "laravel": { 34 | "providers": [ 35 | "Neelkanth\\Laravel\\Schedulable\\Providers\\SchedulableServiceProvider" 36 | ] 37 | } 38 | }, 39 | "scripts": { 40 | "test": "vendor\\bin\\phpunit --testdox" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Providers/SchedulableServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 13 | Blueprint::macro('scheduleAt', function (string $column = 'schedule_at', int $precision = 0) { 14 | return $this->timestamp($column, $precision)->nullable(); 15 | }); 16 | 17 | Blueprint::macro('dropScheduleAt', function (string $column = 'schedule_at') { 18 | return $this->dropColumn($column); 19 | }); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Scopes/SchedulableScope.php: -------------------------------------------------------------------------------- 1 | getFullyQualifiedScheduleAtColumn(); 31 | return $builder->whereNull($scheduleAtColumn)->orWhere($scheduleAtColumn, '<=', $model->freshTimestamp()); 32 | } 33 | 34 | /** 35 | * Extend the query builder with the needed functions. 36 | * 37 | * @param \Illuminate\Database\Eloquent\Builder $builder 38 | * @return void 39 | */ 40 | 41 | public function extend(Builder $builder) 42 | { 43 | foreach ($this->extensions as $extension) { 44 | $this->{"{$extension}"}($builder); 45 | } 46 | } 47 | 48 | /** 49 | * Add the onlyScheduled extension to the builder. 50 | * Returns those model(s) whose scheduled date is in future. 51 | * 52 | * @param \Illuminate\Database\Eloquent\Builder $builder 53 | * @return void 54 | */ 55 | protected function onlyScheduled(Builder $builder) 56 | { 57 | $builder->macro('onlyScheduled', function (Builder $builder) { 58 | $scheduleAtColumn = $builder->getModel()->getFullyQualifiedScheduleAtColumn(); 59 | return $builder->withoutGlobalScope($this)->whereNotNull($scheduleAtColumn)->where($scheduleAtColumn, '>', $builder->getModel()->freshTimestamp()); 60 | }); 61 | } 62 | 63 | 64 | /** 65 | * Add the withScheduled extension to the builder. 66 | * Returns those model(s) along with other models whose scheduled date is in future. 67 | * 68 | * @param \Illuminate\Database\Eloquent\Builder $builder 69 | * @return void 70 | */ 71 | protected function withScheduled(Builder $builder) 72 | { 73 | $builder->macro('withScheduled', function (Builder $builder) { 74 | $scheduleAtColumn = $builder->getModel()->getFullyQualifiedScheduleAtColumn(); 75 | return $builder->withoutGlobalScope($this)->orWhereNull($scheduleAtColumn)->orWhere($scheduleAtColumn, '>', $builder->getModel()->freshTimestamp()); 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Traits/Schedulable.php: -------------------------------------------------------------------------------- 1 | dates[] = $this->getScheduleAtColumn(); 27 | $this->addObservableEvents([ 28 | 'scheduling', 29 | 'scheduled', 30 | 'unscheduling', 31 | 'unscheduled' 32 | ]); 33 | } 34 | 35 | /** 36 | * Set the scheduled_at column value 37 | * 38 | * @param $datetime 39 | */ 40 | public function setScheduleAtAttribute($datetime) 41 | { 42 | $this->attributes[$this->getScheduleAtColumn()] = !is_null($datetime) && Carbon::parse($datetime)->toDateTimeString() ? Carbon::parse($datetime)->toDateTimeString() : null; 43 | } 44 | 45 | /** 46 | * Get the name of the "schedule_at" column. 47 | * 48 | * @return string 49 | */ 50 | public function getScheduleAtColumn() 51 | { 52 | return defined('static::SCHEDULE_AT') ? static::SCHEDULE_AT : 'schedule_at'; 53 | } 54 | 55 | /** 56 | * Get the fully qualified "schedule_at" column. 57 | * 58 | * @return string 59 | */ 60 | public function getFullyQualifiedScheduleAtColumn() 61 | { 62 | return $this->qualifyColumn($this->getScheduleAtColumn()); 63 | } 64 | 65 | /** 66 | * Sets schedule_at column to NULL without saving it 67 | * 68 | * @return $this 69 | */ 70 | public function unscheduleWithoutSaving() 71 | { 72 | $this->setScheduleAtAttribute(null); 73 | return $this; 74 | } 75 | 76 | /** 77 | * Sets schedule_at column to NULL and save the model 78 | * 79 | * @return bool|null 80 | */ 81 | public function unschedule() 82 | { 83 | // If the unscheduling event does not return false, we will proceed with this 84 | // unscheduling operation. Otherwise, we bail out so the developer will stop 85 | // the unscheduling totally. We will clear the schedule_at timestamp and save. 86 | if ($this->fireModelEvent('unscheduling') === false) { 87 | return false; 88 | } 89 | 90 | // Once we have saved the model, we will fire the "unscheduled" event so this 91 | // developer will do anything they need to after a unschedule operation is 92 | // totally finished. Then we will return the result of the save call. 93 | 94 | $model = $this->unscheduleWithoutSaving(); 95 | $result = $model->save(); 96 | 97 | $this->fireModelEvent('unscheduled', false); 98 | return $result; 99 | } 100 | 101 | /** 102 | * Set the schedule_at column to the given datetime without saving the model 103 | * 104 | * @param DateTimeInterface $datetime 105 | * @return 106 | */ 107 | public function scheduleWithoutSaving(DateTimeInterface $datetime) 108 | { 109 | $this->setScheduleAtAttribute($datetime); 110 | return $this; 111 | } 112 | 113 | /** 114 | * Set the schedule_at column to the given datetime and save the model. 115 | * 116 | * @param DateTimeInterface $datetime 117 | * @return bool|null 118 | */ 119 | public function schedule(DateTimeInterface $datetime) 120 | { 121 | // If the scheduling event does not return false, we will proceed with this 122 | // scheduling operation. Otherwise, we bail out so the developer will stop 123 | // the scheduling totally. 124 | if ($this->fireModelEvent('scheduling') === false) { 125 | return false; 126 | } 127 | 128 | // Once we have saved the model, we will fire the "scheduled" event so that 129 | // developer will do anything they need to after a schedule operation is 130 | // totally finished. Then we will return the result of the save call. 131 | 132 | $model = $this->scheduleWithoutSaving($datetime); 133 | $result = $model->save(); 134 | 135 | $this->fireModelEvent('scheduled', false); 136 | return $result; 137 | } 138 | 139 | /** 140 | * Checks if the model is scheduled in future. 141 | * 142 | * @return bool 143 | */ 144 | public function isScheduledInFuture() 145 | { 146 | $isScheduled = false; 147 | $scheduleAt = $this->{$this->getScheduleAtColumn()}; 148 | if ($scheduleAt && $scheduleAt->isFuture()) { 149 | $isScheduled = true; 150 | } 151 | return $isScheduled; 152 | } 153 | 154 | /** 155 | * Checks if the model was scheduled in past. 156 | * 157 | * @return bool 158 | */ 159 | public function wasScheduledInPast() 160 | { 161 | $wasScheduled = false; 162 | $scheduleAt = $this->{$this->getScheduleAtColumn()}; 163 | if ($scheduleAt && $scheduleAt->isPast()) { 164 | $wasScheduled = true; 165 | } 166 | return $wasScheduled; 167 | } 168 | 169 | /** 170 | * Register a scheduling model event with the dispatcher. 171 | * 172 | * @param \Closure|string $callback 173 | * @return void 174 | */ 175 | public static function scheduling($callback) 176 | { 177 | static::registerModelEvent('scheduling', $callback); 178 | } 179 | 180 | /** 181 | * Register a scheduled model event with the dispatcher. 182 | * 183 | * @param \Closure|string $callback 184 | * @return void 185 | */ 186 | public static function scheduled($callback) 187 | { 188 | static::registerModelEvent('scheduled', $callback); 189 | } 190 | 191 | /** 192 | * Register a unscheduling model event with the dispatcher. 193 | * 194 | * @param \Closure|string $callback 195 | * @return void 196 | */ 197 | public static function unscheduling($callback) 198 | { 199 | static::registerModelEvent('unscheduling', $callback); 200 | } 201 | 202 | /** 203 | * Register a unscheduled model event with the dispatcher. 204 | * 205 | * @param \Closure|string $callback 206 | * @return void 207 | */ 208 | public static function unscheduled($callback) 209 | { 210 | static::registerModelEvent('unscheduled', $callback); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /tests/Database/Migration/2020_10_05_184903_create_posts_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('title')->nullable(); 19 | $table->timestamps(); 20 | $table->scheduleAt()->nullable(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('posts'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Database/SchedulableMigrationTest.php: -------------------------------------------------------------------------------- 1 | scheduleAt(); 22 | }); 23 | $post = new Post(); 24 | $this->assertTrue(Schema::hasColumn('post_tests', $post->getScheduleAtColumn())); 25 | } 26 | 27 | public function tearDown(): void 28 | { 29 | parent::tearDown(); 30 | } 31 | } -------------------------------------------------------------------------------- /tests/Models/PostTest.php: -------------------------------------------------------------------------------- 1 | setUpDatabase(); 21 | } 22 | 23 | /** 24 | * Clean up the testing environment before the next test. 25 | * 26 | * @return void 27 | */ 28 | protected function tearDown(): void 29 | { 30 | $this->dropTables('post_tests'); 31 | } 32 | 33 | protected function getPackageProviders($app) 34 | { 35 | return [ 36 | SchedulableServiceProvider::class, 37 | ]; 38 | } 39 | 40 | protected function getEnvironmentSetUp($app) 41 | { 42 | $app['config']->set('schedulable.database_connection', 'mysql'); 43 | $app['config']->set('database.default', 'mysql'); 44 | $mysql = [ 45 | 'driver' => 'mysql', 46 | 'url' => "", 47 | 'host' => "localhost", 48 | 'port' => "3306", 49 | 'database' => "laravel6_testing", 50 | 'username' => "root", 51 | 'password' => "", 52 | 'unix_socket' => "", 53 | 'charset' => 'utf8mb4', 54 | 'collation' => 'utf8mb4_unicode_ci' 55 | ]; 56 | $app['config']->set('database.connections.mysql', $mysql); 57 | } 58 | 59 | protected function setUpDatabase() 60 | { 61 | $this->createTables('post_tests'); 62 | $this->seedModels(Post::class); 63 | } 64 | 65 | protected function createTables(...$tableNames) 66 | { 67 | collect($tableNames)->each(function (string $tableName) { 68 | if (!Schema::hasTable($tableName)) { 69 | Schema::create($tableName, function (Blueprint $table) use ($tableName) { 70 | $table->increments('id'); 71 | $table->string('title')->nullable(); 72 | $table->timestamps(); 73 | }); 74 | } 75 | }); 76 | } 77 | 78 | protected function seedModels(...$modelClasses) 79 | { 80 | collect($modelClasses)->each(function (string $modelClass) { 81 | foreach (range(1, 0) as $index) { 82 | $newPost = new $modelClass; 83 | $newPost->title = "Post Title {$index}"; 84 | $newPost->save(); 85 | } 86 | }); 87 | } 88 | 89 | protected function dropTables(...$tableNames) 90 | { 91 | collect($tableNames)->each(function (string $tableName) { 92 | Schema::dropIfExists($tableName); 93 | }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/SchedulableScopeTest.php: -------------------------------------------------------------------------------- 1 | getScheduleAtColumn())) { 18 | Schema::table('post_tests', function (Blueprint $table) { 19 | $table->scheduleAt(); 20 | }); 21 | } 22 | } 23 | 24 | public function tearDown(): void 25 | { 26 | parent::tearDown(); 27 | } 28 | 29 | /** 30 | * @test 31 | * Test the default scope 32 | */ 33 | public function testDefaultScope() 34 | { 35 | $post = new Post(); 36 | $title = "Test Post - " . time(); 37 | $post->title = $title; 38 | if ($post->schedule(Carbon::now()->addDays(10))) { 39 | $this->assertFalse(Post::where("title", $title)->exists()); 40 | } else { 41 | $this->assertFalse(true); 42 | } 43 | } 44 | 45 | /** 46 | * @test 47 | * Test only scheduled scope 48 | */ 49 | public function testOnlyScheduledScope() 50 | { 51 | $post = new Post(); 52 | $title = "Test Post - " . time(); 53 | $post->title = $title; 54 | if ($post->schedule(Carbon::now()->addDays(10))) { 55 | $this->assertCount(1, Post::onlyScheduled()->get()); 56 | } else { 57 | $this->assertFalse(true); 58 | } 59 | } 60 | 61 | /** 62 | * @test 63 | * Test with scheduled scope 64 | */ 65 | public function testWithScheduledScope() 66 | { 67 | $post = new Post(); 68 | $title = "Test Post - " . time(); 69 | $post->title = $title; 70 | if ($post->schedule(Carbon::now()->addDays(10))) { 71 | // $titles = Post::withScheduled()->get()->pluck("title")->toArray(); 72 | // $this->assertContains(); 73 | $this->assertContains($title, Post::withScheduled()->get()->pluck("title")); 74 | } else { 75 | $this->assertFalse(true); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/SchedulableTraitTest.php: -------------------------------------------------------------------------------- 1 | getScheduleAtColumn())) { 19 | Schema::table('post_tests', function (Blueprint $table) { 20 | $table->scheduleAt(); 21 | }); 22 | } 23 | } 24 | 25 | public function tearDown(): void 26 | { 27 | parent::tearDown(); 28 | } 29 | 30 | /** 31 | * @test 32 | * 33 | * Test if the schedule_at supports NULL value 34 | */ 35 | public function testContainsScheduleAtColumnWithNullValue() 36 | { 37 | $post = new Post(); 38 | $post->{$post->getScheduleAtColumn()} = null; 39 | $this->assertTrue($post->save()); 40 | } 41 | 42 | /** 43 | * @test 44 | * 45 | * Test the type of schedule_at column 46 | */ 47 | public function testContainsScheduleAtColumnWithDateType() 48 | { 49 | $post = new Post(); 50 | $this->assertContains($post->getScheduleAtColumn(), $post->getDates()); 51 | } 52 | 53 | /** 54 | * @test 55 | * 56 | * Test if the package can detect the default 'schedule_at' column name 57 | */ 58 | public function testCanDetectDefaultScheduleAtColumn() 59 | { 60 | $post = new Post(); 61 | $this->assertEquals('schedule_at', $post->getScheduleAtColumn()); 62 | } 63 | 64 | /** 65 | * @test 66 | * 67 | * Test if name of 'schedule_at' can be changed 68 | */ 69 | public function testCanCustomizeScheduleAtColumn() 70 | { 71 | $post = new PostTestCustomColumn(); 72 | $this->assertEquals('schedule_for', $post->getScheduleAtColumn()); 73 | } 74 | 75 | /** 76 | * @test 77 | * 78 | * Test can resolve the fully qualified name of the 'schedule_at' column 79 | */ 80 | public function testCanDetectQualifiedScheduleAtColumn() 81 | { 82 | $post = new Post(); 83 | $this->assertEquals('post_tests.' . $post->getScheduleAtColumn(), $post->getFullyQualifiedScheduleAtColumn()); 84 | } 85 | 86 | /** 87 | * @test 88 | * 89 | * Test if the schedule_at column can be set without saving 90 | */ 91 | public function testCanScheduleWithoutSaving() 92 | { 93 | $scheduleAt = Carbon::now()->addDays(5); 94 | $post = new Post(); 95 | $scheduledPost = $post->scheduleWithoutSaving($scheduleAt); 96 | $isAttributeSet = $scheduledPost->{$scheduledPost->getScheduleAtColumn()} == $scheduleAt->toDateTimeString() ? true : false; 97 | $isSavedInDatabase = Post::where($scheduledPost->getScheduleAtColumn(), $scheduleAt)->exists(); 98 | $this->assertNotEquals($isAttributeSet, $isSavedInDatabase); 99 | } 100 | 101 | /** 102 | * @test 103 | * 104 | * Test if the schedule_at column can be saved 105 | */ 106 | public function testCanSchedule() 107 | { 108 | $scheduleAt = Carbon::now()->addDays(10); 109 | $post = new Post(); 110 | $isScheduled = $post->schedule($scheduleAt); 111 | $this->assertTrue($isScheduled); 112 | } 113 | 114 | /** 115 | * @test 116 | * 117 | * Test if the schedule_at column can be set to null without saving 118 | */ 119 | public function testCanUnScheduleWithoutSaving() 120 | { 121 | $scheduleAt = Carbon::now()->addDays(10); 122 | $post = new Post(); 123 | if ($post->schedule($scheduleAt)) { 124 | $unscheduledPost = $post->unscheduleWithoutSaving(); 125 | $this->assertNull($unscheduledPost->{$post->getScheduleAtColumn()}); 126 | } else { 127 | $this->assertFalse(true); 128 | } 129 | } 130 | 131 | /** 132 | * @test 133 | * 134 | * Test if the schedule_at column can be saved as null 135 | */ 136 | public function testCanUnSchedule() 137 | { 138 | $scheduleAt = Carbon::now()->addDays(10); 139 | $post = new Post(); 140 | if ($post->schedule($scheduleAt)) { 141 | $unscheduledPost = $post->unschedule(); 142 | $this->assertTrue($unscheduledPost); 143 | } else { 144 | $this->assertFalse(false); 145 | } 146 | } 147 | } 148 | --------------------------------------------------------------------------------