├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── phpstan-baseline.neon ├── phpstan.neon.dist ├── pint.json ├── rector.php └── src ├── Database ├── Eloquent │ ├── Publishes.php │ └── PublishingScope.php └── Schema │ └── Blueprint │ ├── dropPublishes.php │ ├── dropPublishesTz.php │ ├── publishes.php │ └── publishesTz.php └── PublishingServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `eloquent-publishing` will be documented in this file 4 | 5 | ## 3.2.0 - 2025-02-23 6 | 7 | ### What's Changed 8 | 9 | * Upgrade Laravel 12 by @leMaur in https://github.com/leMaur/eloquent-publishing/pull/23 10 | 11 | **Full Changelog**: https://github.com/leMaur/eloquent-publishing/compare/3.1.0...3.2.0 12 | 13 | ## Upgrade Laravel 11 - 2025-02-23 14 | 15 | ### What's Changed 16 | 17 | * chore(deps): bump aglipanci/laravel-pint-action from 2.1.0 to 2.2.0 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/4 18 | * chore(deps): bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/5 19 | * chore(deps): bump aglipanci/laravel-pint-action from 2.2.0 to 2.3.0 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/6 20 | * chore(deps): bump dependabot/fetch-metadata from 1.4.0 to 1.5.1 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/7 21 | * chore(deps): bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/8 22 | * chore(deps): bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/10 23 | * chore(deps): bump actions/checkout from 3 to 4 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/9 24 | * chore(deps): bump aglipanci/laravel-pint-action from 2.3.0 to 2.3.1 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/11 25 | * chore(deps): bump aglipanci/laravel-pint-action from 2.3.1 to 2.4 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/15 26 | * chore(deps): bump dependabot/fetch-metadata from 1.6.0 to 2.2.0 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/17 27 | * chore(deps): bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/13 28 | * chore(deps): bump actions/cache from 3 to 4 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/12 29 | * chore(deps): bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/18 30 | * chore(deps): bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/19 31 | * Upgrade Laravel 11 by @leMaur in https://github.com/leMaur/eloquent-publishing/pull/20 32 | 33 | **Full Changelog**: https://github.com/leMaur/eloquent-publishing/compare/3.0.1...3.1.0 34 | 35 | ## 3.0.1 - 2023-03-20 36 | 37 | ### What's Changed 38 | 39 | - chore(deps): bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/leMaur/eloquent-publishing/pull/3 40 | 41 | ### New Contributors 42 | 43 | - @dependabot made their first contribution in https://github.com/leMaur/eloquent-publishing/pull/3 44 | 45 | **Full Changelog**: https://github.com/leMaur/eloquent-publishing/compare/3.0.0...3.0.1 46 | 47 | ## 1.1.1 - 2022-01-23 48 | 49 | - update for Laravel 9 50 | 51 | ## 1.1.0 - 2021-03-21 52 | 53 | - added Treeware on README.md 54 | 55 | ## 1.0.0 - 2021-01-23 56 | 57 | - initial release 58 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) LeMaur 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easily make your eloquent model publishable 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/lemaur/eloquent-publishing.svg?style=flat-square)](https://packagist.org/packages/lemaur/eloquent-publishing) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/lemaur/eloquent-publishing.svg?style=flat-square)](https://packagist.org/packages/lemaur/eloquent-publishing) 5 | [![License](https://img.shields.io/packagist/l/lemaur/eloquent-publishing.svg?style=flat-square&color=yellow)](https://github.com/leMaur/eloquent-publishing/blob/main/LICENSE.md) 6 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/lemaur/eloquent-publishing/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/leMaur/eloquent-publishing/actions/workflows/run-tests.yml) 7 | [![GitHub Sponsors](https://img.shields.io/github/sponsors/lemaur?style=flat-square&color=ea4aaa)](https://github.com/sponsors/leMaur) 8 | 9 | This package provides a trait that will help you publishing eloquent models. 10 | ```php 11 | use Lemaur\Publishing\Database\Eloquent\Publishes; 12 | 13 | class Post extends Model 14 | { 15 | use Publishes; 16 | } 17 | ``` 18 | It also includes custom schema builder blueprint methods to help you setting up your migrations with ease. 19 | 20 | ## Support Me 21 | 22 | Hey folks, 23 | 24 | Do you like this package? Do you find it useful and it fits well in your project? 25 | 26 | I am glad to help you, and I would be so grateful if you considered supporting my work. 27 | 28 | You can even choose 😃: 29 | * You can [sponsor me 😎](https://github.com/sponsors/leMaur) with a monthly subscription. 30 | * You can [buy me a coffee ☕ or a pizza 🍕](https://github.com/sponsors/leMaur?frequency=one-time&sponsor=leMaur) just for this package. 31 | * You can [plant trees 🌴](https://ecologi.com/lemaur?r=6012e849de97da001ddfd6c9). By using this link we will both receive 30 trees for free and the planet (and me) will thank you. 32 | * You can "Star ⭐" this repository (it's free 😉). 33 | 34 | ## Installation 35 | 36 | You can install the package via composer: 37 | 38 | ```bash 39 | composer require lemaur/eloquent-publishing 40 | ``` 41 | 42 | ## Usage 43 | Your eloquent models should use the `Lemaur\Publishing\Database\Eloquent\Publishes` trait. 44 | 45 | Your migration files should have a field to save the publishing date. 46 | 47 | Here's a real-life example of how to implement the trait on a Post model. 48 | 49 | [_(jump to all the available methods)_](#available-methods) 50 | ```php 51 | /** app\Models\Post.php */ 52 | 53 | namespace App\Models; 54 | 55 | use Illuminate\Database\Eloquent\Model; 56 | use Lemaur\Publishing\Database\Eloquent\Publishes; 57 | 58 | class Post extends Model 59 | { 60 | use Publishes; 61 | } 62 | ``` 63 | 64 | ```php 65 | /** database\migrations\create_posts_table.php */ 66 | 67 | use Illuminate\Database\Migrations\Migration; 68 | use Illuminate\Database\Schema\Blueprint; 69 | use Illuminate\Support\Facades\Schema; 70 | 71 | class CreatePostsTable extends Migration 72 | { 73 | public function up() 74 | { 75 | Schema::create('posts', function (Blueprint $table) { 76 | $table->id(); 77 | $table->string('title'); 78 | $table->string('slug'); 79 | $table->longText('body')->nullable(); 80 | $table->timestamps(); 81 | 82 | $table->publishes(); // equivalent to `$table->timestamp('published_at')->nullable();` 83 | }); 84 | } 85 | 86 | ... 87 | } 88 | ``` 89 | 90 | ### Available methods 91 | 92 | #### Using in your migration files. 93 | ```php 94 | /** add a nullable timestamp column named "published_at" */ 95 | $table->publishes(); 96 | 97 | /** it may accepts a custom column name and an optional precision (total digits) */ 98 | $table->publishes('published_at', $precision = 0); 99 | 100 | /** add a nullable timestampTz column named "published_at" */ 101 | $table->publishesTz(); 102 | 103 | /** it may accepts a custom column name and an optional precision (total digits) */ 104 | $table->publishesTz('published_at', $precision = 0); 105 | 106 | /** drop the column named "published_at" */ 107 | $table->dropPublishes(); 108 | 109 | /** it may accepts a custom column name */ 110 | $table->dropPublishes('published_at'); 111 | 112 | /** drop the column named "published_at" */ 113 | $table->dropPublishesTz(); 114 | 115 | /** it may accepts a custom column name */ 116 | $table->dropPublishesTz('published_at'); 117 | ``` 118 | > For more information about timestamps, refer to the [Laravel Documentation](https://laravel.com/docs/8.x/migrations#column-method-timestamp) 119 | 120 | [_(jump to the customize section)_](#customize) 121 | 122 | #### Using in your controllers, actions or whatever you need 123 | 124 | ```php 125 | // Publish your model (this set the publish date at the current date time) 126 | $post->publish(); 127 | 128 | // Publish your model with custom date time (can be in the future or in the past, as you wish. It accepts a class that implement \DatetimeInterface) 129 | $post->publish(Carbon::parse('tomorrow')); 130 | 131 | // Unpublish your model 132 | $post->unpublish(); 133 | 134 | // Check if the model is published (current date time or in the past) 135 | $bool = $post->isPublished(); 136 | 137 | // Check if the model is not published 138 | $bool = $post->isNotPublished(); 139 | 140 | // Check if the model is published with a date time in the future 141 | $bool = $post->isPlanned(); 142 | 143 | // Check if the model is not planned 144 | $bool = $post->isNotPlanned(); 145 | 146 | // Show only published posts 147 | $onlyPublishedPosts = Post::onlyPublished()->get(); 148 | 149 | // Show only planned posts 150 | $onlyPlannedPosts = Post::onlyPlanned()->get(); 151 | 152 | // Show only planned and published posts 153 | $onlyPlannedAndPublishedPosts = Post::onlyPlannedAndPublished()->get(); 154 | 155 | // Show only posts not planned nor published 156 | $withoutPlannedAndPublishedPosts = Post::withoutPlannedAndPublished()->get(); 157 | 158 | // Order by latest published posts 159 | $latestPublishedPosts = Post::latestPublished()->get(); 160 | 161 | // Order by oldest published posts 162 | $oldestPublishedPosts = Post::oldestPublished()->get(); 163 | 164 | // Order by latest planned posts 165 | $latestPlannedPosts = Post::latestPlanned()->get(); 166 | 167 | // Order by oldest planned posts 168 | $oldestPlannedPosts = Post::oldestPlanned()->get(); 169 | 170 | // or you can combine them together... 171 | 172 | // Get only published posts ordered by latest published 173 | $posts = Post::onlyPublished()->latestPublished()->get(); 174 | 175 | // Get only planned posts ordered by latest planned 176 | $posts = Post::onlyPlanned()->latestPlanned()->get(); 177 | 178 | ``` 179 | 180 | ## Customize 181 | 182 | If you want to change the column name, you need to specify it in your model and in your migration file. Let me show you: 183 | ```php 184 | // in your model 185 | 186 | class Post extends Model 187 | { 188 | use Publishes; 189 | 190 | /** 191 | * The custom name of the "published at" column. 192 | * 193 | * @var string 194 | */ 195 | const PUBLISHED_AT = 'publish_date'; 196 | } 197 | 198 | // in your migration file 199 | 200 | class CreatePostsTable extends Migration 201 | { 202 | public function up() 203 | { 204 | Schema::create('posts', function (Blueprint $table) { 205 | $table->id(); 206 | ... 207 | $table->publishes('publish_date'); 208 | }); 209 | } 210 | 211 | ... 212 | } 213 | ``` 214 | 215 | ## Events 216 | 217 | When you publish or unpublish a model, the package dispatches several events: `publishing`, `published`, `unpublishing`, `unpublished`. 218 | 219 | The `publishing` / `published` events will dispatch when a model is published. 220 | The `unpublishing` / `unpublished` events will dispatch when a model is unpublished. 221 | 222 | > For more information about the events, refer to the [Laravel Documentation](https://laravel.com/docs/8.x/eloquent#events) 223 | 224 | ## Testing 225 | 226 | ```bash 227 | composer test 228 | ``` 229 | 230 | ## Changelog 231 | 232 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 233 | 234 | ## Contributing 235 | 236 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 237 | 238 | ## Security Vulnerability 239 | 240 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 241 | 242 | ## Credits 243 | 244 | - [Maurizio](https://github.com/lemaur) 245 | - [All Contributors](../../contributors) 246 | 247 | ## License 248 | 249 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 250 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lemaur/eloquent-publishing", 3 | "description": "", 4 | "keywords": [ 5 | "laravel", 6 | "eloquent", 7 | "model", 8 | "publishing", 9 | "eloquent-publishing", 10 | "lemaur" 11 | ], 12 | "homepage": "https://github.com/lemaur/eloquent-publishing", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Maurizio", 17 | "email": "hello@lemaur.me" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.1", 22 | "illuminate/support": "^10.0|^11.0|^12.0" 23 | }, 24 | "require-dev": { 25 | "driftingly/rector-laravel": "^1.2", 26 | "laravel/pint": "^1.0", 27 | "orchestra/testbench": "^8.0|^9.0", 28 | "phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0", 29 | "phpstan/phpstan-phpunit": "^1.3.3|^2.0", 30 | "phpunit/phpunit": "^10.5.0|^11.0|^12.0", 31 | "rector/rector": "^1.2", 32 | "roave/security-advisories": "dev-latest" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Lemaur\\Publishing\\": "src" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Tests\\": "tests" 42 | } 43 | }, 44 | "scripts": { 45 | "analyse": "vendor/bin/phpstan analyse", 46 | "test": "vendor/bin/phpunit", 47 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage", 48 | "format": "vendor/bin/pint", 49 | "refactor": "vendor/bin/rector process --config=rector.php" 50 | }, 51 | "extra": { 52 | "laravel": { 53 | "providers": [ 54 | "Lemaur\\Publishing\\PublishingServiceProvider" 55 | ] 56 | } 57 | }, 58 | "config": { 59 | "sort-packages": true 60 | }, 61 | "minimum-stability": "dev", 62 | "prefer-stable": true 63 | } 64 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\:\\:latestPublished\\(\\)\\.$#" 5 | count: 1 6 | path: src/Database/Eloquent/PublishingScope.php 7 | 8 | - 9 | message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Builder\\:\\:oldestPublished\\(\\)\\.$#" 10 | count: 1 11 | path: src/Database/Eloquent/PublishingScope.php 12 | 13 | - 14 | message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getPublishedAtColumn\\(\\)\\.$#" 15 | count: 1 16 | path: src/Database/Eloquent/PublishingScope.php 17 | 18 | - 19 | message: "#^Call to an undefined method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:getQualifiedPublishedAtColumn\\(\\)\\.$#" 20 | count: 9 21 | path: src/Database/Eloquent/PublishingScope.php 22 | 23 | - 24 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:addLatestPlanned\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 25 | count: 1 26 | path: src/Database/Eloquent/PublishingScope.php 27 | 28 | - 29 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:addLatestPublished\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 30 | count: 1 31 | path: src/Database/Eloquent/PublishingScope.php 32 | 33 | - 34 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:addOldestPlanned\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 35 | count: 1 36 | path: src/Database/Eloquent/PublishingScope.php 37 | 38 | - 39 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:addOldestPublished\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 40 | count: 1 41 | path: src/Database/Eloquent/PublishingScope.php 42 | 43 | - 44 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:addOnlyPlanned\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 45 | count: 1 46 | path: src/Database/Eloquent/PublishingScope.php 47 | 48 | - 49 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:addOnlyPlannedAndPublished\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 50 | count: 1 51 | path: src/Database/Eloquent/PublishingScope.php 52 | 53 | - 54 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:addOnlyPublished\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 55 | count: 1 56 | path: src/Database/Eloquent/PublishingScope.php 57 | 58 | - 59 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:addWithoutPlannedAndPublished\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 60 | count: 1 61 | path: src/Database/Eloquent/PublishingScope.php 62 | 63 | - 64 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:apply\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 65 | count: 1 66 | path: src/Database/Eloquent/PublishingScope.php 67 | 68 | - 69 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:extend\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 70 | count: 1 71 | path: src/Database/Eloquent/PublishingScope.php 72 | 73 | - 74 | message: "#^Method Lemaur\\\\Publishing\\\\Database\\\\Eloquent\\\\PublishingScope\\:\\:getPublishedAtColumn\\(\\) has parameter \\$builder with generic class Illuminate\\\\Database\\\\Eloquent\\\\Builder but does not specify its types\\: TModel$#" 75 | count: 1 76 | path: src/Database/Eloquent/PublishingScope.php 77 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | - vendor/phpstan/phpstan-deprecation-rules/rules.neon 4 | 5 | parameters: 6 | level: 8 7 | paths: 8 | - src 9 | excludePaths: 10 | - src/Database/Schema/Blueprint 11 | 12 | tmpDir: build/phpstan 13 | checkMissingIterableValueType: true 14 | 15 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "backtick_to_shell_exec": true, 5 | "declare_strict_types": true, 6 | "final_class": false, 7 | "fully_qualified_strict_types": true, 8 | "function_typehint_space": true, 9 | "global_namespace_import": { 10 | "import_classes": true, 11 | "import_constants": true, 12 | "import_functions": true 13 | }, 14 | "ordered_class_elements": { 15 | "order": [ 16 | "use_trait", 17 | "case", 18 | "constant", 19 | "constant_public", 20 | "constant_protected", 21 | "constant_private", 22 | "property_public", 23 | "property_protected", 24 | "property_private", 25 | "construct", 26 | "destruct", 27 | "magic", 28 | "phpunit", 29 | "method_abstract", 30 | "method_public_static", 31 | "method_public", 32 | "method_protected_static", 33 | "method_protected", 34 | "method_private_static", 35 | "method_private" 36 | ], 37 | "sort_algorithm": "none" 38 | }, 39 | "ordered_interfaces": true, 40 | "ordered_traits": true, 41 | "protected_to_private": true, 42 | "strict_comparison": true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([__DIR__.'/src']) 7 | ->withParallel() 8 | ->withPhpSets() 9 | ->withImportNames( 10 | removeUnusedImports: \true, 11 | ) 12 | ->withPreparedSets( 13 | deadCode: \true, 14 | codeQuality: \true, 15 | codingStyle: \true, 16 | typeDeclarations: \true, 17 | naming: \true, 18 | instanceOf: \true, 19 | earlyReturn: \true, 20 | strictBooleans: \true, 21 | carbon: \true, 22 | rectorPreset: \true, 23 | ) 24 | ->withPHPStanConfigs([__DIR__.'/phpstan.neon.dist']) 25 | ->withSets([ 26 | RectorLaravel\Set\LaravelSetList::LARAVEL_110, 27 | RectorLaravel\Set\LaravelSetList::LARAVEL_CODE_QUALITY, 28 | ]); 29 | -------------------------------------------------------------------------------- /src/Database/Eloquent/Publishes.php: -------------------------------------------------------------------------------- 1 | casts[$this->getPublishedAtColumn()])) { 78 | $this->casts[$this->getPublishedAtColumn()] = 'datetime'; 79 | } 80 | } 81 | 82 | /** 83 | * Publish the model instance. 84 | */ 85 | public function publish(?DateTimeInterface $datetime = null): ?bool 86 | { 87 | // If the publishing event does not return false, we will proceed with this 88 | // publish operation. Otherwise, we bail out so the developer will stop 89 | // the publish totally. We will clear the published timestamp and save. 90 | if ($this->fireModelEvent('publishing') === false) { 91 | return false; 92 | } 93 | 94 | $this->{$this->getPublishedAtColumn()} = $datetime ?? $this->freshTimestamp(); 95 | 96 | // Once we have saved the model, we will fire the "published" event so this 97 | // developer will do anything they need to after a publish operation is 98 | // totally finished. Then we will return the result of the save call. 99 | $this->exists = true; 100 | 101 | $result = $this->save(); 102 | 103 | $this->fireModelEvent('published', false); 104 | 105 | return $result; 106 | } 107 | 108 | /** 109 | * Unpublish the model instance. 110 | */ 111 | public function unpublish(): ?bool 112 | { 113 | // If the unpublishing event does not return false, we will proceed with this 114 | // unpublish operation. Otherwise, we bail out so the developer will stop 115 | // the unpublish totally. 116 | if ($this->fireModelEvent('unpublishing') === false) { 117 | return false; 118 | } 119 | 120 | $this->{$this->getPublishedAtColumn()} = null; 121 | 122 | // Once we have saved the model, we will fire the "unpublished" event so this 123 | // developer will do anything they need to after an unpublish operation is 124 | // totally finished. Then we will return the result of the save call. 125 | $this->exists = true; 126 | 127 | $result = $this->save(); 128 | 129 | $this->fireModelEvent('unpublished', false); 130 | 131 | return $result; 132 | } 133 | 134 | /** 135 | * Determine if the model instance is published. 136 | */ 137 | public function isPublished(): bool 138 | { 139 | return $this->{$this->getPublishedAtColumn()} !== null 140 | && $this->{$this->getPublishedAtColumn()} <= Date::now(); 141 | } 142 | 143 | /** 144 | * Determine if the model instance is not published. 145 | */ 146 | public function isNotPublished(): bool 147 | { 148 | return ! $this->isPublished(); 149 | } 150 | 151 | /** 152 | * Determine if the model instance is planned. 153 | */ 154 | public function isPlanned(): bool 155 | { 156 | return $this->{$this->getPublishedAtColumn()} !== null 157 | && $this->{$this->getPublishedAtColumn()} > Date::now(); 158 | } 159 | 160 | /** 161 | * Determine if the model instance is not planned. 162 | */ 163 | public function isNotPlanned(): bool 164 | { 165 | return $this->{$this->getPublishedAtColumn()} !== null 166 | && $this->{$this->getPublishedAtColumn()} <= Date::now(); 167 | } 168 | 169 | /** 170 | * Get the name of the "published at" column. 171 | */ 172 | public function getPublishedAtColumn(): string 173 | { 174 | return defined('static::PUBLISHED_AT') ? static::PUBLISHED_AT : 'published_at'; 175 | } 176 | 177 | /** 178 | * Get the fully qualified "published at" column. 179 | */ 180 | public function getQualifiedPublishedAtColumn(): string 181 | { 182 | return $this->qualifyColumn($this->getPublishedAtColumn()); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Database/Eloquent/PublishingScope.php: -------------------------------------------------------------------------------- 1 | extensions as $extension) { 44 | $this->{'add'.$extension}($builder); 45 | } 46 | } 47 | 48 | /** 49 | * Get the "published at" column for the builder. 50 | */ 51 | protected function getPublishedAtColumn(Builder $builder): string 52 | { 53 | if ((array) $builder->getQuery()->joins !== []) { 54 | return $builder->getModel()->getQualifiedPublishedAtColumn(); 55 | } 56 | 57 | return $builder->getModel()->getPublishedAtColumn(); 58 | } 59 | 60 | /** 61 | * Add the only published extension to the builder. 62 | */ 63 | protected function addOnlyPublished(Builder $builder): void 64 | { 65 | $builder->macro('onlyPublished', function (Builder $builder): Builder { 66 | $model = $builder->getModel(); 67 | 68 | $builder->whereNotNull( 69 | $model->getQualifiedPublishedAtColumn() 70 | )->where( 71 | $model->getQualifiedPublishedAtColumn(), 72 | '<=', 73 | Date::now() 74 | ); 75 | 76 | return $builder; 77 | }); 78 | } 79 | 80 | /** 81 | * Add the only planned extension to the builder. 82 | */ 83 | protected function addOnlyPlanned(Builder $builder): void 84 | { 85 | $builder->macro('onlyPlanned', function (Builder $builder): Builder { 86 | $model = $builder->getModel(); 87 | 88 | $builder->whereNotNull( 89 | $model->getQualifiedPublishedAtColumn() 90 | )->where( 91 | $model->getQualifiedPublishedAtColumn(), 92 | '>', 93 | Date::now() 94 | ); 95 | 96 | return $builder; 97 | }); 98 | } 99 | 100 | /** 101 | * Add the only planned and published extension to the builder. 102 | */ 103 | protected function addOnlyPlannedAndPublished(Builder $builder): void 104 | { 105 | $builder->macro('onlyPlannedAndPublished', function (Builder $builder): Builder { 106 | $model = $builder->getModel(); 107 | 108 | $builder->whereNotNull($model->getQualifiedPublishedAtColumn()); 109 | 110 | return $builder; 111 | }); 112 | } 113 | 114 | /** 115 | * Add the without planned and published extension to the builder. 116 | */ 117 | protected function addWithoutPlannedAndPublished(Builder $builder): void 118 | { 119 | $builder->macro('withoutPlannedAndPublished', function (Builder $builder): Builder { 120 | $model = $builder->getModel(); 121 | 122 | $builder->whereNull($model->getQualifiedPublishedAtColumn()); 123 | 124 | return $builder; 125 | }); 126 | } 127 | 128 | /** 129 | * Add the latest published extension to the builder. 130 | */ 131 | protected function addLatestPublished(Builder $builder): void 132 | { 133 | $builder->macro('latestPublished', function (Builder $builder): Builder { 134 | $model = $builder->getModel(); 135 | 136 | $builder->orderBy($model->getQualifiedPublishedAtColumn(), 'desc'); 137 | 138 | return $builder; 139 | }); 140 | } 141 | 142 | /** 143 | * Add the oldest published extension to the builder. 144 | */ 145 | protected function addOldestPublished(Builder $builder): void 146 | { 147 | $builder->macro('oldestPublished', function (Builder $builder): Builder { 148 | $model = $builder->getModel(); 149 | 150 | $builder->orderBy($model->getQualifiedPublishedAtColumn(), 'asc'); 151 | 152 | return $builder; 153 | }); 154 | } 155 | 156 | /** 157 | * Add the latest planned extension to the builder. 158 | */ 159 | protected function addLatestPlanned(Builder $builder): void 160 | { 161 | $builder->macro('latestPlanned', function (Builder $builder): Builder { 162 | $builder->latestPublished(); 163 | 164 | return $builder; 165 | }); 166 | } 167 | 168 | /** 169 | * Add the oldest planned extension to the builder. 170 | */ 171 | protected function addOldestPlanned(Builder $builder): void 172 | { 173 | $builder->macro('oldestPlanned', function (Builder $builder): Builder { 174 | $builder->oldestPublished(); 175 | 176 | return $builder; 177 | }); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Database/Schema/Blueprint/dropPublishes.php: -------------------------------------------------------------------------------- 1 | $this->dropColumn($column)); 13 | -------------------------------------------------------------------------------- /src/Database/Schema/Blueprint/dropPublishesTz.php: -------------------------------------------------------------------------------- 1 | $this->dropPublishes($column)); 13 | -------------------------------------------------------------------------------- /src/Database/Schema/Blueprint/publishes.php: -------------------------------------------------------------------------------- 1 | $this->timestamp($column, $precision)->nullable()); 13 | -------------------------------------------------------------------------------- /src/Database/Schema/Blueprint/publishesTz.php: -------------------------------------------------------------------------------- 1 | $this->timestampTz($column, $precision)->nullable()); 13 | -------------------------------------------------------------------------------- /src/PublishingServiceProvider.php: -------------------------------------------------------------------------------- 1 | each(function ($path): void { 15 | require $path; 16 | }); 17 | } 18 | } 19 | --------------------------------------------------------------------------------