├── .gitattributes ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .php-cs-fixer.php ├── .styleci.yml ├── LICENSE.md ├── README.md ├── composer.json ├── doc ├── 1-one-to-one.md ├── 2-one-to-many.md ├── 3-many-to-many.md ├── 4-has-many-through.md ├── 5-one-to-one-polymorphic.md ├── 6-one-to-many-polymorphic.md └── 7-many-to-many-polymorphic.md ├── phpunit.xml.dist ├── src ├── BelongsTo.php ├── BelongsToMany.php ├── Concerns │ ├── HasBelongsToEvents.php │ ├── HasBelongsToManyEvents.php │ ├── HasManyEvents.php │ ├── HasMorphManyEvents.php │ ├── HasMorphOneEvents.php │ ├── HasMorphToEvents.php │ ├── HasMorphToManyEvents.php │ ├── HasMorphedByManyEvents.php │ └── HasOneEvents.php ├── Contracts │ └── EventDispatcher.php ├── HasMany.php ├── HasOne.php ├── Helpers │ └── AttributesMethods.php ├── MorphMany.php ├── MorphOne.php ├── MorphTo.php ├── MorphToMany.php ├── MorphedByMany.php ├── RelationshipEventsServiceProvider.php └── Traits │ ├── HasDispatchableEvents.php │ ├── HasEventDispatcher.php │ ├── HasOneOrManyMethods.php │ └── HasRelationshipObservables.php └── tests ├── Feature ├── HasBelongsToEventsTest.php ├── HasBelongsToManyEventsTest.php ├── HasManyEventsTest.php ├── HasMorphManyEventsTest.php ├── HasMorphOneEventsTest.php ├── HasMorphToEventsTest.php ├── HasMorphToManyEventsTest.php ├── HasMorphedByManyEventsTest.php └── HasOneEventsTest.php ├── Stubs ├── Address.php ├── Comment.php ├── Post.php ├── Profile.php ├── Role.php ├── Tag.php └── User.php └── TestCase.php /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | tests: 9 | 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: true 13 | matrix: 14 | php: [8.2, 8.3] 15 | stability: [prefer-lowest, prefer-stable] 16 | 17 | name: P${{ matrix.php }} - S${{ matrix.stability }} 18 | 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v2 22 | 23 | - name: Cache dependencies 24 | uses: actions/cache@v4 25 | with: 26 | path: ~/.composer/cache/files 27 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | extensions: pdo, sqlite, pdo_sqlite 34 | coverage: none 35 | 36 | - name: Install dependencies 37 | run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress --no-suggest 38 | 39 | - name: Execute tests 40 | run: vendor/bin/phpunit 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /storage/*.key 5 | /vendor 6 | /.idea 7 | /.vscode 8 | /.vagrant 9 | Homestead.json 10 | Homestead.yaml 11 | npm-debug.log 12 | yarn-error.log 13 | .env 14 | .DS_Store 15 | /composer.lock 16 | /.phpunit.cache 17 | /.phpunit.result.cache 18 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | setUsingCache(false); 5 | 6 | $finder = PhpCsFixer\Finder::create() 7 | ->exclude([ 8 | 'admin/components/gii/crud/default', 9 | 'admin/module/rbac/views', 10 | 'admin/views', 11 | 'api/docs', 12 | 'common/components/cards/protobuf', 13 | 'common/components/iban/protobuf', 14 | 'common/components/walletApi/protobuf', 15 | 'console/views', 16 | 'partners/views', 17 | 'partners/views_old', 18 | 'tests', 19 | ]) 20 | ->in(__DIR__); 21 | 22 | return $config->setRules([ 23 | '@PSR12' => true, 24 | '@PHP82Migration' => true, 25 | 'array_syntax' => ['syntax' => 'short'], 26 | 'binary_operator_spaces' => true, 27 | 'blank_line_before_statement' => [ 28 | 'statements' => [ 29 | 'break', 30 | 'continue', 31 | 'declare', 32 | 'exit', 33 | 'return', 34 | 'throw', 35 | 'try', 36 | 'yield', 37 | 'yield_from', 38 | ], 39 | ], 40 | 'braces' => ['allow_single_line_anonymous_class_with_empty_body' => true], 41 | 'cast_spaces' => true, 42 | 'class_attributes_separation' => [ 43 | 'elements' => [ 44 | 'method' => 'one', 45 | ], 46 | ], 47 | 'class_definition' => ['multi_line_extends_each_single_line' => true], 48 | 'class_reference_name_casing' => true, 49 | 'clean_namespace' => true, 50 | 'concat_space' => ['spacing' => 'one'], 51 | 'echo_tag_syntax' => ['format' => 'short'], 52 | 'empty_loop_body' => ['style' => 'braces'], 53 | 'empty_loop_condition' => true, 54 | 'fully_qualified_strict_types' => true, 55 | 'function_typehint_space' => true, 56 | 'global_namespace_import' => [ 57 | 'import_classes' => true, 58 | 'import_constants' => true, 59 | 'import_functions' => false, 60 | ], 61 | 'include' => true, 62 | 'integer_literal_case' => true, 63 | 'lambda_not_used_import' => true, 64 | 'linebreak_after_opening_tag' => true, 65 | 'magic_constant_casing' => true, 66 | 'magic_method_casing' => true, 67 | 'method_argument_space' => [ 68 | 'on_multiline' => 'ensure_fully_multiline', 69 | 'keep_multiple_spaces_after_comma' => false, 70 | 'after_heredoc' => true, 71 | ], 72 | 'native_function_casing' => true, 73 | 'native_function_type_declaration_casing' => true, 74 | 'no_alias_language_construct_call' => true, 75 | 'no_alternative_syntax' => ['fix_non_monolithic_code' => true], 76 | 'no_binary_string' => true, 77 | 'no_blank_lines_after_phpdoc' => true, 78 | 'no_empty_comment' => true, 79 | 'no_empty_phpdoc' => true, 80 | 'no_empty_statement' => true, 81 | 'no_extra_blank_lines' => [ 82 | 'tokens' => [ 83 | 'attribute', 84 | 'break', 85 | 'case', 86 | 'continue', 87 | 'curly_brace_block', 88 | 'default', 89 | 'extra', 90 | 'parenthesis_brace_block', 91 | 'return', 92 | 'square_brace_block', 93 | 'switch', 94 | 'throw', 95 | 'use', 96 | ], 97 | ], 98 | 'no_leading_namespace_whitespace' => true, 99 | 'no_mixed_echo_print' => true, 100 | 'no_multiline_whitespace_around_double_arrow' => true, 101 | 'no_short_bool_cast' => true, 102 | 'no_singleline_whitespace_before_semicolons' => true, 103 | 'no_spaces_around_offset' => true, 104 | 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true], // Убирать лишние теги из phpdoc 105 | 'no_trailing_comma_in_singleline' => [ 106 | 'elements' => [ 107 | 'arguments', 108 | 'array_destructuring', 109 | 'array', 110 | 'group_import', 111 | ], 112 | ], 113 | 'no_unneeded_control_parentheses' => [ 114 | 'statements' => [ 115 | 'break', 116 | 'clone', 117 | 'continue', 118 | 'echo_print', 119 | 'negative_instanceof', 120 | 'others', 121 | 'return', 122 | 'switch_case', 123 | 'yield', 124 | 'yield_from', 125 | ], 126 | ], 127 | 'no_unneeded_curly_braces' => ['namespaces' => true], 128 | 'no_unneeded_import_alias' => true, 129 | 'no_unset_cast' => true, 130 | 'no_unused_imports' => true, 131 | 'no_useless_concat_operator' => true, 132 | 'no_useless_nullsafe_operator' => true, 133 | 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], 134 | 'normalize_index_brace' => true, 135 | 'object_operator_without_whitespace' => true, 136 | 'ordered_imports' => [ 137 | 'imports_order' => [ 138 | 'class', 139 | 'function', 140 | 'const', 141 | ], 142 | 'sort_algorithm' => 'alpha', 143 | ], 144 | 'php_unit_fqcn_annotation' => true, 145 | 'php_unit_method_casing' => true, 146 | 'phpdoc_align' => ['align' => 'left'], 147 | 'phpdoc_annotation_without_dot' => true, 148 | 'phpdoc_indent' => true, 149 | 'phpdoc_no_access' => true, 150 | 'phpdoc_no_alias_tag' => [ 151 | 'replacements' => [ 152 | 'type' => 'var', 153 | 'link' => 'see', 154 | ], 155 | ], 156 | 'phpdoc_no_package' => true, 157 | 'phpdoc_no_useless_inheritdoc' => true, 158 | 'phpdoc_order' => [ 159 | 'order' => [ 160 | 'param', 161 | 'return', 162 | 'throws', 163 | ], 164 | ], 165 | 'phpdoc_return_self_reference' => true, 166 | 'phpdoc_scalar' => true, 167 | 'phpdoc_separation' => true, 168 | 'phpdoc_single_line_var_spacing' => true, 169 | 'phpdoc_tag_type' => [ 170 | 'tags' => [ 171 | 'inheritDoc' => 'inline', 172 | ], 173 | ], 174 | 'phpdoc_to_comment' => [ 175 | 'ignored_tags' => [ 176 | 'var', 177 | ], 178 | ], 179 | 'phpdoc_trim' => true, 180 | 'phpdoc_trim_consecutive_blank_line_separation' => true, 181 | 'phpdoc_types' => true, 182 | 'phpdoc_types_order' => [ 183 | 'null_adjustment' => 'always_first', 184 | 'sort_algorithm' => 'alpha', 185 | ], 186 | 'phpdoc_var_without_name' => true, 187 | 'simple_to_complex_string_variable' => true, 188 | 'simplified_if_return' => true, 189 | 'single_class_element_per_statement' => true, 190 | 'single_import_per_statement' => true, 191 | 'single_line_comment_spacing' => true, 192 | 'single_line_comment_style' => [ 193 | 'comment_types' => [ 194 | 'hash', 195 | ], 196 | ], 197 | 'single_quote' => true, 198 | 'single_space_after_construct' => [ 199 | 'constructs' => [ 200 | 'abstract', 201 | 'as', 202 | 'attribute', 203 | 'break', 204 | 'case', 205 | 'catch', 206 | 'class', 207 | 'clone', 208 | 'comment', 209 | 'const', 210 | 'const_import', 211 | 'continue', 212 | 'do', 213 | 'echo', 214 | 'else', 215 | 'elseif', 216 | 'enum', 217 | 'extends', 218 | 'final', 219 | 'finally', 220 | 'for', 221 | 'foreach', 222 | 'function', 223 | 'function_import', 224 | 'global', 225 | 'goto', 226 | 'if', 227 | 'implements', 228 | 'include', 229 | 'include_once', 230 | 'instanceof', 231 | 'insteadof', 232 | 'interface', 233 | 'match', 234 | 'named_argument', 235 | 'namespace', 236 | 'new', 237 | 'open_tag_with_echo', 238 | 'php_doc', 239 | 'php_open', 240 | 'print', 241 | 'private', 242 | 'protected', 243 | 'public', 244 | 'readonly', 245 | 'require', 246 | 'require_once', 247 | 'return', 248 | 'static', 249 | 'switch', 250 | 'throw', 251 | 'trait', 252 | 'try', 253 | 'type_colon', 254 | 'use', 255 | 'use_lambda', 256 | 'use_trait', 257 | 'var', 258 | 'while', 259 | 'yield', 260 | 'yield_from', 261 | ], 262 | ], 263 | 'space_after_semicolon' => ['remove_in_empty_for_expressions' => true], 264 | 'standardize_increment' => true, 265 | 'standardize_not_equals' => true, 266 | 'switch_continue_to_break' => true, 267 | 'trailing_comma_in_multiline' => [ 268 | 'after_heredoc' => true, 269 | 'elements' => [ 270 | 'arguments', 271 | 'arrays', 272 | 'match', 273 | 'parameters', 274 | ], 275 | ], 276 | 'trim_array_spaces' => true, 277 | 'types_spaces' => true, 278 | 'unary_operator_spaces' => true, 279 | 'whitespace_after_comma_in_array' => true, 280 | 'yoda_style' => [ 281 | 'equal' => false, 282 | 'identical' => false, 283 | 'less_and_greater' => false, 284 | ], 285 | // Risky 286 | '@PSR12:risky' => true, 287 | '@PHP80Migration:risky' => true, 288 | // '@Symfony:risky' => true, // ? revise 289 | 'random_api_migration' => [ 290 | 'replacements' => [ 291 | 'mt_rand' => 'random_int', 292 | 'rand' => 'random_int', 293 | ], 294 | ], 295 | 'declare_strict_types' => false, 296 | 'void_return' => false, // temp, risky 297 | 'modernize_types_casting' => true, 298 | // 'self_accessor' => true, // temp, risky 299 | // 'date_time_immutable' => true, // temp, risky 300 | // 'native_function_invocation' => true, // ping Gorkovoy 301 | // 'native_constant_invocation' => true, // ping Gorkovoy 302 | 'function_to_constant' => true, 303 | 'is_null' => true, 304 | ])->setFinder($finder); 305 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Viacheslav Ostrovskiy 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 | 2 | # Laravel Relationship Events 3 | 4 | Missing relationship events for Laravel 5 | 6 |

7 | Build Status 8 | Total Downloads 9 | Latest Stable Version 10 | License 11 |

12 | 13 | ## Install 14 | 15 | 1. Install package with composer 16 | 17 | #### Stable branch: 18 | ``` 19 | composer require chelout/laravel-relationship-events 20 | ``` 21 | 22 | #### Development branch: 23 | ``` 24 | composer require chelout/laravel-relationship-events:dev-master 25 | ``` 26 | 27 | 2. Use necessary trait in your model. 28 | #### Available traits: 29 | - HasOneEvents 30 | - HasBelongsToEvents 31 | - HasManyEvents 32 | - HasBelongsToManyEvents 33 | - HasMorphOneEvents 34 | - HasMorphToEvents 35 | - HasMorphManyEvents 36 | - HasMorphToManyEvents 37 | - HasMorphedByManyEvents 38 | 39 | ```php 40 | 41 | use Chelout\RelationshipEvents\Concerns\HasOneEvents; 42 | use Illuminate\Database\Eloquent\Model; 43 | 44 | class User extends Model 45 | { 46 | use HasOneEvents; 47 | 48 | public static function boot() 49 | { 50 | parent::boot(); 51 | 52 | /** 53 | * One To One Relationship Events 54 | */ 55 | static::hasOneSaved(function ($parent, $related) { 56 | dump('hasOneSaved', $parent, $related); 57 | }); 58 | 59 | static::hasOneUpdated(function ($parent, $related) { 60 | dump('hasOneUpdated', $parent, $related); 61 | }); 62 | } 63 | 64 | } 65 | ``` 66 | 67 | ```php 68 | 69 | use Chelout\RelationshipEvents\Concerns\HasMorphToManyEvents; 70 | use Illuminate\Database\Eloquent\Model; 71 | 72 | class Post extends Model 73 | { 74 | use HasMorphToManyEvents; 75 | 76 | public static function boot() 77 | { 78 | parent::boot(); 79 | 80 | /** 81 | * Many To Many Polymorphic Relations Events. 82 | */ 83 | static::morphToManyAttached(function ($relation, $parent, $ids, $attributes) { 84 | dump('morphToManyAttached', $relation, $parent, $ids, $attributes); 85 | }); 86 | 87 | static::morphToManyDetached(function ($relation, $parent, $ids) { 88 | dump('morphToManyDetached', $relation, $parent, $ids); 89 | }); 90 | } 91 | 92 | public function tags() 93 | { 94 | return $this->morphToMany(Tag::class, 'taggable'); 95 | } 96 | 97 | } 98 | ``` 99 | 100 | 3. Dispatchable relationship events. 101 | It is possible to fire event classes via $dispatchesEvents properties and adding ```HasDispatchableEvents``` trait: 102 | 103 | ```php 104 | 105 | use Chelout\RelationshipEvents\Concerns\HasOneEvents; 106 | use Chelout\RelationshipEvents\Traits\HasDispatchableEvents; 107 | use Illuminate\Database\Eloquent\Model; 108 | 109 | class User extends Model 110 | { 111 | use HasDispatchableEvents; 112 | use HasOneEvents; 113 | 114 | protected $dispatchesEvents = [ 115 | 'hasOneSaved' => HasOneSaved::class, 116 | ]; 117 | 118 | } 119 | ``` 120 | 121 | ## Relationships 122 | - [One To One Relations](doc/1-one-to-one.md) 123 | - [One To Many Relations](doc/2-one-to-many.md) 124 | - [Many To Many Relations](doc/3-many-to-many.md) 125 | - [Has Many Through Relations](doc/4-has-many-through.md) 126 | - [One To One Polymorphic Relations](doc/5-one-to-one-polymorphic.md) 127 | - [One To Many Polymorphic Relations](doc/6-one-to-many-polymorphic.md) 128 | - [Many To Many Polymorphic Relations](doc/7-many-to-many-polymorphic.md) 129 | 130 | 131 | ## Observers 132 | Starting from v0.4 it is possible to use relationship events in [Laravel observers classes](https://laravel.com/docs/5.6/eloquent#observers) Usage is very simple. Let's take ```User``` and ```Profile``` classes from [One To One Relations](doc/1-one-to-one.md), add ```HasRelationshipObservables``` trait to ```User``` class. Define observer class: 133 | 134 | ```php 135 | namespace App\Observer; 136 | 137 | class UserObserver 138 | { 139 | /** 140 | * Handle the User "hasOneCreating" event. 141 | * 142 | * @param \App\Models\User $user 143 | * @param \Illuminate\Database\Eloquent\Model $related 144 | * 145 | * @return void 146 | */ 147 | public function hasOneCreating(User $user, Model $related) 148 | { 149 | Log::info("Creating profile for user {$related->name}."); 150 | } 151 | 152 | /** 153 | * Handle the User "hasOneCreated" event. 154 | * 155 | * @param \App\Models\User $user 156 | * @param \Illuminate\Database\Eloquent\Model $related 157 | * 158 | * @return void 159 | */ 160 | public function hasOneCreated(User $user, Model $related) 161 | { 162 | Log::info("Profile for user {$related->name} has been created."); 163 | } 164 | } 165 | ``` 166 | 167 | Don't forget to register an observer in the ```boot``` method of your ```AppServiceProvider```: 168 | ```php 169 | namespace App\Providers; 170 | 171 | use App\Models\User; 172 | use App\Observers\UserObserver; 173 | use Illuminate\Support\ServiceProvider; 174 | 175 | class AppServiceProvider extends ServiceProvider 176 | { 177 | // ... 178 | public function boot() 179 | { 180 | User::observe(UserObserver::class); 181 | } 182 | // ... 183 | } 184 | ``` 185 | 186 | And now just create profile for user: 187 | ```php 188 | // ... 189 | $user = factory(User::class)->create([ 190 | 'name' => 'John Smith', 191 | ]); 192 | 193 | // Create profile and assosiate it with user 194 | // This will fire two events hasOneCreating, hasOneCreated 195 | $user->profile()->create([ 196 | 'phone' => '8-800-123-45-67', 197 | 'email' => 'user@example.com', 198 | 'address' => 'One Infinite Loop Cupertino, CA 95014', 199 | ]); 200 | // ... 201 | ``` 202 | 203 | 204 | ## Todo 205 | - Tests, tests, tests 206 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chelout/laravel-relationship-events", 3 | "description": "Missing relationship events for Laravel", 4 | "homepage": "https://github.com/chelout/laravel-relationship-events", 5 | "license": "MIT", 6 | "keywords": [ 7 | "laravel", 8 | "relationship", 9 | "relations", 10 | "events" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "chelout", 15 | "email": "chelout@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.2", 20 | "illuminate/container": "^12.0", 21 | "illuminate/database": "^12.0", 22 | "illuminate/events": "^12.0", 23 | "illuminate/support": "^12.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", 27 | "orchestra/testbench": "^10.0", 28 | "friendsofphp/php-cs-fixer": "^3.14" 29 | }, 30 | "scripts": { 31 | "fix": "php-cs-fixer --config=.php-cs-fixer.php --dry-run --allow-risky=yes --verbose fix", 32 | "test": "phpunit tests" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Chelout\\RelationshipEvents\\": "src/" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Chelout\\RelationshipEvents\\Tests\\": "tests/" 42 | } 43 | }, 44 | "minimum-stability": "stable", 45 | "extra": { 46 | "laravel": { 47 | "providers": [ 48 | "Chelout\\RelationshipEvents\\RelationshipEventsServiceProvider" 49 | ] 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /doc/1-one-to-one.md: -------------------------------------------------------------------------------- 1 | # One To One Relations: 2 | 3 | ## hasOne 4 | 5 | ```User``` model might be associated with one ```Profile``` 6 | 7 | ```php 8 | namespace App\Models; 9 | 10 | use Illuminate\Database\Eloquent\Model; 11 | use Chelout\RelationshipEvents\Concerns\HasOneEvents; 12 | 13 | class User extends Model 14 | { 15 | use HasOneEvents; 16 | 17 | /** 18 | * Get the profile associated with the user. 19 | */ 20 | public function profile() 21 | { 22 | return $this->hasOne(Profile::class); 23 | } 24 | } 25 | ``` 26 | 27 | Now we can use methods to assosiate ```User``` with ```Profile``` and also update assosiated model. 28 | 29 | ```php 30 | // ... 31 | $user = factory(User::class)->create([ 32 | 'name' => 'John Smith', 33 | ]); 34 | 35 | // Create profile and assosiate it with user 36 | // This will fire two events hasOneCreating, hasOneCreated 37 | $user->profile()->create([ 38 | 'phone' => '8-800-123-45-67', 39 | 'email' => 'user@example.com', 40 | 'address' => 'One Infinite Loop Cupertino, CA 95014', 41 | ]); 42 | // ... 43 | ``` 44 | 45 | Now we should listen our events, for example we can register event listners in model's boot method: 46 | ```php 47 | // ... 48 | protected static function boot() 49 | { 50 | parent::boot(); 51 | 52 | static::hasOneCreating(function ($parent, $related) { 53 | Log::info("Creating profile for user {$parent->name}."); 54 | }); 55 | 56 | static::hasOneCreated(function ($parent, $related) { 57 | Log::info("Profile for user {$parent->name} has been created."); 58 | }); 59 | } 60 | // ... 61 | ``` 62 | 63 | ### Available methods and events 64 | 65 | #### HasOne::create (HasOneOrMany::save) 66 | - fires hasOneCreating, hasOneCreated 67 | - events have $parent and $related models 68 | 69 | #### HasOne::save (HasOneOrMany::save) 70 | - fires hasOneSaving, hasOneSaved 71 | - events have $parent and $related models 72 | 73 | #### HasOne::update (HasOneOrMany::update) 74 | - fires hasOneUpdating, hasOneUpdated 75 | - events have $parent and $related models 76 | > Note: has additional query to get related model 77 | 78 | ## belongsTo 79 | 80 | There is also inverse relation ```Profile``` belongs to ```User``` 81 | 82 | ```php 83 | namespace App\Models; 84 | 85 | use Illuminate\Database\Eloquent\Model; 86 | use Chelout\RelationshipEvents\Concerns\HasBelongsToEvents; 87 | 88 | class Profile extends Model 89 | { 90 | use HasBelongsToEvents; 91 | 92 | /** 93 | * Get the user that owns profile. 94 | */ 95 | public function user() 96 | { 97 | return $this->belongsTo(User::class); 98 | } 99 | } 100 | ``` 101 | 102 | Now we can use methods to assosiate, dissosiate and update ```User``` with ```Profile```: 103 | 104 | ```php 105 | // ... 106 | $user = factory(User::class)->create([ 107 | 'name' => 'John Smith', 108 | ]); 109 | 110 | $profile = factory(Profile::class)->create([ 111 | 'phone' => '8-800-123-45-67', 112 | 'email' => 'user@example.com', 113 | 'address' => 'One Infinite Loop Cupertino, CA 95014', 114 | ]); 115 | 116 | // Assosiate profile with user 117 | // This will fire two events belongsToAssociating, belongsToAssociated 118 | $profile->user()->assosiate($user); 119 | // ... 120 | ``` 121 | 122 | Now we should listen our events, for example we can register event listners in model's boot method: 123 | ```php 124 | // ... 125 | protected static function boot() 126 | { 127 | parent::boot(); 128 | 129 | static::belongsToAssociating(function ($relation, $related, $parent) { 130 | Log::info("Associating user {$parent->name} with profile."); 131 | }); 132 | 133 | static::belongsToAssociated(function ($relation, $related, $parent) { 134 | Log::info("Profile has been assosiated with user {$parent->name}."); 135 | }); 136 | } 137 | // ... 138 | ``` 139 | 140 | ### Available methods and events 141 | 142 | #### BelongsTo::associate 143 | - fires belongsToAssociating, belongsToAssociated 144 | - events have $relation name, $related model and $parent model or key(depends on BelongsTo::associate $model parametr). 145 | > Note: related model is dirty, should be saved after associating 146 | 147 | #### BelongsTo::dissociate 148 | - fires belongsToDissociating, belongsToDissociated 149 | - events have $relation name, $related and $parent models. 150 | > Note: has additional query to get parent model 151 | > Note: related model is dirty, should be saved after dissociating 152 | 153 | #### BelongsTo::update 154 | - fires belongsToUpdating, belongsToUpdated 155 | - events have $relation name, $related and $parent models. -------------------------------------------------------------------------------- /doc/2-one-to-many.md: -------------------------------------------------------------------------------- 1 | # One To Many Relations: 2 | 3 | ## hasMany 4 | 5 | ```Country``` model have many ```User``` models 6 | 7 | ```php 8 | namespace App\Models; 9 | 10 | use App\User; 11 | use Chelout\RelationshipEvents\Concerns\HasManyEvents; 12 | use Illuminate\Database\Eloquent\Model; 13 | 14 | class Country extends Model 15 | { 16 | use HasManyEvents; 17 | 18 | protected $fillable = [ 19 | 'name', 20 | ]; 21 | 22 | public function users() 23 | { 24 | return $this->hasMany(User::class); 25 | } 26 | } 27 | ``` 28 | 29 | Now we can use methods to assosiate ```Country``` with ```User``` and also update assosiated models. 30 | 31 | ```php 32 | // ... 33 | 34 | // create user 35 | $user = factory('App\User')->create(); 36 | 37 | // Getting random country 38 | $country = App\Models\Country::inRandomOrder()->first(); 39 | 40 | // Saving user with specified country 41 | // This will fire two events hasManySaving, hasManySaved 42 | $country->users()->save($user); 43 | 44 | // ... 45 | ``` 46 | 47 | Now we should listen our events, for example we can register event listners in model's boot method: 48 | ```php 49 | // ... 50 | public static function boot() 51 | { 52 | parent::boot(); 53 | 54 | /** 55 | * One To Many Relationship Events 56 | */ 57 | 58 | static::hasManySaving(function ($parent, $related) { 59 | Log::info("Saving user's country {$parent->name}."); 60 | }); 61 | 62 | static::hasManySaved(function ($parent, $related) { 63 | Log::info("User's country is now set to {$parent->name}."); 64 | }); 65 | } 66 | // ... 67 | ``` 68 | 69 | ### Available methods and events 70 | 71 | #### HasMany::create (HasOneOrMany::create) 72 | - fires hasManyCreating, hasManyCreated 73 | - events have $parent and $related models 74 | 75 | #### HasMany::save (HasOneOrMany::save) 76 | - fires hasManyCreating, hasManyCreated 77 | - events have $parent and $related models 78 | 79 | #### HasMany::update (HasOneOrMany::update) 80 | - fires hasManyUpdating, hasManyUpdated 81 | - events have $parent model and $related Eloquent collection 82 | > Note: has additional query to get related Eloquent collection 83 | 84 | ## belongsTo 85 | 86 | There is also inverse relation ```User``` belongs to ```Country``` 87 | 88 | ```php 89 | namespace App\Models; 90 | 91 | use Illuminate\Database\Eloquent\Model; 92 | use Chelout\RelationshipEvents\Concerns\HasBelongsToEvents; 93 | 94 | class User extends Model 95 | { 96 | use HasBelongsToEvents; 97 | 98 | /** 99 | * Get the country associated with the user. 100 | */ 101 | public function country() 102 | { 103 | return $this->belongsTo(Country::class); 104 | } 105 | } 106 | ``` 107 | 108 | Now we can use methods to assosiate, dissosiate and update ```Country``` with ```User```: 109 | 110 | ```php 111 | // ... 112 | $country = App\Models\Country::inRandomOrder()->first(); 113 | 114 | $user = factory(User::class)->create([ 115 | 'name' => 'John Smith', 116 | ]); 117 | 118 | // Assosiate user with country 119 | // This will fire two events belongsToAssociating, belongsToAssociated 120 | $user->country()->associate($country); 121 | // ... 122 | ``` 123 | 124 | Now we should listen our events, for example we can register event listners in model's boot method: 125 | ```php 126 | // ... 127 | protected static function boot() 128 | { 129 | parent::boot(); 130 | 131 | static::belongsToAssociating(function ($relation, $related, $parent) { 132 | Log::info("Associating country {$parent->name} with user."); 133 | }); 134 | 135 | static::belongsToAssociated(function ($relation, $related, $parent) { 136 | Log::info("User has been assosiated with country {$parent->name}."); 137 | }); 138 | } 139 | // ... 140 | ``` 141 | 142 | ### Available methods and events 143 | 144 | #### BelongsTo::associate 145 | - fires belongsToAssociating, belongsToAssociated 146 | - events have $relation name, $related model and $parent model or key(depends on BelongsTo::associate $model parametr). 147 | > Note: related model is dirty, should be saved after associating 148 | 149 | #### BelongsTo::dissociate 150 | - fires belongsToAssociating, belongsToAssociated 151 | - events have $relation name, $related and $parent models. 152 | > Note: has additional query to get parent model 153 | > Note: related model is dirty, should be saved after dissociating 154 | 155 | #### BelongsTo::update 156 | - fires belongsToUpdating, belongsToUpdated 157 | - events have $relation name, $related and $parent models. -------------------------------------------------------------------------------- /doc/3-many-to-many.md: -------------------------------------------------------------------------------- 1 | # Many To Many Relations: 2 | 3 | ## belongsToMany 4 | 5 | ```User``` model belongs to many ```Role``` models 6 | 7 | ```php 8 | namespace App\Models; 9 | 10 | use App\User; 11 | use Chelout\RelationshipEvents\Concerns\HasBelongsToManyEvents; 12 | use Illuminate\Database\Eloquent\Model; 13 | 14 | class User extends Model 15 | { 16 | use HasBelongsToManyEvents; 17 | 18 | protected $fillable = [ 19 | 'name', 'email', 'password', 'country_id', 20 | ]; 21 | 22 | public function roles() 23 | { 24 | return $this->belongsToMany(Role::class) 25 | ->withPivot('is_active'); 26 | } 27 | } 28 | ``` 29 | 30 | Now we can use attach, detach, sync, toggle methods to update existing pivot: 31 | 32 | ```php 33 | // ... 34 | $user = factory('App\User')->create(); 35 | 36 | // Attcha one role to user 37 | // This will fire two events belongsToManyAttaching, belongsToManyAttached 38 | $role = factory('App\Models\Role')->create(); 39 | $user->roles()->attach($role, ['is_active' => true]); 40 | 41 | // Attach many roles to user 42 | // This will fire two events belongsToManyAttaching, belongsToManyAttached 43 | $roles = factory('App\Models\Role', 2)->create(); 44 | $user->roles()->attach( 45 | $roles->mapWithKeys(function ($role) { 46 | return [ 47 | $role->id => [ 48 | 'is_active' => true, 49 | ], 50 | ]; 51 | }) 52 | ->toArray() 53 | ); 54 | 55 | // ... 56 | ``` 57 | 58 | Now we should listen our events, for example we can register event listners in model's boot method: 59 | ```php 60 | // ... 61 | protected static function boot() 62 | { 63 | parent::boot(); 64 | 65 | static::belongsToManyAttaching(function ($relation, $parent, $ids) { 66 | Log::info("Attaching roles to user {$parent->name}."); 67 | }); 68 | 69 | static::belongsToManyAttached(function ($relation, $parent, $ids) { 70 | Log::info("Roles has been attached to user {$parent->name}."); 71 | }); 72 | } 73 | // ... 74 | ``` 75 | 76 | ### Available methods and events 77 | 78 | #### BelongsToMany::attach 79 | - fires belongToManyAttaching, belongToManyAttached 80 | - events have $relation name, $parent model, $attributes attaching model ids 81 | #### BelongsToMany::detach 82 | - fires belongToManyDetaching, belongToManyDetached 83 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 84 | > Note: has additional query to get related ids 85 | #### BelongsToMany::sync 86 | - fires belongToManySyncing, belongToManySynced, BelongsToMany::attach, BelongsToMany::detach 87 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 88 | #### BelongsToMany::toggle 89 | - fires belongToManyToggling, belongToManyToggled, BelongsToMany::attach, BelongsToMany::detach 90 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 91 | #### BelongsToMany::updateExistingPivot 92 | - fires belongsToManyUpdatingExistingPivot, belongsToManyUpdatedExistingPivot 93 | - events have $relation name, $parent model, $id updating model id, $attributes additional data 94 | 95 | 96 | ## belongsTo 97 | 98 | There is also inverse relation ```User``` belongs to ```Country``` 99 | 100 | ```php 101 | namespace App\Models; 102 | 103 | use Illuminate\Database\Eloquent\Model; 104 | use Chelout\RelationshipEvents\Concerns\HasBelongsToEvents; 105 | 106 | class User extends Model 107 | { 108 | use HasBelongsToEvents; 109 | 110 | /** 111 | * Get the country associated with the user. 112 | */ 113 | public function country() 114 | { 115 | return $this->belongsTo(Country::class); 116 | } 117 | } 118 | ``` 119 | 120 | Now we can use methods to assosiate ```Country``` with ```User``` and also update assosiated models. 121 | 122 | ```php 123 | // ... 124 | 125 | // create user 126 | $user = factory('App\User')->create(); 127 | 128 | // Getting random country 129 | $country = App\Models\Country::inRandomOrder()->first(); 130 | 131 | // Saving user with specified country 132 | // This will fire two events hasManySaving, hasManySaved 133 | $country->users()->save($user); 134 | 135 | // ... 136 | ``` 137 | 138 | Now we should listen our events, for example we can register event listners in model's boot method: 139 | ```php 140 | // ... 141 | public static function boot() 142 | { 143 | parent::boot(); 144 | 145 | /** 146 | * One To Many Relationship Events 147 | */ 148 | 149 | static::hasManySaving(function ($parent, $related) { 150 | Log::info("Saving user's country {$parent->name}."); 151 | }); 152 | 153 | static::hasManySaved(function ($parent, $related) { 154 | Log::info("User's country is now set to {$parent->name}."); 155 | }); 156 | } 157 | // ... 158 | ``` 159 | 160 | ### Available methods and events 161 | 162 | #### HasMany::create (HasOneOrMany::create) 163 | - fires hasManyCreating, hasManyCreated 164 | - events have $parent and $related models 165 | 166 | #### HasMany::save (HasOneOrMany::save) 167 | - fires hasManyCreating, hasManyCreated 168 | - events have $parent and $related models 169 | 170 | #### HasMany::update (HasOneOrMany::update) 171 | - fires hasManyUpdating, hasManyUpdated 172 | - events have $parent model and $related Eloquent collection 173 | > Note: has additional query to get related Eloquent collection 174 | -------------------------------------------------------------------------------- /doc/4-has-many-through.md: -------------------------------------------------------------------------------- 1 | # Has Many Through Relations: 2 | - no events to be added 3 | -------------------------------------------------------------------------------- /doc/5-one-to-one-polymorphic.md: -------------------------------------------------------------------------------- 1 | # One To One Polymorphic Relation: 2 | 3 | ## morphOne 4 | 5 | ```User``` model might be associated with one ```Address``` 6 | 7 | ```php 8 | namespace App; 9 | 10 | use Illuminate\Database\Eloquent\Model; 11 | use Chelout\RelationshipEvents\Concerns\HasMorphOneEvents; 12 | 13 | class User extends Model 14 | { 15 | use HasMorphOneEvents; 16 | 17 | /** 18 | * Get the address associated with the user. 19 | */ 20 | public function address() 21 | { 22 | return $this->morphOne(Address::class, 'addressable'); 23 | } 24 | } 25 | ``` 26 | 27 | Now we can use methods to assosiate ```User``` with ```Address``` and also update assosiated model. 28 | 29 | ```php 30 | // ... 31 | $user = factory(User::class)->create([ 32 | 'name' => 'John Smith', 33 | ]); 34 | 35 | // Create address and assosiate it with user 36 | // This will fire two events morphOneCreating, morphOneCreated 37 | $user->address()->create([ 38 | 'phone' => '8-800-123-45-67', 39 | 'email' => 'user@example.com', 40 | 'address' => 'One Infinite Loop Cupertino, CA 95014', 41 | ]); 42 | // ... 43 | ``` 44 | 45 | Now we should listen our events, for example we can register event listners in model's boot method: 46 | ```php 47 | // ... 48 | protected static function boot() 49 | { 50 | parent::boot(); 51 | 52 | static::morphOneCreating(function ($parent, $related) { 53 | Log::info("Creating address for user {$parent->name}."); 54 | }); 55 | 56 | static::morphOneCreated(function ($parent, $related) { 57 | Log::info("Address for user {$parent->name} has been created."); 58 | }); 59 | } 60 | // ... 61 | ``` 62 | 63 | ### Available methods and events 64 | 65 | #### MorphOne::create (HasOneOrMany::create) 66 | - fires morphOneCreating, morphOneCreated 67 | - events have $parent and $related models 68 | 69 | #### MorphOne::save (HasOneOrMany::save) 70 | - fires morphOneSaving, morphOneSaved 71 | - events have $parent and $related models 72 | 73 | #### MorphOne::update (HasOneOrMany::update) 74 | - fires morphOneUpdating, morphOneUpdated 75 | - events have $parent and $related models 76 | 77 | ## belongsTo 78 | 79 | There is also inverse relation ```Address``` belongs to ```User``` 80 | 81 | ```php 82 | namespace App\Models; 83 | 84 | use Illuminate\Database\Eloquent\Model; 85 | use Chelout\RelationshipEvents\Concerns\HasBelongsToEvents; 86 | 87 | class Address extends Model 88 | { 89 | use HasBelongsToEvents; 90 | 91 | /** 92 | * Get the user that owns address. 93 | */ 94 | public function user() 95 | { 96 | return $this->morphTo(User::class); 97 | } 98 | } 99 | ``` 100 | 101 | Now we can use methods to assosiate, dissosiate and update ```User``` with ```Address```: 102 | 103 | ```php 104 | // ... 105 | $user = factory(User::class)->create([ 106 | 'name' => 'John Smith', 107 | ]); 108 | 109 | $address = factory(Address::class)->create([ 110 | 'phone' => '8-800-123-45-67', 111 | 'email' => 'user@example.com', 112 | 'address' => 'One Infinite Loop Cupertino, CA 95014', 113 | ]); 114 | 115 | // Assosiate address with user 116 | // This will fire two events morphToAssociating, morphToAssociated 117 | $address->user()->assosiate($user); 118 | // ... 119 | ``` 120 | 121 | Now we should listen our events, for example we can register event listners in model's boot method: 122 | ```php 123 | // ... 124 | protected static function boot() 125 | { 126 | parent::boot(); 127 | 128 | static::morphToAssociating(function ($relation, $related, $parent) { 129 | Log::info("Associating user {$parent->name} with address."); 130 | }); 131 | 132 | static::morphToAssociated(function ($relation, $related, $parent) { 133 | Log::info("Address has been assosiated with user {$parent->name}."); 134 | }); 135 | } 136 | // ... 137 | ``` 138 | 139 | ### Available methods and events 140 | 141 | #### MorphTo::associate 142 | - fires belongToAssociating, belongToAssociated 143 | - events have $relation name, $related and $parent models. 144 | > Note: related model is dirty, should be saved after associating 145 | 146 | #### MorphTo::dissociate 147 | - fires belongToAssociating, belongToAssociated 148 | - events have $relation name, $related and $parent models. 149 | > Note: has additional query to get parent model 150 | > Note: related model is dirty, should be saved after dissociating 151 | 152 | #### MorphTo::update 153 | - fires belongToUpdating, belongToUpdated 154 | - events have $relation name, $related and $parent models. 155 | > Note: has additional query to get related model -------------------------------------------------------------------------------- /doc/6-one-to-many-polymorphic.md: -------------------------------------------------------------------------------- 1 | # One To Many Polymorphic Relations: 2 | 3 | ## morphMany 4 | 5 | ```Post``` model have many ```Comment``` models 6 | 7 | ```php 8 | namespace App\Models; 9 | 10 | use Chelout\RelationshipEvents\Concerns\HasMorphManyEvents; 11 | use Illuminate\Database\Eloquent\Model; 12 | 13 | class Post extends Model 14 | { 15 | use HasMorphManyEvents; 16 | 17 | protected $fillable = [ 18 | 'title', 19 | 'body', 20 | ]; 21 | 22 | public function comments() 23 | { 24 | return $this->morphMany(Comment::class, 'commentable'); 25 | } 26 | } 27 | ``` 28 | 29 | Now we can use methods to assosiate ```Post``` with ```Comment``` and also update assosiated models. 30 | 31 | ```php 32 | // ... 33 | // create post 34 | $post = factory('App\Models\Post')->create(); 35 | 36 | // create comment 37 | $comment = factory('App\Models\Comment')->create(); 38 | 39 | // Saving comment with specified post 40 | // This will fire two events morphManySaving, morphManySaved 41 | $post->comments()->save($comment); 42 | 43 | // ... 44 | ``` 45 | 46 | Now we should listen our events, for example we can register event listners in model's boot method: 47 | ```php 48 | // ... 49 | public static function boot() 50 | { 51 | parent::boot(); 52 | 53 | /** 54 | * One To Many Polymorphic Relationship Events 55 | */ 56 | static::morphManySaving(function ($parent, $related) { 57 | Log::info("Saving comment's post."); 58 | }); 59 | 60 | static::morphManySaved(function ($parent, $related) { 61 | Log::info("Comment's post is now set."); 62 | }); 63 | } 64 | // ... 65 | ``` 66 | 67 | ### Available methods and events 68 | 69 | #### MorphMany::create (HasOneOrMany::create) 70 | - fires hasManyCreating, hasManyCreated 71 | - events have $parent and $related models 72 | #### MorphMany::save (HasOneOrMany::save) 73 | - fires hasManyCreating, hasManyCreated 74 | - events have $parent and $related models 75 | #### MorphMany::update (HasOneOrMany::update) 76 | - fires hasManyUpdating, hasManyUpdated 77 | - events have $parent model and $related Eloquent collection 78 | > Note: has additional query to get related Eloquent collection 79 | 80 | ## morphTo 81 | 82 | There is also inverse relation ```Comment``` belongs to ```Post``` 83 | 84 | ```php 85 | namespace App\Models; 86 | 87 | use Illuminate\Database\Eloquent\Model; 88 | use Chelout\RelationshipEvents\Concerns\HasMorphToEvents; 89 | 90 | class Comment extends Model 91 | { 92 | use HasMorphToEvents; 93 | 94 | /** 95 | * Get the post associated with the comment. 96 | */ 97 | public function post() 98 | { 99 | return $this->morphTo(Post::class); 100 | } 101 | } 102 | ``` 103 | 104 | Now we can use methods to assosiate, dissosiate and update ```Post``` with ```Comment```: 105 | 106 | ```php 107 | // ... 108 | // create post 109 | $post = factory('App\Models\Post')->create(); 110 | 111 | // create comment 112 | $comment = factory('App\Models\Comment')->create(); 113 | 114 | // Saving comment with specified post 115 | // This will fire two events morphToAssociating, morphToAssociated 116 | $comment->post()->associate($post); 117 | 118 | // ... 119 | ``` 120 | 121 | Now we should listen our events, for example we can register event listners in model's boot method: 122 | ```php 123 | // ... 124 | protected static function boot() 125 | { 126 | parent::boot(); 127 | 128 | static::morphToAssociating(function ($relation, $related, $parent) { 129 | Log::info("Associating post {$parent->name} with comment."); 130 | }); 131 | 132 | static::morphToAssociated(function ($relation, $related, $parent) { 133 | Log::info("Comment has been assosiated with post {$parent->name}."); 134 | }); 135 | } 136 | // ... 137 | ``` 138 | 139 | ### Available methods and events 140 | 141 | #### MorphTo::associate 142 | - fires belongToAssociating, belongToAssociated 143 | - events have $relation name, $related and $parent models. 144 | > Note: related model is dirty, should be saved after associating 145 | #### MorphTo::dissociate 146 | - fires belongToAssociating, belongToAssociated 147 | - events have $relation name, $related and $parent models. 148 | > Note: has additional query to get parent model 149 | > Note: related model is dirty, should be saved after dissociating 150 | #### MorphTo::update 151 | - fires belongToUpdating, belongToUpdated 152 | - events have $relation name, $related and $parent models. 153 | > Note: has additional query to get related model 154 | -------------------------------------------------------------------------------- /doc/7-many-to-many-polymorphic.md: -------------------------------------------------------------------------------- 1 | # Many To Many Polymorphic Relations: 2 | 3 | ## morphToMany 4 | 5 | ```Post``` model has many ```Tag``` models 6 | 7 | ```php 8 | namespace App\Models; 9 | 10 | use App\Tag; 11 | use Chelout\RelationshipEvents\Concerns\HasMorphToManyEvents; 12 | use Illuminate\Database\Eloquent\Model; 13 | 14 | class Post extends Model 15 | { 16 | use HasMorphToManyEvents; 17 | 18 | protected $fillable = [ 19 | 'title', 20 | 'body', 21 | ]; 22 | 23 | public function tags() 24 | { 25 | return $this->morphToMany(Tag::class, 'taggable'); 26 | } 27 | } 28 | ``` 29 | 30 | Now we can use methods to attach ```Tag``` to ```Post```. Also we can detach and sync models. 31 | 32 | ```php 33 | // ... 34 | 35 | // create post 36 | $post = factory('App\Post')->create(); 37 | 38 | // create couple tags 39 | $tags = factory('App\Tag', 2)->create(); 40 | 41 | // Attaching tags to post with random priority 42 | $post->tags()->attach( 43 | $tags->mapWithKeys(function ($tag) { 44 | return [ 45 | $tag->id => [ 46 | 'priority' => rand(1, 10), 47 | ] 48 | ]; 49 | }) 50 | ->toArray() 51 | ); 52 | 53 | // ... 54 | ``` 55 | 56 | Now we should listen our events, for example we can register event listners in model's boot method: 57 | ```php 58 | // ... 59 | public static function boot() 60 | { 61 | parent::boot(); 62 | 63 | /** 64 | * Many To Many Polymorphic Relationship Events 65 | */ 66 | 67 | static::morphToManyAttaching(function ($relation, $parent, $attributes) { 68 | Log::info("Attaching tags to post {$parent->title}."); 69 | }); 70 | 71 | static::morphToManyAttached(function ($relation, $parent, $attributes) { 72 | Log::info("Tags have been attached to post {$parent->title}."); 73 | }); 74 | } 75 | // ... 76 | ``` 77 | 78 | ### Available methods and events 79 | 80 | #### MorphToMany::attach 81 | - fires morphToManyAttaching, morphToManyAttached 82 | - events have $relation name, $parent model, $attributes attaching model ids 83 | 84 | #### MorphToMany::detach 85 | - fires morphToManyDetaching, morphToManyDetached 86 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 87 | > Note: has additional query to get related ids 88 | 89 | #### MorphToMany::sync 90 | - fires morphToManySyncing, morphToManySynced, MorphToMany::attach, MorphToMany::detach 91 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 92 | 93 | #### MorphToMany::toggle 94 | - fires morphToManyToggling, morphToManyToggled, MorphToMany::attach, MorphToMany::detach 95 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 96 | 97 | #### MorphToMany::updateExistingPivot 98 | - fires morphToManyUpdatingExistingPivot, morphToManyUpdatedExistingPivot 99 | - events have $relation name, $parent model, $id updating model id, $attributes additional data 100 | 101 | 102 | ## morphedByMany 103 | 104 | There is also inverse relation ```Tag``` morphed by ```Post``` 105 | 106 | ```php 107 | namespace App; 108 | 109 | use Illuminate\Database\Eloquent\Model; 110 | use Chelout\RelationshipEvents\Concerns\HasBelongsToEvents; 111 | 112 | class Tag extends Model 113 | { 114 | use HasMorphedByManyEvents; 115 | 116 | /** 117 | * Get morphed posts. 118 | */ 119 | public function posts() 120 | { 121 | return $this->morphedByMany(Post::class, 'taggable'); 122 | } 123 | } 124 | ``` 125 | 126 | Now we can use methods to attach, detach, sync, toggle and update existing pivot: 127 | 128 | ```php 129 | // create post 130 | $post = factory('App\Post')->create(); 131 | 132 | // create couple tags 133 | $tag = factory('App\Tag')->create(); 134 | 135 | // Attaching tags to post with random priority 136 | $tag->posts()->attach($tag, [ 137 | 'priority' => rand(1, 10), 138 | ]); 139 | 140 | // ... 141 | ``` 142 | 143 | Now we should listen our events, for example we can register event listners in model's boot method: 144 | ```php 145 | // ... 146 | protected static function boot() 147 | { 148 | parent::boot(); 149 | 150 | /** 151 | * Many To Many Polymorphic Relationship Events 152 | */ 153 | 154 | static::morphedByManyAttaching(function ($relation, $parent, $attributes) { 155 | Log::info("Attaching post to tag {$parent->name}."); 156 | }); 157 | 158 | static::morphedByManyAttached(function ($relation, $parent, $attributes) { 159 | Log::info("Post has been attached to tag {$parent->name}."); 160 | }); 161 | } 162 | // ... 163 | ``` 164 | 165 | ### Available methods and events 166 | 167 | #### MorphedByMany::attach 168 | - fires morphedByManyAttaching, morphedByManyAttached 169 | - events have $relation name, $parent model, $attributes attaching model ids 170 | #### MorphedByMany::detach 171 | - fires morphedByManyDetaching, morphedByManyDetached 172 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 173 | > Note: has additional query to get related ids 174 | #### MorphedByMany::sync 175 | - fires morphedByManySyncing, morphedByManySynced, MorphedByMany::attach, MorphedByMany::detach 176 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 177 | #### MorphedByMany::toggle 178 | - fires morphedByManyToggling, morphedByManyToggled, MorphedByMany::attach, MorphedByMany::detach 179 | - events have $relation name, $parent model, $ids detaching model ids, $attributes additional data 180 | #### MorphedByMany::updateExistingPivot 181 | - fires morphedByManyUpdatingExistingPivot, morphedByManyUpdatedExistingPivot 182 | - events have $relation name, $parent model, $id updating model id, $attributes additional data 183 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/ 6 | 7 | 8 | 9 | 10 | ./src 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/BelongsTo.php: -------------------------------------------------------------------------------- 1 | parent->fireModelBelongsToEvent('associating', $this->relationName, $model); 30 | 31 | $result = parent::associate($model); 32 | 33 | $this->parent->fireModelBelongsToEvent('associated', $this->relationName, $model); 34 | 35 | return $result; 36 | } 37 | 38 | /** 39 | * Dissociate previously associated model from the given parent. 40 | * 41 | * @return \Illuminate\Database\Eloquent\Model 42 | */ 43 | public function dissociate() 44 | { 45 | $parent = $this->getResults(); 46 | 47 | $this->parent->fireModelBelongsToEvent('dissociating', $this->relationName, $parent); 48 | 49 | $result = parent::dissociate(); 50 | 51 | if ($parent !== null) { 52 | $this->parent->fireModelBelongsToEvent('dissociated', $this->relationName, $parent); 53 | } 54 | 55 | return $result; 56 | } 57 | 58 | /** 59 | * Update the parent model on the relationship. 60 | * 61 | * @return mixed 62 | */ 63 | public function update(array $attributes) 64 | { 65 | $related = $this->getResults(); 66 | 67 | $this->parent->fireModelBelongsToEvent('updating', $this->relationName, $related); 68 | 69 | if ($result = $related->fill($attributes)->save()) { 70 | $this->parent->fireModelBelongsToEvent('updated', $this->relationName, $related); 71 | } 72 | 73 | return $result; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/BelongsToMany.php: -------------------------------------------------------------------------------- 1 | parent->fireModelBelongsToManyEvent('toggling', $this->getRelationName(), $ids); 33 | 34 | $result = parent::toggle($ids, $touch); 35 | 36 | $this->parent->fireModelBelongsToManyEvent('toggled', $this->getRelationName(), $ids, [], false); 37 | 38 | return $result; 39 | } 40 | 41 | /** 42 | * Sync the intermediate tables with a list of IDs or collection of models. 43 | * 44 | * @param \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model|\Illuminate\Support\Collection|int|string $ids 45 | * @param bool $detaching 46 | * 47 | * @return array 48 | */ 49 | public function sync($ids, $detaching = true) 50 | { 51 | $this->parent->fireModelBelongsToManyEvent('syncing', $this->getRelationName(), $ids); 52 | 53 | $result = parent::sync($ids, $detaching); 54 | 55 | $this->parent->fireModelBelongsToManyEvent('synced', $this->getRelationName(), $ids, [], false); 56 | 57 | return $result; 58 | } 59 | 60 | /** 61 | * Update an existing pivot record on the table. 62 | * 63 | * @param mixed $id 64 | * @param bool $touch 65 | * 66 | * @return int 67 | */ 68 | public function updateExistingPivot($id, array $attributes, $touch = true) 69 | { 70 | $this->parent->fireModelBelongsToManyEvent('updatingExistingPivot', $this->getRelationName(), $id, $attributes); 71 | 72 | if ($result = parent::updateExistingPivot($id, $attributes, $touch)) { 73 | $this->parent->fireModelBelongsToManyEvent('updatedExistingPivot', $this->getRelationName(), $id, $attributes, false); 74 | } 75 | 76 | return $result; 77 | } 78 | 79 | /** 80 | * Attach a model to the parent. 81 | * 82 | * @param mixed $id 83 | * @param bool $touch 84 | */ 85 | public function attach($id, array $attributes = [], $touch = true) 86 | { 87 | $this->parent->fireModelBelongsToManyEvent('attaching', $this->getRelationName(), $id, $attributes); 88 | 89 | parent::attach($id, $attributes, $touch); 90 | 91 | $this->parent->fireModelBelongsToManyEvent('attached', $this->getRelationName(), $id, $attributes, false); 92 | } 93 | 94 | /** 95 | * Detach models from the relationship. 96 | * 97 | * @param mixed $ids 98 | * @param bool $touch 99 | * 100 | * @return int 101 | */ 102 | public function detach($ids = null, $touch = true) 103 | { 104 | // Get detached ids to pass them to event 105 | $ids ??= $this->parent->{$this->getRelationName()}->pluck($this->relatedKey); 106 | 107 | $this->parent->fireModelBelongsToManyEvent('detaching', $this->getRelationName(), $ids); 108 | 109 | if ($result = parent::detach($ids, $touch)) { 110 | // If records are detached fire detached event 111 | // Note: detached event will be fired even if one of all records have been detached 112 | $this->parent->fireModelBelongsToManyEvent('detached', $this->getRelationName(), $ids, [], false); 113 | } 114 | 115 | return $result; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Concerns/HasBelongsToEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 43 | } 44 | } 45 | 46 | /** 47 | * Register a deleted model event with the dispatcher. 48 | * 49 | * @param Closure|string $callback 50 | */ 51 | public static function belongsToAssociating($callback) 52 | { 53 | static::registerModelBelongsToEvent('belongsToAssociating', $callback); 54 | } 55 | 56 | /** 57 | * Register a deleted model event with the dispatcher. 58 | * 59 | * @param Closure|string $callback 60 | */ 61 | public static function belongsToAssociated($callback) 62 | { 63 | static::registerModelBelongsToEvent('belongsToAssociated', $callback); 64 | } 65 | 66 | /** 67 | * Register a deleted model event with the dispatcher. 68 | * 69 | * @param Closure|string $callback 70 | */ 71 | public static function belongsToDissociating($callback) 72 | { 73 | static::registerModelBelongsToEvent('belongsToDissociating', $callback); 74 | } 75 | 76 | /** 77 | * Register a deleted model event with the dispatcher. 78 | * 79 | * @param Closure|string $callback 80 | */ 81 | public static function belongsToDissociated($callback) 82 | { 83 | static::registerModelBelongsToEvent('belongsToDissociated', $callback); 84 | } 85 | 86 | /** 87 | * Register a deleted model event with the dispatcher. 88 | * 89 | * @param Closure|string $callback 90 | */ 91 | public static function belongsToUpdating($callback) 92 | { 93 | static::registerModelBelongsToEvent('belongsToUpdating', $callback); 94 | } 95 | 96 | /** 97 | * Register a deleted model event with the dispatcher. 98 | * 99 | * @param Closure|string $callback 100 | */ 101 | public static function belongsToUpdated($callback) 102 | { 103 | static::registerModelBelongsToEvent('belongsToUpdated', $callback); 104 | } 105 | 106 | /** 107 | * Fire the given event for the model relationship. 108 | * 109 | * @param string $event 110 | * @param string $relation 111 | * @param \Illuminate\Database\Eloquent\Model|int|string $parent 112 | * @param bool $halt 113 | * 114 | * @return bool 115 | */ 116 | public function fireModelBelongsToEvent($event, $relation, $parent, $halt = true) 117 | { 118 | if (!isset(static::$dispatcher)) { 119 | return true; 120 | } 121 | 122 | $event = 'belongsTo' . ucfirst($event); 123 | 124 | // First, we will get the proper method to call on the event dispatcher, and then we 125 | // will attempt to fire a custom, object based event for the given event. If that 126 | // returns a result we can return that result, or we'll call the string events. 127 | $method = $halt ? 'until' : 'dispatch'; 128 | 129 | $result = $this->filterModelEventResults( 130 | $this->fireCustomModelEvent($event, $method, $relation, $parent), 131 | ); 132 | 133 | if ($result === false) { 134 | return false; 135 | } 136 | 137 | return !empty($result) ? $result : static::$dispatcher->{$method}( 138 | "eloquent.{$event}: " . static::class, 139 | [ 140 | $relation, 141 | $this, 142 | $parent, 143 | ] 144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Concerns/HasBelongsToManyEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 55 | } 56 | } 57 | 58 | /** 59 | * Register a deleted model event with the dispatcher. 60 | * 61 | * @param Closure|string $callback 62 | */ 63 | public static function belongsToManyAttaching($callback) 64 | { 65 | static::registerModelBelongsToManyEvent('belongsToManyAttaching', $callback); 66 | } 67 | 68 | /** 69 | * Register a deleted model event with the dispatcher. 70 | * 71 | * @param Closure|string $callback 72 | */ 73 | public static function belongsToManyAttached($callback) 74 | { 75 | static::registerModelBelongsToManyEvent('belongsToManyAttached', $callback); 76 | } 77 | 78 | /** 79 | * Register a deleted model event with the dispatcher. 80 | * 81 | * @param Closure|string $callback 82 | */ 83 | public static function belongsToManyDetaching($callback) 84 | { 85 | static::registerModelBelongsToManyEvent('belongsToManyDetaching', $callback); 86 | } 87 | 88 | /** 89 | * Register a deleted model event with the dispatcher. 90 | * 91 | * @param Closure|string $callback 92 | */ 93 | public static function belongsToManyDetached($callback) 94 | { 95 | static::registerModelBelongsToManyEvent('belongsToManyDetached', $callback); 96 | } 97 | 98 | /** 99 | * Register a deleted model event with the dispatcher. 100 | * 101 | * @param Closure|string $callback 102 | */ 103 | public static function belongsToManySyncing($callback) 104 | { 105 | static::registerModelBelongsToManyEvent('belongsToManySyncing', $callback); 106 | } 107 | 108 | /** 109 | * Register a deleted model event with the dispatcher. 110 | * 111 | * @param Closure|string $callback 112 | */ 113 | public static function belongsToManySynced($callback) 114 | { 115 | static::registerModelBelongsToManyEvent('belongsToManySynced', $callback); 116 | } 117 | 118 | /** 119 | * Register a deleted model event with the dispatcher. 120 | * 121 | * @param Closure|string $callback 122 | */ 123 | public static function belongsToManyToggling($callback) 124 | { 125 | static::registerModelBelongsToManyEvent('belongsToManyToggling', $callback); 126 | } 127 | 128 | /** 129 | * Register a deleted model event with the dispatcher. 130 | * 131 | * @param Closure|string $callback 132 | */ 133 | public static function belongsToManyToggled($callback) 134 | { 135 | static::registerModelBelongsToManyEvent('belongsToManyToggled', $callback); 136 | } 137 | 138 | /** 139 | * Register a deleted model event with the dispatcher. 140 | * 141 | * @param Closure|string $callback 142 | */ 143 | public static function belongsToManyUpdatingExistingPivot($callback) 144 | { 145 | static::registerModelBelongsToManyEvent('belongsToManyUpdatingExistingPivot', $callback); 146 | } 147 | 148 | /** 149 | * Register a deleted model event with the dispatcher. 150 | * 151 | * @param Closure|string $callback 152 | */ 153 | public static function belongsToManyUpdatedExistingPivot($callback) 154 | { 155 | static::registerModelBelongsToManyEvent('belongsToManyUpdatedExistingPivot', $callback); 156 | } 157 | 158 | /** 159 | * Fire the given event for the model relationship. 160 | * 161 | * @param string $event 162 | * @param string $relation 163 | * @param mixed $ids 164 | * @param array $attributes 165 | * @param bool $halt 166 | * 167 | * @return mixed 168 | */ 169 | public function fireModelBelongsToManyEvent($event, $relation, $ids, $attributes = [], $halt = true) 170 | { 171 | if (!isset(static::$dispatcher)) { 172 | return true; 173 | } 174 | 175 | $parsedIds = AttributesMethods::parseIds($ids); 176 | $parsedIdsForEvent = AttributesMethods::parseIdsForEvent($parsedIds); 177 | $parseAttributesForEvent = AttributesMethods::parseAttributesForEvent($ids, $parsedIds, $attributes); 178 | 179 | $event = 'belongsToMany' . ucfirst($event); 180 | 181 | // First, we will get the proper method to call on the event dispatcher, and then we 182 | // will attempt to fire a custom, object based event for the given event. If that 183 | // returns a result we can return that result, or we'll call the string events. 184 | $method = $halt ? 'until' : 'dispatch'; 185 | 186 | $result = $this->filterModelEventResults( 187 | $this->fireCustomModelEvent($event, $method, $relation, $parsedIdsForEvent, $parseAttributesForEvent), 188 | ); 189 | 190 | if ($result === false) { 191 | return false; 192 | } 193 | 194 | return !empty($result) ? $result : static::$dispatcher->{$method}( 195 | "eloquent.{$event}: " . static::class, 196 | [ 197 | $relation, 198 | $this, 199 | $parsedIdsForEvent, 200 | $parseAttributesForEvent, 201 | ] 202 | ); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/Concerns/HasManyEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 42 | } 43 | } 44 | 45 | /** 46 | * Register a deleted model event with the dispatcher. 47 | * 48 | * @param Closure|string $callback 49 | */ 50 | public static function hasManyCreating($callback) 51 | { 52 | static::registerModelHasManyEvent('hasManyCreating', $callback); 53 | } 54 | 55 | /** 56 | * Register a deleted model event with the dispatcher. 57 | * 58 | * @param Closure|string $callback 59 | */ 60 | public static function hasManyCreated($callback) 61 | { 62 | static::registerModelHasManyEvent('hasManyCreated', $callback); 63 | } 64 | 65 | /** 66 | * Register a deleted model event with the dispatcher. 67 | * 68 | * @param Closure|string $callback 69 | */ 70 | public static function hasManySaving($callback) 71 | { 72 | static::registerModelHasManyEvent('hasManySaving', $callback); 73 | } 74 | 75 | /** 76 | * Register a deleted model event with the dispatcher. 77 | * 78 | * @param Closure|string $callback 79 | */ 80 | public static function hasManySaved($callback) 81 | { 82 | static::registerModelHasManyEvent('hasManySaved', $callback); 83 | } 84 | 85 | /** 86 | * Register a deleted model event with the dispatcher. 87 | * 88 | * @param Closure|string $callback 89 | */ 90 | public static function hasManyUpdating($callback) 91 | { 92 | static::registerModelHasManyEvent('hasManyUpdating', $callback); 93 | } 94 | 95 | /** 96 | * Register a deleted model event with the dispatcher. 97 | * 98 | * @param Closure|string $callback 99 | */ 100 | public static function hasManyUpdated($callback) 101 | { 102 | static::registerModelHasManyEvent('hasManyUpdated', $callback); 103 | } 104 | 105 | /** 106 | * Fire the given event for the model relationship. 107 | * 108 | * @param string $event 109 | * @param mixed $related 110 | * @param bool $halt 111 | * 112 | * @return mixed 113 | */ 114 | public function fireModelHasManyEvent($event, $related = null, $halt = true) 115 | { 116 | if (!isset(static::$dispatcher)) { 117 | return true; 118 | } 119 | 120 | $event = 'hasMany' . ucfirst($event); 121 | 122 | // First, we will get the proper method to call on the event dispatcher, and then we 123 | // will attempt to fire a custom, object based event for the given event. If that 124 | // returns a result we can return that result, or we'll call the string events. 125 | $method = $halt ? 'until' : 'dispatch'; 126 | 127 | $result = $this->filterModelEventResults( 128 | $this->fireCustomModelEvent($event, $method, $related), 129 | ); 130 | 131 | if ($result === false) { 132 | return false; 133 | } 134 | 135 | return !empty($result) ? $result : static::$dispatcher->{$method}( 136 | "eloquent.{$event}: " . static::class, 137 | [ 138 | $this, 139 | $related, 140 | ] 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Concerns/HasMorphManyEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 43 | } 44 | } 45 | 46 | /** 47 | * Register a deleted model event with the dispatcher. 48 | * 49 | * @param Closure|string $callback 50 | */ 51 | public static function morphManyCreating($callback) 52 | { 53 | static::registerModelMorphManyEvent('morphManyCreating', $callback); 54 | } 55 | 56 | /** 57 | * Register a deleted model event with the dispatcher. 58 | * 59 | * @param Closure|string $callback 60 | */ 61 | public static function morphManyCreated($callback) 62 | { 63 | static::registerModelMorphManyEvent('morphManyCreated', $callback); 64 | } 65 | 66 | /** 67 | * Register a deleted model event with the dispatcher. 68 | * 69 | * @param Closure|string $callback 70 | */ 71 | public static function morphManySaving($callback) 72 | { 73 | static::registerModelMorphManyEvent('morphManySaving', $callback); 74 | } 75 | 76 | /** 77 | * Register a deleted model event with the dispatcher. 78 | * 79 | * @param Closure|string $callback 80 | */ 81 | public static function morphManySaved($callback) 82 | { 83 | static::registerModelMorphManyEvent('morphManySaved', $callback); 84 | } 85 | 86 | /** 87 | * Register a deleted model event with the dispatcher. 88 | * 89 | * @param Closure|string $callback 90 | */ 91 | public static function morphManyUpdating($callback) 92 | { 93 | static::registerModelMorphManyEvent('morphManyUpdating', $callback); 94 | } 95 | 96 | /** 97 | * Register a deleted model event with the dispatcher. 98 | * 99 | * @param Closure|string $callback 100 | */ 101 | public static function morphManyUpdated($callback) 102 | { 103 | static::registerModelMorphManyEvent('morphManyUpdated', $callback); 104 | } 105 | 106 | /** 107 | * Fire the given event for the model relationship. 108 | * 109 | * @param string $event 110 | * @param mixed $related 111 | * @param bool $halt 112 | * 113 | * @return mixed 114 | */ 115 | public function fireModelMorphManyEvent($event, $related = null, $halt = true) 116 | { 117 | if (!isset(static::$dispatcher)) { 118 | return true; 119 | } 120 | 121 | $event = 'morphMany' . ucfirst($event); 122 | 123 | // First, we will get the proper method to call on the event dispatcher, and then we 124 | // will attempt to fire a custom, object based event for the given event. If that 125 | // returns a result we can return that result, or we'll call the string events. 126 | $method = $halt ? 'until' : 'dispatch'; 127 | 128 | $result = $this->filterModelEventResults( 129 | $this->fireCustomModelEvent($event, $method, $related), 130 | ); 131 | 132 | if ($result === false) { 133 | return false; 134 | } 135 | 136 | return !empty($result) ? $result : static::$dispatcher->{$method}( 137 | "eloquent.{$event}: " . static::class, 138 | [ 139 | $this, 140 | $related, 141 | ] 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Concerns/HasMorphOneEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 43 | } 44 | } 45 | 46 | /** 47 | * Register a deleted model event with the dispatcher. 48 | * 49 | * @param Closure|string $callback 50 | */ 51 | public static function morphOneCreating($callback) 52 | { 53 | static::registerModelMorphOneEvent('morphOneCreating', $callback); 54 | } 55 | 56 | /** 57 | * Register a deleted model event with the dispatcher. 58 | * 59 | * @param Closure|string $callback 60 | */ 61 | public static function morphOneCreated($callback) 62 | { 63 | static::registerModelMorphOneEvent('morphOneCreated', $callback); 64 | } 65 | 66 | /** 67 | * Register a deleted model event with the dispatcher. 68 | * 69 | * @param Closure|string $callback 70 | */ 71 | public static function morphOneSaving($callback) 72 | { 73 | static::registerModelMorphOneEvent('morphOneSaving', $callback); 74 | } 75 | 76 | /** 77 | * Register a deleted model event with the dispatcher. 78 | * 79 | * @param Closure|string $callback 80 | */ 81 | public static function morphOneSaved($callback) 82 | { 83 | static::registerModelMorphOneEvent('morphOneSaved', $callback); 84 | } 85 | 86 | /** 87 | * Register a deleted model event with the dispatcher. 88 | * 89 | * @param Closure|string $callback 90 | */ 91 | public static function morphOneUpdating($callback) 92 | { 93 | static::registerModelMorphOneEvent('morphOneUpdating', $callback); 94 | } 95 | 96 | /** 97 | * Register a deleted model event with the dispatcher. 98 | * 99 | * @param Closure|string $callback 100 | */ 101 | public static function morphOneUpdated($callback) 102 | { 103 | static::registerModelMorphOneEvent('morphOneUpdated', $callback); 104 | } 105 | 106 | /** 107 | * Fire the given event for the model relationship. 108 | * 109 | * @param string $event 110 | * @param mixed $related 111 | * @param bool $halt 112 | * 113 | * @return mixed 114 | */ 115 | public function fireModelMorphOneEvent($event, $related = null, $halt = true) 116 | { 117 | if (!isset(static::$dispatcher)) { 118 | return true; 119 | } 120 | 121 | $event = 'morphOne' . ucfirst($event); 122 | 123 | // First, we will get the proper method to call on the event dispatcher, and then we 124 | // will attempt to fire a custom, object based event for the given event. If that 125 | // returns a result we can return that result, or we'll call the string events. 126 | $method = $halt ? 'until' : 'dispatch'; 127 | 128 | $result = $this->filterModelEventResults( 129 | $this->fireCustomModelEvent($event, $method, $related), 130 | ); 131 | 132 | if ($result === false) { 133 | return false; 134 | } 135 | 136 | return !empty($result) ? $result : static::$dispatcher->{$method}( 137 | "eloquent.{$event}: " . static::class, 138 | [ 139 | $this, 140 | $related, 141 | ] 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Concerns/HasMorphToEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 44 | } 45 | } 46 | 47 | /** 48 | * Register a deleted model event with the dispatcher. 49 | * 50 | * @param Closure|string $callback 51 | */ 52 | public static function morphToAssociating($callback) 53 | { 54 | static::registerModelMorphToEvent('morphToAssociating', $callback); 55 | } 56 | 57 | /** 58 | * Register a deleted model event with the dispatcher. 59 | * 60 | * @param Closure|string $callback 61 | */ 62 | public static function morphToAssociated($callback) 63 | { 64 | static::registerModelMorphToEvent('morphToAssociated', $callback); 65 | } 66 | 67 | /** 68 | * Register a deleted model event with the dispatcher. 69 | * 70 | * @param Closure|string $callback 71 | */ 72 | public static function morphToDissociating($callback) 73 | { 74 | static::registerModelMorphToEvent('morphToDissociating', $callback); 75 | } 76 | 77 | /** 78 | * Register a deleted model event with the dispatcher. 79 | * 80 | * @param Closure|string $callback 81 | */ 82 | public static function morphToDissociated($callback) 83 | { 84 | static::registerModelMorphToEvent('morphToDissociated', $callback); 85 | } 86 | 87 | /** 88 | * Register a deleted model event with the dispatcher. 89 | * 90 | * @param Closure|string $callback 91 | */ 92 | public static function morphToUpdating($callback) 93 | { 94 | static::registerModelMorphToEvent('morphToUpdating', $callback); 95 | } 96 | 97 | /** 98 | * Register a deleted model event with the dispatcher. 99 | * 100 | * @param Closure|string $callback 101 | */ 102 | public static function morphToUpdated($callback) 103 | { 104 | static::registerModelMorphToEvent('morphToUpdated', $callback); 105 | } 106 | 107 | /** 108 | * Fire the given event for the model relationship. 109 | * 110 | * @param string $event 111 | * @param string $relation 112 | * @param \Illuminate\Database\Eloquent\Model|int|string $parent 113 | * @param bool $halt 114 | * 115 | * @return mixed 116 | */ 117 | public function fireModelMorphToEvent($event, $relation, $parent, $halt = true) 118 | { 119 | if (!isset(static::$dispatcher)) { 120 | return true; 121 | } 122 | 123 | $event = 'morphTo' . ucfirst($event); 124 | 125 | // First, we will get the proper method to call on the event dispatcher, and then we 126 | // will attempt to fire a custom, object based event for the given event. If that 127 | // returns a result we can return that result, or we'll call the string events. 128 | $method = $halt ? 'until' : 'dispatch'; 129 | 130 | $result = $this->filterModelEventResults( 131 | $this->fireCustomModelEvent($event, $method, $relation, $parent), 132 | ); 133 | 134 | if ($result === false) { 135 | return false; 136 | } 137 | 138 | return !empty($result) ? $result : static::$dispatcher->{$method}( 139 | "eloquent.{$event}: " . static::class, 140 | [ 141 | $relation, 142 | $this, 143 | $parent, 144 | ] 145 | ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Concerns/HasMorphToManyEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 70 | } 71 | } 72 | 73 | /** 74 | * Register a deleted model event with the dispatcher. 75 | * 76 | * @param Closure|string $callback 77 | */ 78 | public static function morphToManyCreating($callback) 79 | { 80 | static::registerModelMorphToManyEvent('morphToManyCreating', $callback); 81 | } 82 | 83 | /** 84 | * Register a deleted model event with the dispatcher. 85 | * 86 | * @param Closure|string $callback 87 | */ 88 | public static function morphToManyCreated($callback) 89 | { 90 | static::registerModelMorphToManyEvent('morphToManyCreated', $callback); 91 | } 92 | 93 | /** 94 | * Register a deleted model event with the dispatcher. 95 | * 96 | * @param Closure|string $callback 97 | */ 98 | public static function morphToManySaving($callback) 99 | { 100 | static::registerModelMorphToManyEvent('morphToManySaving', $callback); 101 | } 102 | 103 | /** 104 | * Register a deleted model event with the dispatcher. 105 | * 106 | * @param Closure|string $callback 107 | */ 108 | public static function morphToManySaved($callback) 109 | { 110 | static::registerModelMorphToManyEvent('morphToManySaved', $callback); 111 | } 112 | 113 | /** 114 | * Register a deleted model event with the dispatcher. 115 | * 116 | * @param Closure|string $callback 117 | */ 118 | public static function morphToManyAttaching($callback) 119 | { 120 | static::registerModelMorphToManyEvent('morphToManyAttaching', $callback); 121 | } 122 | 123 | /** 124 | * Register a deleted model event with the dispatcher. 125 | * 126 | * @param Closure|string $callback 127 | */ 128 | public static function morphToManyAttached($callback) 129 | { 130 | static::registerModelMorphToManyEvent('morphToManyAttached', $callback); 131 | } 132 | 133 | /** 134 | * Register a deleted model event with the dispatcher. 135 | * 136 | * @param Closure|string $callback 137 | */ 138 | public static function morphToManyDetaching($callback) 139 | { 140 | static::registerModelMorphToManyEvent('morphToManyDetaching', $callback); 141 | } 142 | 143 | /** 144 | * Register a deleted model event with the dispatcher. 145 | * 146 | * @param Closure|string $callback 147 | */ 148 | public static function morphToManyDetached($callback) 149 | { 150 | static::registerModelMorphToManyEvent('morphToManyDetached', $callback); 151 | } 152 | 153 | /** 154 | * Register a deleted model event with the dispatcher. 155 | * 156 | * @param Closure|string $callback 157 | */ 158 | public static function morphToManySyncing($callback) 159 | { 160 | static::registerModelMorphToManyEvent('morphToManySyncing', $callback); 161 | } 162 | 163 | /** 164 | * Register a deleted model event with the dispatcher. 165 | * 166 | * @param Closure|string $callback 167 | */ 168 | public static function morphToManySynced($callback) 169 | { 170 | static::registerModelMorphToManyEvent('morphToManySynced', $callback); 171 | } 172 | 173 | /** 174 | * Register a deleted model event with the dispatcher. 175 | * 176 | * @param Closure|string $callback 177 | */ 178 | public static function morphToManyToggling($callback) 179 | { 180 | static::registerModelMorphToManyEvent('morphToManyToggling', $callback); 181 | } 182 | 183 | /** 184 | * Register a deleted model event with the dispatcher. 185 | * 186 | * @param Closure|string $callback 187 | */ 188 | public static function morphToManyToggled($callback) 189 | { 190 | static::registerModelMorphToManyEvent('morphToManyToggled', $callback); 191 | } 192 | 193 | /** 194 | * Register a deleted model event with the dispatcher. 195 | * 196 | * @param Closure|string $callback 197 | */ 198 | public static function morphToManyUpdatingExistingPivot($callback) 199 | { 200 | static::registerModelMorphToManyEvent('morphToManyUpdatingExistingPivot', $callback); 201 | } 202 | 203 | /** 204 | * Register a deleted model event with the dispatcher. 205 | * 206 | * @param Closure|string $callback 207 | */ 208 | public static function morphToManyUpdatedExistingPivot($callback) 209 | { 210 | static::registerModelMorphToManyEvent('morphToManyUpdatedExistingPivot', $callback); 211 | } 212 | 213 | /** 214 | * Fire the given event for the model relationship. 215 | * 216 | * @param string $event 217 | * @param string $relation 218 | * @param mixed $ids 219 | * @param array $attributes 220 | * @param bool $halt 221 | * 222 | * @return mixed 223 | */ 224 | public function fireModelMorphToManyEvent($event, $relation, $ids, $attributes = [], $halt = true) 225 | { 226 | if (!isset(static::$dispatcher)) { 227 | return true; 228 | } 229 | 230 | $parsedIds = AttributesMethods::parseIds($ids); 231 | $parsedIdsForEvent = AttributesMethods::parseIdsForEvent($parsedIds); 232 | $parseAttributesForEvent = AttributesMethods::parseAttributesForEvent($ids, $parsedIds, $attributes); 233 | 234 | $event = 'morphToMany' . ucfirst($event); 235 | 236 | // First, we will get the proper method to call on the event dispatcher, and then we 237 | // will attempt to fire a custom, object based event for the given event. If that 238 | // returns a result we can return that result, or we'll call the string events. 239 | $method = $halt ? 'until' : 'dispatch'; 240 | 241 | $result = $this->filterModelEventResults( 242 | $this->fireCustomModelEvent($event, $method, $relation, $parsedIdsForEvent, $parseAttributesForEvent), 243 | ); 244 | 245 | if ($result === false) { 246 | return false; 247 | } 248 | 249 | return !empty($result) ? $result : static::$dispatcher->{$method}( 250 | "eloquent.{$event}: " . static::class, 251 | [ 252 | $relation, 253 | $this, 254 | $parsedIdsForEvent, 255 | $parseAttributesForEvent, 256 | ] 257 | ); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/Concerns/HasMorphedByManyEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 70 | } 71 | } 72 | 73 | /** 74 | * Register a deleted model event with the dispatcher. 75 | * 76 | * @param Closure|string $callback 77 | */ 78 | public static function morphedByManyCreating($callback) 79 | { 80 | static::registerModelMorphedByManyEvent('morphedByManyCreating', $callback); 81 | } 82 | 83 | /** 84 | * Register a deleted model event with the dispatcher. 85 | * 86 | * @param Closure|string $callback 87 | */ 88 | public static function morphedByManyCreated($callback) 89 | { 90 | static::registerModelMorphedByManyEvent('morphedByManyCreated', $callback); 91 | } 92 | 93 | /** 94 | * Register a deleted model event with the dispatcher. 95 | * 96 | * @param Closure|string $callback 97 | */ 98 | public static function morphedByManySaving($callback) 99 | { 100 | static::registerModelMorphedByManyEvent('morphedByManySaving', $callback); 101 | } 102 | 103 | /** 104 | * Register a deleted model event with the dispatcher. 105 | * 106 | * @param Closure|string $callback 107 | */ 108 | public static function morphedByManySaved($callback) 109 | { 110 | static::registerModelMorphedByManyEvent('morphedByManySaved', $callback); 111 | } 112 | 113 | /** 114 | * Register a deleted model event with the dispatcher. 115 | * 116 | * @param Closure|string $callback 117 | */ 118 | public static function morphedByManyAttaching($callback) 119 | { 120 | static::registerModelMorphedByManyEvent('morphedByManyAttaching', $callback); 121 | } 122 | 123 | /** 124 | * Register a deleted model event with the dispatcher. 125 | * 126 | * @param Closure|string $callback 127 | */ 128 | public static function morphedByManyAttached($callback) 129 | { 130 | static::registerModelMorphedByManyEvent('morphedByManyAttached', $callback); 131 | } 132 | 133 | /** 134 | * Register a deleted model event with the dispatcher. 135 | * 136 | * @param Closure|string $callback 137 | */ 138 | public static function morphedByManyDetaching($callback) 139 | { 140 | static::registerModelMorphedByManyEvent('morphedByManyDetaching', $callback); 141 | } 142 | 143 | /** 144 | * Register a deleted model event with the dispatcher. 145 | * 146 | * @param Closure|string $callback 147 | */ 148 | public static function morphedByManyDetached($callback) 149 | { 150 | static::registerModelMorphedByManyEvent('morphedByManyDetached', $callback); 151 | } 152 | 153 | /** 154 | * Register a deleted model event with the dispatcher. 155 | * 156 | * @param Closure|string $callback 157 | */ 158 | public static function morphedByManySyncing($callback) 159 | { 160 | static::registerModelMorphedByManyEvent('morphedByManySyncing', $callback); 161 | } 162 | 163 | /** 164 | * Register a deleted model event with the dispatcher. 165 | * 166 | * @param Closure|string $callback 167 | */ 168 | public static function morphedByManySynced($callback) 169 | { 170 | static::registerModelMorphedByManyEvent('morphedByManySynced', $callback); 171 | } 172 | 173 | /** 174 | * Register a deleted model event with the dispatcher. 175 | * 176 | * @param Closure|string $callback 177 | */ 178 | public static function morphedByManyToggling($callback) 179 | { 180 | static::registerModelMorphedByManyEvent('morphedByManyToggling', $callback); 181 | } 182 | 183 | /** 184 | * Register a deleted model event with the dispatcher. 185 | * 186 | * @param Closure|string $callback 187 | */ 188 | public static function morphedByManyToggled($callback) 189 | { 190 | static::registerModelMorphedByManyEvent('morphedByManyToggled', $callback); 191 | } 192 | 193 | /** 194 | * Register a deleted model event with the dispatcher. 195 | * 196 | * @param Closure|string $callback 197 | */ 198 | public static function morphedByManyUpdatingExistingPivot($callback) 199 | { 200 | static::registerModelMorphedByManyEvent('morphedByManyUpdatingExistingPivot', $callback); 201 | } 202 | 203 | /** 204 | * Register a deleted model event with the dispatcher. 205 | * 206 | * @param Closure|string $callback 207 | */ 208 | public static function morphedByManyUpdatedExistingPivot($callback) 209 | { 210 | static::registerModelMorphedByManyEvent('morphedByManyUpdatedExistingPivot', $callback); 211 | } 212 | 213 | /** 214 | * Fire the given event for the model relationship. 215 | * 216 | * @param string $event 217 | * @param string $relation 218 | * @param mixed $ids 219 | * @param array $attributes 220 | * @param bool $halt 221 | * 222 | * @return mixed 223 | */ 224 | public function fireModelMorphedByManyEvent($event, $relation, $ids, $attributes = [], $halt = true) 225 | { 226 | if (!isset(static::$dispatcher)) { 227 | return true; 228 | } 229 | 230 | $parsedIds = AttributesMethods::parseIds($ids); 231 | $parsedIdsForEvent = AttributesMethods::parseIdsForEvent($parsedIds); 232 | $parseAttributesForEvent = AttributesMethods::parseAttributesForEvent($ids, $parsedIds, $attributes); 233 | 234 | $event = 'morphedByMany' . ucfirst($event); 235 | 236 | // First, we will get the proper method to call on the event dispatcher, and then we 237 | // will attempt to fire a custom, object based event for the given event. If that 238 | // returns a result we can return that result, or we'll call the string events. 239 | $method = $halt ? 'until' : 'dispatch'; 240 | 241 | $result = $this->filterModelEventResults( 242 | $this->fireCustomModelEvent($event, $method, $relation, $parsedIdsForEvent, $parseAttributesForEvent), 243 | ); 244 | 245 | if ($result === false) { 246 | return false; 247 | } 248 | 249 | return !empty($result) ? $result : static::$dispatcher->{$method}( 250 | "eloquent.{$event}: " . static::class, 251 | [ 252 | $relation, 253 | $this, 254 | $parsedIdsForEvent, 255 | $parseAttributesForEvent, 256 | ] 257 | ); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/Concerns/HasOneEvents.php: -------------------------------------------------------------------------------- 1 | listen("eloquent.{$event}: {$name}", $callback); 42 | } 43 | } 44 | 45 | /** 46 | * Register a creating model event with the dispatcher. 47 | * 48 | * @param Closure|string $callback 49 | */ 50 | public static function hasOneCreating($callback) 51 | { 52 | static::registerModelHasOneEvent('hasOneCreating', $callback); 53 | } 54 | 55 | /** 56 | * Register a created model event with the dispatcher. 57 | * 58 | * @param Closure|string $callback 59 | */ 60 | public static function hasOneCreated($callback) 61 | { 62 | static::registerModelHasOneEvent('hasOneCreated', $callback); 63 | } 64 | 65 | /** 66 | * Register a saving model event with the dispatcher. 67 | * 68 | * @param Closure|string $callback 69 | */ 70 | public static function hasOneSaving($callback) 71 | { 72 | static::registerModelHasOneEvent('hasOneSaving', $callback); 73 | } 74 | 75 | /** 76 | * Register a saved model event with the dispatcher. 77 | * 78 | * @param Closure|string $callback 79 | */ 80 | public static function hasOneSaved($callback) 81 | { 82 | static::registerModelHasOneEvent('hasOneSaved', $callback); 83 | } 84 | 85 | /** 86 | * Register a updating model event with the dispatcher. 87 | * 88 | * @param Closure|string $callback 89 | */ 90 | public static function hasOneUpdating($callback) 91 | { 92 | static::registerModelHasOneEvent('hasOneUpdating', $callback); 93 | } 94 | 95 | /** 96 | * Register a updated model event with the dispatcher. 97 | * 98 | * @param Closure|string $callback 99 | */ 100 | public static function hasOneUpdated($callback) 101 | { 102 | static::registerModelHasOneEvent('hasOneUpdated', $callback); 103 | } 104 | 105 | /** 106 | * Fire the given event for the model relationship. 107 | * 108 | * @param string $event 109 | * @param mixed $related 110 | * @param bool $halt 111 | * 112 | * @return mixed 113 | */ 114 | public function fireModelHasOneEvent($event, $related = null, $halt = true) 115 | { 116 | if (!isset(static::$dispatcher)) { 117 | return true; 118 | } 119 | 120 | $event = 'hasOne' . ucfirst($event); 121 | 122 | // First, we will get the proper method to call on the event dispatcher, and then we 123 | // will attempt to fire a custom, object based event for the given event. If that 124 | // returns a result we can return that result, or we'll call the string events. 125 | $method = $halt ? 'until' : 'dispatch'; 126 | 127 | $result = $this->filterModelEventResults( 128 | $this->fireCustomModelEvent($event, $method, $related), 129 | ); 130 | 131 | if ($result === false) { 132 | return false; 133 | } 134 | 135 | return !empty($result) ? $result : static::$dispatcher->{$method}( 136 | "eloquent.{$event}: " . static::class, 137 | [ 138 | $this, 139 | $related, 140 | ] 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Contracts/EventDispatcher.php: -------------------------------------------------------------------------------- 1 | getKey()]; 22 | } 23 | 24 | if ($value instanceof Collection) { 25 | return $value->modelKeys(); 26 | } 27 | 28 | if ($value instanceof BaseCollection) { 29 | return $value->toArray(); 30 | } 31 | 32 | return (array) $value; 33 | } 34 | 35 | /** 36 | * Parse ids for event. 37 | */ 38 | public static function parseIdsForEvent(array $ids): array 39 | { 40 | return array_map(fn ($key, $id) => is_array($id) ? $key : $id, array_keys($ids), $ids); 41 | } 42 | 43 | /** 44 | * Parse attributes for event. 45 | * 46 | * @param mixed $rawIds 47 | */ 48 | public static function parseAttributesForEvent($rawIds, array $parsedIds, array $attributes = []): array 49 | { 50 | return is_array($rawIds) ? array_filter($parsedIds, fn ($id) => is_array($id) && !empty($id)) : $attributes; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/MorphMany.php: -------------------------------------------------------------------------------- 1 | fireModelRelationshipEvent('saving', $model); 27 | 28 | $result = parent::save($model); 29 | 30 | if ($result !== false) { 31 | $this->fireModelRelationshipEvent('saved', $result, false); 32 | } 33 | 34 | return $result; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/MorphOne.php: -------------------------------------------------------------------------------- 1 | parent->fireModelMorphToEvent('associating', $this->relationName, $model); 28 | 29 | $result = parent::associate($model); 30 | 31 | $this->parent->fireModelMorphToEvent('associated', $this->relationName, $model); 32 | 33 | return $result; 34 | } 35 | 36 | /** 37 | * Dissociate previously associated model from the given parent. 38 | * 39 | * @return \Illuminate\Database\Eloquent\Model 40 | */ 41 | public function dissociate() 42 | { 43 | $parent = $this->getResults(); 44 | 45 | $this->parent->fireModelMorphToEvent('dissociating', $this->relationName, $parent); 46 | 47 | $result = parent::dissociate(); 48 | 49 | if ($parent !== null) { 50 | $this->parent->fireModelMorphToEvent('dissociated', $this->relationName, $parent); 51 | } 52 | 53 | return $result; 54 | } 55 | 56 | /** 57 | * Update the parent model on the relationship. 58 | * 59 | * @return mixed 60 | */ 61 | public function update(array $attributes) 62 | { 63 | $related = $this->getResults(); 64 | 65 | $this->parent->fireModelMorphToEvent('updating', $this->relationName, $related); 66 | 67 | $result = $related->fill($attributes)->save(); 68 | if ($related && $result) { 69 | $this->parent->fireModelMorphToEvent('updated', $this->relationName, $related); 70 | } 71 | 72 | return $result; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/MorphToMany.php: -------------------------------------------------------------------------------- 1 | parent->fireModelMorphToManyEvent('toggling', $this->getRelationName(), $ids); 31 | 32 | $result = parent::toggle($ids, $touch); 33 | 34 | $this->parent->fireModelMorphToManyEvent('toggled', $this->getRelationName(), $ids, [], false); 35 | 36 | return $result; 37 | } 38 | 39 | /** 40 | * Sync the intermediate tables with a list of IDs or collection of models. 41 | * 42 | * @param array|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection $ids 43 | * @param bool $detaching 44 | * 45 | * @return array 46 | */ 47 | public function sync($ids, $detaching = true) 48 | { 49 | $this->parent->fireModelMorphToManyEvent('syncing', $this->getRelationName(), $ids); 50 | 51 | $result = parent::sync($ids, $detaching); 52 | 53 | $this->parent->fireModelMorphToManyEvent('synced', $this->getRelationName(), $ids, [], false); 54 | 55 | return $result; 56 | } 57 | 58 | /** 59 | * Update an existing pivot record on the table. 60 | * 61 | * @param mixed $id 62 | * @param bool $touch 63 | * 64 | * @return int 65 | */ 66 | public function updateExistingPivot($id, array $attributes, $touch = true) 67 | { 68 | $this->parent->fireModelMorphToManyEvent('updatingExistingPivot', $this->getRelationName(), $id, $attributes); 69 | 70 | if ($result = parent::updateExistingPivot($id, $attributes, $touch)) { 71 | $this->parent->fireModelMorphToManyEvent('updatedExistingPivot', $this->getRelationName(), $id, $attributes, false); 72 | } 73 | 74 | return $result; 75 | } 76 | 77 | /** 78 | * Attach a model to the parent. 79 | * 80 | * @param mixed $id 81 | * @param bool $touch 82 | */ 83 | public function attach($id, array $attributes = [], $touch = true) 84 | { 85 | $this->parent->fireModelMorphToManyEvent('attaching', $this->getRelationName(), $id, $attributes); 86 | 87 | parent::attach($id, $attributes, $touch); 88 | 89 | $this->parent->fireModelMorphToManyEvent('attached', $this->getRelationName(), $id, $attributes, false); 90 | } 91 | 92 | /** 93 | * Detach models from the relationship. 94 | * 95 | * @param mixed $ids 96 | * @param bool $touch 97 | * 98 | * @return int 99 | */ 100 | public function detach($ids = null, $touch = true) 101 | { 102 | // Get detached ids to pass them to event 103 | $ids ??= $this->parent->{$this->getRelationName()}->pluck($this->relatedKey); 104 | 105 | $this->parent->fireModelMorphToManyEvent('detaching', $this->getRelationName(), $ids); 106 | 107 | if ($result = parent::detach($ids, $touch)) { 108 | // If records are detached fire detached event 109 | // Note: detached event will be fired even if one of all records have been detached 110 | $this->parent->fireModelMorphToManyEvent('detached', $this->getRelationName(), $ids, [], false); 111 | } 112 | 113 | return $result; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/MorphedByMany.php: -------------------------------------------------------------------------------- 1 | parent->fireModelMorphedByManyEvent('toggling', $this->getRelationName(), $ids); 31 | 32 | $result = parent::toggle($ids, $touch); 33 | 34 | $this->parent->fireModelMorphedByManyEvent('toggled', $this->getRelationName(), $ids, [], false); 35 | 36 | return $result; 37 | } 38 | 39 | /** 40 | * Sync the intermediate tables with a list of IDs or collection of models. 41 | * 42 | * @param array|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection $ids 43 | * @param bool $detaching 44 | * 45 | * @return array 46 | */ 47 | public function sync($ids, $detaching = true) 48 | { 49 | $this->parent->fireModelMorphedByManyEvent('syncing', $this->getRelationName(), $ids); 50 | 51 | $result = parent::sync($ids, $detaching); 52 | 53 | $this->parent->fireModelMorphedByManyEvent('synced', $this->getRelationName(), $ids, [], false); 54 | 55 | return $result; 56 | } 57 | 58 | /** 59 | * Update an existing pivot record on the table. 60 | * 61 | * @param mixed $id 62 | * @param bool $touch 63 | * 64 | * @return int 65 | */ 66 | public function updateExistingPivot($id, array $attributes, $touch = true) 67 | { 68 | $this->parent->fireModelMorphedByManyEvent('updatingExistingPivot', $this->getRelationName(), $id, $attributes); 69 | 70 | if ($result = parent::updateExistingPivot($id, $attributes, $touch)) { 71 | $this->parent->fireModelMorphedByManyEvent('updatedExistingPivot', $this->getRelationName(), $id, $attributes, false); 72 | } 73 | 74 | return $result; 75 | } 76 | 77 | /** 78 | * Attach a model to the parent. 79 | * 80 | * @param mixed $id 81 | * @param bool $touch 82 | */ 83 | public function attach($id, array $attributes = [], $touch = true) 84 | { 85 | $this->parent->fireModelMorphedByManyEvent('attaching', $this->getRelationName(), $id, $attributes); 86 | 87 | parent::attach($id, $attributes, $touch); 88 | 89 | $this->parent->fireModelMorphedByManyEvent('attached', $this->getRelationName(), $id, $attributes, false); 90 | } 91 | 92 | /** 93 | * Detach models from the relationship. 94 | * 95 | * @param mixed $ids 96 | * @param bool $touch 97 | * 98 | * @return int 99 | */ 100 | public function detach($ids = null, $touch = true) 101 | { 102 | // Get detached ids to pass them to event 103 | $ids ??= $this->parent->{$this->getRelationName()}->pluck($this->relatedKey); 104 | 105 | $this->parent->fireModelMorphedByManyEvent('detaching', $this->getRelationName(), $ids); 106 | 107 | if ($result = parent::detach($ids, $touch)) { 108 | // If records are detached fire detached event 109 | // Note: detached event will be fired even if one of all records have been detached 110 | $this->parent->fireModelMorphedByManyEvent('detached', $this->getRelationName(), $ids, [], false); 111 | } 112 | 113 | return $result; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/RelationshipEventsServiceProvider.php: -------------------------------------------------------------------------------- 1 | app['events']); 18 | BelongsToMany::setEventDispatcher($this->app['events']); 19 | HasMany::setEventDispatcher($this->app['events']); 20 | HasOne::setEventDispatcher($this->app['events']); 21 | MorphedByMany::setEventDispatcher($this->app['events']); 22 | MorphMany::setEventDispatcher($this->app['events']); 23 | MorphOne::setEventDispatcher($this->app['events']); 24 | MorphTo::setEventDispatcher($this->app['events']); 25 | MorphToMany::setEventDispatcher($this->app['events']); 26 | } 27 | 28 | /** 29 | * Register the service provider. 30 | */ 31 | public function register() 32 | { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Traits/HasDispatchableEvents.php: -------------------------------------------------------------------------------- 1 | dispatchesEvents[$event])) { 24 | return; 25 | } 26 | 27 | $result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this, $params)); 28 | 29 | if ($result !== null) { 30 | return $result; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Traits/HasEventDispatcher.php: -------------------------------------------------------------------------------- 1 | related->newInstance($attributes), function (Model $instance) { 23 | $this->fireModelRelationshipEvent('creating', $instance); 24 | 25 | $this->setForeignAttributesForCreate($instance); 26 | 27 | if ($instance->save() !== false) { 28 | $this->fireModelRelationshipEvent('created', $instance, false); 29 | } 30 | }); 31 | } 32 | 33 | /** 34 | * Attach a model instance to the parent model. 35 | * 36 | * @return false|\Illuminate\Database\Eloquent\Model 37 | */ 38 | public function save(Model $model) 39 | { 40 | $this->fireModelRelationshipEvent('saving', $model); 41 | 42 | $result = parent::save($model); 43 | 44 | if ($result !== false) { 45 | $this->fireModelRelationshipEvent('saved', $result, false); 46 | } 47 | 48 | return $result; 49 | } 50 | 51 | /** 52 | * Perform an update on all the related models. 53 | * 54 | * @return int 55 | */ 56 | public function update(array $attributes) 57 | { 58 | $related = $this->getResults(); 59 | 60 | $this->fireModelRelationshipEvent('updating', $related); 61 | 62 | if ($result = parent::update($attributes)) { 63 | if ($related instanceof Model) { 64 | $this->updateRelated($related, $attributes); 65 | } 66 | if ($related instanceof Collection) { 67 | $related->each(function ($model) use ($attributes) { 68 | $this->updateRelated($model, $attributes); 69 | }); 70 | } 71 | 72 | $this->fireModelRelationshipEvent('updated', $related, false); 73 | } 74 | 75 | return $result; 76 | } 77 | 78 | /** 79 | * Fire the given event for the model relationship. 80 | * 81 | * @param string $event 82 | * @param mixed $related 83 | * @param bool $halt 84 | * 85 | * @return mixed 86 | */ 87 | public function fireModelRelationshipEvent($event, $related = null, $halt = true) 88 | { 89 | return $this->parent->{'fireModel' . class_basename(static::class) . 'Event'}($event, $related, $halt); 90 | } 91 | 92 | /** 93 | * Updated related model's attributes. 94 | */ 95 | protected function updateRelated(Model $related, array $attributes): Model 96 | { 97 | return $related->fill($attributes)->syncChanges()->syncOriginal(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Traits/HasRelationshipObservables.php: -------------------------------------------------------------------------------- 1 | filter(fn ($trait) => Str::startsWith($trait, 'Chelout\RelationshipEvents\Concerns'))->flatMap(function ($trait) { 31 | $trait = new ReflectionClass($trait); 32 | $methods = $trait->getMethods(ReflectionMethod::IS_PUBLIC); 33 | 34 | return collect($methods)->filter(fn (ReflectionMethod $method) => $method->isStatic())->map(fn ($method) => $method->name); 35 | })->toArray(); 36 | 37 | static::mergeRelationshipObservables($methods); 38 | } 39 | 40 | /** 41 | * Merge relationship observables. 42 | * 43 | * @return void 44 | */ 45 | public static function mergeRelationshipObservables(array $relationshipObservables) 46 | { 47 | static::$relationshipObservables = array_merge(static::$relationshipObservables, $relationshipObservables); 48 | } 49 | 50 | /** 51 | * Get the observable event names. 52 | * 53 | * @return array 54 | */ 55 | public function getObservableEvents() 56 | { 57 | return array_merge( 58 | [ 59 | 'retrieved', 'creating', 'created', 'updating', 'updated', 60 | 'saving', 'saved', 'restoring', 'restored', 'replicating', 61 | 'deleting', 'deleted', 'forceDeleted', 62 | ], 63 | static::getRelationshipObservables(), 64 | $this->observables, 65 | ); 66 | } 67 | 68 | /** 69 | * Get relationship observables. 70 | */ 71 | public static function getRelationshipObservables(): array 72 | { 73 | return static::$relationshipObservables; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Feature/HasBelongsToEventsTest.php: -------------------------------------------------------------------------------- 1 | user()->associate($user = User::create()); 28 | 29 | Event::assertDispatched( 30 | 'eloquent.belongsToAssociating: ' . Profile::class, 31 | function ($event, $callback) use ($user, $profile) { 32 | return $callback[0] == 'user' && $callback[1]->is($profile) && $callback[2]->is($user); 33 | } 34 | ); 35 | Event::assertDispatched( 36 | 'eloquent.belongsToAssociated: ' . Profile::class, 37 | function ($event, $callback) use ($user, $profile) { 38 | return $callback[0] == 'user' && $callback[1]->is($profile) && $callback[2]->is($user); 39 | } 40 | ); 41 | } 42 | 43 | #[Test] 44 | public function it_fires_belongsToDissociating_and_belongsToDissociated_when_a_model_dissociated(): void 45 | { 46 | Event::fake(); 47 | 48 | $profile = Profile::create(); 49 | $profile->user()->associate($user = User::create()); 50 | $profile->user()->dissociate(); 51 | 52 | Event::assertDispatched( 53 | 'eloquent.belongsToDissociating: ' . Profile::class, 54 | function ($event, $callback) use ($user, $profile) { 55 | return $callback[0] == 'user' && $callback[1]->is($profile) && $callback[2]->is($user); 56 | } 57 | ); 58 | Event::assertDispatched( 59 | 'eloquent.belongsToDissociated: ' . Profile::class, 60 | function ($event, $callback) use ($user, $profile) { 61 | return $callback[0] == 'user' && $callback[1]->is($profile) && $callback[2]->is($user); 62 | } 63 | ); 64 | } 65 | 66 | #[Test] 67 | public function it_fires_belongsToUpdating_and_belongsToUpdated_when_a_parent_model_updated(): void 68 | { 69 | Event::fake(); 70 | 71 | $user = User::create(); 72 | $profile = Profile::create(); 73 | $profile->user()->associate($user); 74 | $profile->user()->update([]); 75 | 76 | Event::assertDispatched( 77 | 'eloquent.belongsToUpdating: ' . Profile::class, 78 | function ($event, $callback) use ($user, $profile) { 79 | return $callback[0] == 'user' && $callback[1]->is($profile) && $callback[2]->is($user); 80 | } 81 | ); 82 | Event::assertDispatched( 83 | 'eloquent.belongsToUpdated: ' . Profile::class, 84 | function ($event, $callback) use ($user, $profile) { 85 | return $callback[0] == 'user' && $callback[1]->is($profile) && $callback[2]->is($user); 86 | } 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/Feature/HasBelongsToManyEventsTest.php: -------------------------------------------------------------------------------- 1 | 'admin']); 28 | $user->roles()->attach($role); 29 | 30 | Event::assertDispatched( 31 | 'eloquent.belongsToManyAttaching: ' . User::class, 32 | function ($event, $callback) use ($user, $role) { 33 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 34 | } 35 | ); 36 | Event::assertDispatched( 37 | 'eloquent.belongsToManyAttached: ' . User::class, 38 | function ($event, $callback) use ($user, $role) { 39 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 40 | } 41 | ); 42 | } 43 | 44 | #[Test] 45 | public function it_fires_belongsToManyDetaching_and_belongsToManyDetached_when_a_model_detached(): void 46 | { 47 | Event::fake(); 48 | 49 | $user = User::create(); 50 | $role = Role::create(['name' => 'admin']); 51 | $user->roles()->attach($role); 52 | $user->roles()->detach($role); 53 | 54 | Event::assertDispatched( 55 | 'eloquent.belongsToManyDetaching: ' . User::class, 56 | function ($event, $callback) use ($user, $role) { 57 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 58 | } 59 | ); 60 | Event::assertDispatched( 61 | 'eloquent.belongsToManyDetached: ' . User::class, 62 | function ($event, $callback) use ($user, $role) { 63 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 64 | } 65 | ); 66 | } 67 | 68 | #[Test] 69 | public function it_fires_belongsToManySyncing_and_belongsToManySynced_when_a_model_synced(): void 70 | { 71 | Event::fake(); 72 | 73 | $user = User::create(); 74 | $role = Role::create(['name' => 'admin']); 75 | $user->roles()->sync($role); 76 | 77 | Event::assertDispatched( 78 | 'eloquent.belongsToManySyncing: ' . User::class, 79 | function ($event, $callback) use ($user, $role) { 80 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 81 | } 82 | ); 83 | Event::assertDispatched( 84 | 'eloquent.belongsToManySynced: ' . User::class, 85 | function ($event, $callback) use ($user, $role) { 86 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 87 | } 88 | ); 89 | } 90 | 91 | #[Test] 92 | public function it_fires_belongsToManyToggling_and_belongsToManyToggled_when_a_model_toggled(): void 93 | { 94 | Event::fake(); 95 | 96 | $user = User::create(); 97 | $role = Role::create(['name' => 'admin']); 98 | $user->roles()->toggle($role); 99 | 100 | Event::assertDispatched( 101 | 'eloquent.belongsToManyToggling: ' . User::class, 102 | function ($event, $callback) use ($user, $role) { 103 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 104 | } 105 | ); 106 | Event::assertDispatched( 107 | 'eloquent.belongsToManyToggled: ' . User::class, 108 | function ($event, $callback) use ($user, $role) { 109 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 110 | } 111 | ); 112 | } 113 | 114 | #[Test] 115 | public function it_fires_belongsToManyUpdatingExistingPivot_and_belongsToManyUpdatedExistingPivot_when_updaing_pivot_table(): void 116 | { 117 | Event::fake(); 118 | 119 | $user = User::create(); 120 | $role = Role::create(['name' => 'admin']); 121 | $user->roles()->attach($role); 122 | $user->roles()->updateExistingPivot(1, ['note' => 'bla bla']); 123 | 124 | Event::assertDispatched( 125 | 'eloquent.belongsToManyUpdatingExistingPivot: ' . User::class, 126 | function ($event, $callback) use ($user, $role) { 127 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 128 | } 129 | ); 130 | Event::assertDispatched( 131 | 'eloquent.belongsToManyUpdatedExistingPivot: ' . User::class, 132 | function ($event, $callback) use ($user, $role) { 133 | return $callback[0] == 'roles' && $callback[1]->is($user) && $callback[2][0] == $role->id; 134 | } 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /tests/Feature/HasManyEventsTest.php: -------------------------------------------------------------------------------- 1 | posts()->create([]); 28 | 29 | Event::assertDispatched( 30 | 'eloquent.hasManyCreating: ' . User::class, 31 | function ($event, $callback) use ($user, $post) { 32 | return $callback[0]->is($user) && $callback[1]->is($post); 33 | } 34 | ); 35 | Event::assertDispatched( 36 | 'eloquent.hasManyCreated: ' . User::class, 37 | function ($event, $callback) use ($user, $post) { 38 | return $callback[0]->is($user) && $callback[1]->is($post); 39 | } 40 | ); 41 | } 42 | 43 | #[Test] 44 | public function it_fires_hasManySaving_and_hasManySaved_when_belonged_model_with_many_saved(): void 45 | { 46 | Event::fake(); 47 | 48 | $user = User::create(); 49 | $post = $user->posts()->save(new Post); 50 | 51 | Event::assertDispatched( 52 | 'eloquent.hasManySaving: ' . User::class, 53 | function ($event, $callback) use ($user, $post) { 54 | return $callback[0]->is($user) && $callback[1]->is($post); 55 | } 56 | ); 57 | Event::assertDispatched( 58 | 'eloquent.hasManySaved: ' . User::class, 59 | function ($event, $callback) use ($user, $post) { 60 | return $callback[0]->is($user) && $callback[1]->is($post); 61 | } 62 | ); 63 | } 64 | 65 | #[Test] 66 | public function it_fires_hasManyUpdating_and_hasManyUpdated_when_belonged_model_with_many_updated(): void 67 | { 68 | Event::fake(); 69 | 70 | $user = User::create(); 71 | $post = $user->posts()->create([]); 72 | $user->posts()->update([]); 73 | 74 | Event::assertDispatched( 75 | 'eloquent.hasManyUpdating: ' . User::class, 76 | function ($event, $callback) use ($user, $post) { 77 | return $callback[0]->is($user) && $callback[1][0]->is($post); 78 | } 79 | ); 80 | Event::assertDispatched( 81 | 'eloquent.hasManyUpdated: ' . User::class, 82 | function ($event, $callback) use ($user, $post) { 83 | return $callback[0]->is($user) && $callback[1][0]->is($post); 84 | } 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Feature/HasMorphManyEventsTest.php: -------------------------------------------------------------------------------- 1 | 1]); 27 | $comment = $post->comments()->create([]); 28 | 29 | Event::assertDispatched( 30 | 'eloquent.morphManyCreating: ' . Post::class, 31 | function ($event, $callback) use ($post, $comment) { 32 | return $callback[0]->is($post) && $callback[1]->is($comment); 33 | } 34 | ); 35 | Event::assertDispatched( 36 | 'eloquent.morphManyCreated: ' . Post::class, 37 | function ($event, $callback) use ($post, $comment) { 38 | return $callback[0]->is($post) && $callback[1]->is($comment); 39 | } 40 | ); 41 | } 42 | 43 | #[Test] 44 | public function it_fires_morphManySaving_and_morphManySaved_when_belonged_model_with_morph_many_saved(): void 45 | { 46 | Event::fake(); 47 | 48 | $post = Post::create(['user_id' => 1]); 49 | $comment = $post->comments()->save(new Comment); 50 | 51 | Event::assertDispatched( 52 | 'eloquent.morphManySaving: ' . Post::class, 53 | function ($event, $callback) use ($post, $comment) { 54 | return $callback[0]->is($post) && $callback[1]->is($comment); 55 | } 56 | ); 57 | Event::assertDispatched( 58 | 'eloquent.morphManySaved: ' . Post::class, 59 | function ($event, $callback) use ($post, $comment) { 60 | return $callback[0]->is($post) && $callback[1]->is($comment); 61 | } 62 | ); 63 | } 64 | 65 | #[Test] 66 | public function it_fires_morphManyUpdating_and_morphManyUpdated_when_belonged_model_with_morph_many_updated(): void 67 | { 68 | Event::fake(); 69 | 70 | $post = Post::create(['user_id' => 1]); 71 | $comment = $post->comments()->save(new Comment); 72 | $post->comments()->update([]); 73 | 74 | Event::assertDispatched( 75 | 'eloquent.morphManyUpdating: ' . Post::class, 76 | function ($event, $callback) use ($post, $comment) { 77 | return $callback[0]->is($post); 78 | } 79 | ); 80 | Event::assertDispatched( 81 | 'eloquent.morphManyUpdated: ' . Post::class, 82 | function ($event, $callback) use ($post, $comment) { 83 | return $callback[0]->is($post) && $callback[1][0]->is($comment); 84 | } 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Feature/HasMorphOneEventsTest.php: -------------------------------------------------------------------------------- 1 | address()->create([]); 28 | 29 | Event::assertDispatched( 30 | 'eloquent.morphOneCreating: ' . User::class, 31 | function ($event, $callback) use ($user, $address) { 32 | return $callback[0]->is($user) && $callback[1]->is($address); 33 | } 34 | ); 35 | Event::assertDispatched( 36 | 'eloquent.morphOneCreated: ' . User::class, 37 | function ($event, $callback) use ($user, $address) { 38 | return $callback[0]->is($user) && $callback[1]->is($address); 39 | } 40 | ); 41 | } 42 | 43 | #[Test] 44 | public function it_fires_morphOneSaving_and_morphOneSaved_when_belonged_model_with_morph_one_saved(): void 45 | { 46 | Event::fake(); 47 | 48 | $user = User::create(); 49 | $address = $user->address()->save(new Address); 50 | 51 | Event::assertDispatched( 52 | 'eloquent.morphOneSaving: ' . User::class, 53 | function ($event, $callback) use ($user, $address) { 54 | return $callback[0]->is($user) && $callback[1]->is($address); 55 | } 56 | ); 57 | Event::assertDispatched( 58 | 'eloquent.morphOneSaved: ' . User::class, 59 | function ($event, $callback) use ($user, $address) { 60 | return $callback[0]->is($user) && $callback[1]->is($address); 61 | } 62 | ); 63 | } 64 | 65 | #[Test] 66 | public function it_fires_morphOneUpdating_and_morphOneUpdated_when_belonged_model_with_morph_one_updated(): void 67 | { 68 | Event::fake(); 69 | 70 | $user = User::create(); 71 | $address = $user->address()->save(new Address); 72 | $user->address()->update([]); 73 | 74 | Event::assertDispatched( 75 | 'eloquent.morphOneUpdating: ' . User::class, 76 | function ($event, $callback) use ($user, $address) { 77 | return $callback[0]->is($user) && $callback[1]->is($address); 78 | } 79 | ); 80 | Event::assertDispatched( 81 | 'eloquent.morphOneUpdated: ' . User::class, 82 | function ($event, $callback) use ($user, $address) { 83 | return $callback[0]->is($user) && $callback[1]->is($address); 84 | } 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Feature/HasMorphToEventsTest.php: -------------------------------------------------------------------------------- 1 | post()->associate($post); 29 | 30 | Event::assertDispatched( 31 | 'eloquent.morphToAssociating: ' . Comment::class, 32 | function ($event, $callback) use ($post, $comment) { 33 | return $callback[0] == 'Chelout\RelationshipEvents\Tests\Stubs\Post' && $callback[1]->is($comment) && $callback[2]->is($post); 34 | } 35 | ); 36 | Event::assertDispatched( 37 | 'eloquent.morphToAssociated: ' . Comment::class, 38 | function ($event, $callback) use ($post, $comment) { 39 | return $callback[0] == 'Chelout\RelationshipEvents\Tests\Stubs\Post' && $callback[1]->is($comment) && $callback[2]->is($post); 40 | } 41 | ); 42 | } 43 | 44 | #[Test] 45 | public function it_fires_morphToDissociating_and_morphToDissociated(): void 46 | { 47 | Event::fake(); 48 | 49 | $post = Post::create(); 50 | $comment = Comment::create(); 51 | $comment->post()->associate($post); 52 | $comment->post()->dissociate($post); 53 | 54 | Event::assertDispatched( 55 | 'eloquent.morphToDissociating: ' . Comment::class, 56 | function ($event, $callback) use ($post, $comment) { 57 | return $callback[0] == 'Chelout\RelationshipEvents\Tests\Stubs\Post' && $callback[1]->is($comment) && $callback[2]->is($post); 58 | } 59 | ); 60 | Event::assertDispatched( 61 | 'eloquent.morphToDissociated: ' . Comment::class, 62 | function ($event, $callback) use ($post, $comment) { 63 | return $callback[0] == 'Chelout\RelationshipEvents\Tests\Stubs\Post' && $callback[1]->is($comment) && $callback[2]->is($post); 64 | } 65 | ); 66 | } 67 | 68 | #[Test] 69 | public function it_fires_morphToUpdating_and_morphToUpdated(): void 70 | { 71 | Event::fake(); 72 | 73 | $post = Post::create(); 74 | $comment = Comment::create(); 75 | $comment->post()->associate($post); 76 | $comment->post()->update([]); 77 | 78 | Event::assertDispatched( 79 | 'eloquent.morphToUpdating: ' . Comment::class, 80 | function ($event, $callback) use ($post, $comment) { 81 | return $callback[0] == 'Chelout\RelationshipEvents\Tests\Stubs\Post' && $callback[1]->is($comment) && $callback[2]->is($post); 82 | } 83 | ); 84 | Event::assertDispatched( 85 | 'eloquent.morphToUpdated: ' . Comment::class, 86 | function ($event, $callback) use ($post, $comment) { 87 | return $callback[0] == 'Chelout\RelationshipEvents\Tests\Stubs\Post' && $callback[1]->is($comment) && $callback[2]->is($post); 88 | } 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/Feature/HasMorphToManyEventsTest.php: -------------------------------------------------------------------------------- 1 | tags()->attach($tag); 29 | 30 | Event::assertDispatched( 31 | 'eloquent.morphToManyAttaching: ' . Post::class, 32 | function ($event, $callback) use ($post, $tag) { 33 | return $callback[0] == 'tags' && $callback[1]->is($post) && $callback[2][0] == $tag->id; 34 | } 35 | ); 36 | Event::assertDispatched( 37 | 'eloquent.morphToManyAttached: ' . Post::class, 38 | function ($event, $callback) use ($post, $tag) { 39 | return $callback[0] == 'tags' && $callback[1]->is($post) && $callback[2][0] == $tag->id; 40 | } 41 | ); 42 | } 43 | 44 | #[Test] 45 | public function it_fires_morphToManyDetaching_and_morphToManyDetached(): void 46 | { 47 | Event::fake(); 48 | 49 | $post = Post::create(); 50 | $tag = Tag::create(); 51 | $post->tags()->attach($tag); 52 | $post->tags()->detach($tag); 53 | 54 | Event::assertDispatched( 55 | 'eloquent.morphToManyDetaching: ' . Post::class, 56 | function ($event, $callback) use ($post, $tag) { 57 | return $callback[0] == 'tags' && $callback[1]->is($post) && $callback[2][0] == $tag->id; 58 | } 59 | ); 60 | Event::assertDispatched( 61 | 'eloquent.morphToManyDetached: ' . Post::class, 62 | function ($event, $callback) use ($post, $tag) { 63 | return $callback[0] == 'tags' && $callback[1]->is($post) && $callback[2][0] == $tag->id; 64 | } 65 | ); 66 | } 67 | 68 | #[Test] 69 | public function it_fires_morphToManySyncing_and_morphToManySynced(): void 70 | { 71 | Event::fake(); 72 | 73 | $post = Post::create(); 74 | $tag = Tag::create(); 75 | $post->tags()->sync($tag); 76 | 77 | Event::assertDispatched( 78 | 'eloquent.morphToManySyncing: ' . Post::class, 79 | function ($event, $callback) use ($post, $tag) { 80 | return $callback[0] == 'tags' && $callback[1]->is($post) && $callback[2][0] == $tag->id; 81 | } 82 | ); 83 | Event::assertDispatched( 84 | 'eloquent.morphToManySynced: ' . Post::class, 85 | function ($event, $callback) use ($post, $tag) { 86 | return $callback[0] == 'tags' && $callback[1]->is($post) && $callback[2][0] == $tag->id; 87 | } 88 | ); 89 | } 90 | 91 | #[Test] 92 | public function it_fires_morphToManyUpdatingExistingPivot_and_morphToManyUpdatedExistingPivot(): void 93 | { 94 | Event::fake(); 95 | 96 | $post = Post::create(); 97 | $tag = Tag::create(); 98 | $post->tags()->sync($tag); 99 | $post->tags()->updateExistingPivot(1, ['created_at' => now()]); 100 | 101 | Event::assertDispatched( 102 | 'eloquent.morphToManyUpdatingExistingPivot: ' . Post::class, 103 | function ($event, $callback) use ($post, $tag) { 104 | return $callback[0] == 'tags' && $callback[1]->is($post) && $callback[2][0] == $tag->id; 105 | } 106 | ); 107 | Event::assertDispatched( 108 | 'eloquent.morphToManyUpdatedExistingPivot: ' . Post::class, 109 | function ($event, $callback) use ($post, $tag) { 110 | return $callback[0] == 'tags' && $callback[1]->is($post) && $callback[2][0] == $tag->id; 111 | } 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/Feature/HasMorphedByManyEventsTest.php: -------------------------------------------------------------------------------- 1 | posts()->create([]); 28 | 29 | Event::assertDispatched( 30 | 'eloquent.morphedByManyAttaching: ' . Tag::class, 31 | function ($event, $callback) use ($post, $tag) { 32 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 33 | } 34 | ); 35 | Event::assertDispatched( 36 | 'eloquent.morphedByManyAttached: ' . Tag::class, 37 | function ($event, $callback) use ($post, $tag) { 38 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 39 | } 40 | ); 41 | } 42 | 43 | #[Test] 44 | public function it_fires_morphedByManyAttaching_and_morphedByManyAttached_when_saved(): void 45 | { 46 | Event::fake(); 47 | 48 | $tag = Tag::create(); 49 | $post = $tag->posts()->save(new Post); 50 | 51 | Event::assertDispatched( 52 | 'eloquent.morphedByManyAttaching: ' . Tag::class, 53 | function ($event, $callback) use ($post, $tag) { 54 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 55 | } 56 | ); 57 | Event::assertDispatched( 58 | 'eloquent.morphedByManyAttached: ' . Tag::class, 59 | function ($event, $callback) use ($post, $tag) { 60 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 61 | } 62 | ); 63 | } 64 | 65 | #[Test] 66 | public function it_fires_morphedByManyAttaching_and_morphedByManyAttached_when_attached(): void 67 | { 68 | Event::fake(); 69 | 70 | $post = Post::create(); 71 | $tag = Tag::create(); 72 | $tag->posts()->attach($post); 73 | 74 | Event::assertDispatched( 75 | 'eloquent.morphedByManyAttaching: ' . Tag::class, 76 | function ($event, $callback) use ($post, $tag) { 77 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 78 | } 79 | ); 80 | Event::assertDispatched( 81 | 'eloquent.morphedByManyAttached: ' . Tag::class, 82 | function ($event, $callback) use ($post, $tag) { 83 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 84 | } 85 | ); 86 | } 87 | 88 | #[Test] 89 | public function it_fires_morphedByManyDetaching_and_morphedByManyDetached_when_detached(): void 90 | { 91 | Event::fake(); 92 | 93 | $post = Post::create(); 94 | $tag = Tag::create(); 95 | $tag->posts()->attach($post); 96 | $tag->posts()->detach($post); 97 | 98 | Event::assertDispatched( 99 | 'eloquent.morphedByManyDetaching: ' . Tag::class, 100 | function ($event, $callback) use ($post, $tag) { 101 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 102 | } 103 | ); 104 | Event::assertDispatched( 105 | 'eloquent.morphedByManyDetached: ' . Tag::class, 106 | function ($event, $callback) use ($post, $tag) { 107 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 108 | } 109 | ); 110 | } 111 | 112 | #[Test] 113 | public function it_fires_morphedByManySyncing_and_morphedByManySynced(): void 114 | { 115 | Event::fake(); 116 | 117 | $post = Post::create(); 118 | $tag = Tag::create(); 119 | $tag->posts()->sync($post); 120 | 121 | Event::assertDispatched( 122 | 'eloquent.morphedByManySyncing: ' . Tag::class, 123 | function ($event, $callback) use ($post, $tag) { 124 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 125 | } 126 | ); 127 | Event::assertDispatched( 128 | 'eloquent.morphedByManySynced: ' . Tag::class, 129 | function ($event, $callback) use ($post, $tag) { 130 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 131 | } 132 | ); 133 | } 134 | 135 | #[Test] 136 | public function it_fires_morphedByManyToggling_and_morphedByManyToggled(): void 137 | { 138 | Event::fake(); 139 | 140 | $post = Post::create(); 141 | $tag = Tag::create(); 142 | $tag->posts()->toggle($post); 143 | 144 | Event::assertDispatched( 145 | 'eloquent.morphedByManyToggling: ' . Tag::class, 146 | function ($event, $callback) use ($post, $tag) { 147 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 148 | } 149 | ); 150 | Event::assertDispatched( 151 | 'eloquent.morphedByManyToggled: ' . Tag::class, 152 | function ($event, $callback) use ($post, $tag) { 153 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 154 | } 155 | ); 156 | } 157 | 158 | #[Test] 159 | public function it_fires_morphedByManyUpdatingExistingPivot_and_morphedByManyUpdatedExistingPivot(): void 160 | { 161 | Event::fake(); 162 | 163 | $post = Post::create(); 164 | $tag = Tag::create(); 165 | $tag->posts()->attach($post); 166 | $tag->posts()->updateExistingPivot(1, ['created_at' => now()]); 167 | 168 | Event::assertDispatched( 169 | 'eloquent.morphedByManyUpdatingExistingPivot: ' . Tag::class, 170 | function ($event, $callback) use ($post, $tag) { 171 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 172 | } 173 | ); 174 | Event::assertDispatched( 175 | 'eloquent.morphedByManyUpdatedExistingPivot: ' . Tag::class, 176 | function ($event, $callback) use ($post, $tag) { 177 | return $callback[0] == 'posts' && $callback[1]->is($tag) && $callback[2][0] == $post->id; 178 | } 179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /tests/Feature/HasOneEventsTest.php: -------------------------------------------------------------------------------- 1 | profile()->create([]); 28 | 29 | Event::assertDispatched( 30 | 'eloquent.hasOneCreating: ' . User::class, 31 | function ($event, $callback) use ($user, $profile) { 32 | return $callback[0]->is($user) && $callback[1]->is($profile); 33 | } 34 | ); 35 | Event::assertDispatched( 36 | 'eloquent.hasOneCreated: ' . User::class, 37 | function ($event, $callback) use ($user, $profile) { 38 | return $callback[0]->is($user) && $callback[1]->is($profile); 39 | } 40 | ); 41 | } 42 | 43 | #[Test] 44 | public function it_fires_hasOneSaving_and_hasOneSaved_when_a_belonged_model_saved(): void 45 | { 46 | Event::fake(); 47 | 48 | $user = User::create(); 49 | $profile = $user->profile()->save(new Profile); 50 | 51 | Event::assertDispatched( 52 | 'eloquent.hasOneSaving: ' . User::class, 53 | function ($event, $callback) use ($user, $profile) { 54 | return $callback[0]->is($user) && $callback[1]->is($profile); 55 | } 56 | ); 57 | Event::assertDispatched( 58 | 'eloquent.hasOneSaved: ' . User::class, 59 | function ($event, $callback) use ($user, $profile) { 60 | return $callback[0]->is($user) && $callback[1]->is($profile); 61 | } 62 | ); 63 | } 64 | 65 | #[Test] 66 | public function it_fires_hasOneUpdating_and_hasOneUpdated_when_a_belonged_model_updated(): void 67 | { 68 | Event::fake(); 69 | 70 | $user = User::create(); 71 | $profile = $user->profile()->save(new Profile); 72 | $user->profile()->update([]); 73 | 74 | Event::assertDispatched( 75 | 'eloquent.hasOneUpdating: ' . User::class, 76 | function ($event, $callback) use ($user, $profile) { 77 | return $callback[0]->is($user) && $callback[1]->is($profile); 78 | } 79 | ); 80 | Event::assertDispatched( 81 | 'eloquent.hasOneUpdated: ' . User::class, 82 | function ($event, $callback) use ($user, $profile) { 83 | return $callback[0]->is($user) && $callback[1]->is($profile); 84 | } 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Stubs/Address.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('addressable_id'); 18 | $table->string('addressable_type'); 19 | $table->timestamps(); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Stubs/Comment.php: -------------------------------------------------------------------------------- 1 | increments('id'); 20 | $table->string('commentable_id')->nullable(); 21 | $table->string('commentable_type')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | public function post() 27 | { 28 | return $this->morphTo(Post::class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Stubs/Post.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->unsignedInteger('user_id')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | public function comments() 28 | { 29 | return $this->morphMany(Comment::class, 'commentable'); 30 | } 31 | 32 | public function tags() 33 | { 34 | return $this->morphToMany(Tag::class, 'taggable'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Stubs/Profile.php: -------------------------------------------------------------------------------- 1 | increments('id'); 20 | $table->unsignedInteger('user_id')->nullable(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | public function user() 26 | { 27 | return $this->belongsTo(User::class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Stubs/Role.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name'); 18 | $table->timestamps(); 19 | }); 20 | 21 | Schema::create('role_user', function (Blueprint $table) { 22 | $table->increments('id'); 23 | $table->unsignedInteger('role_id'); 24 | $table->unsignedInteger('user_id'); 25 | $table->string('note')->nullable(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Stubs/Tag.php: -------------------------------------------------------------------------------- 1 | increments('id'); 20 | $table->timestamps(); 21 | }); 22 | 23 | Schema::create('taggables', function (Blueprint $table) { 24 | $table->increments('id'); 25 | $table->unsignedInteger('tag_id'); 26 | $table->unsignedInteger('taggable_id'); 27 | $table->string('taggable_type'); 28 | $table->timestamps(); 29 | }); 30 | } 31 | 32 | public function posts() 33 | { 34 | return $this->morphedByMany(Post::class, 'taggable'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Stubs/User.php: -------------------------------------------------------------------------------- 1 | increments('id'); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | public function profile() 29 | { 30 | return $this->hasOne(Profile::class); 31 | } 32 | 33 | public function roles() 34 | { 35 | return $this->belongsToMany(Role::class, 'role_user'); 36 | } 37 | 38 | public function posts() 39 | { 40 | return $this->hasMany(Post::class); 41 | } 42 | 43 | public function address() 44 | { 45 | return $this->morphOne(Address::class, 'addressable'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('database.default', 'testbench'); 31 | $app['config']->set('database.connections.testbench', [ 32 | 'driver' => 'sqlite', 33 | 'database' => ':memory:', 34 | 'prefix' => '', 35 | ]); 36 | } 37 | } 38 | --------------------------------------------------------------------------------