diff --git a/src/Database/Relations/AttachOneOrMany.php b/src/Database/Relations/AttachOneOrMany.php index 7ab21a28b..7bb8234d1 100644 --- a/src/Database/Relations/AttachOneOrMany.php +++ b/src/Database/Relations/AttachOneOrMany.php @@ -166,6 +166,21 @@ public function add(Model $model, $sessionKey = null) } if ($sessionKey === null) { + /** + * @event model.relation.beforeAdd + * Called before adding a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeAdd', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'dummyRelation') { + * throw new \Exception("Invalid relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeAdd', [$this->relationName, $model]); + // Delete siblings for single attachments if ($this instanceof AttachOne) { $this->delete(); @@ -185,6 +200,21 @@ public function add(Model $model, $sessionKey = null) else { $this->parent->reloadRelations($this->relationName); } + + /** + * @event model.relation.afterAdd + * Called after adding a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterAdd', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * $relatedClass = get_class($relatedModel); + * $modelClass = get_class($model); + * traceLog("{$relatedClass} was added as {$relationName} to {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterAdd', [$this->relationName, $model]); } else { $this->parent->bindDeferred($this->relationName, $model, $sessionKey); @@ -209,6 +239,21 @@ public function addMany($models, $sessionKey = null) public function remove(Model $model, $sessionKey = null) { if ($sessionKey === null) { + /** + * @event model.relation.beforeRemove + * Called before removing a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeRemove', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'permanentRelation') { + * throw new \Exception("Cannot dissociate a permanent relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeRemove', [$this->relationName, $model]); + $options = $this->parent->getRelationDefinition($this->relationName); if (array_get($options, 'delete', false)) { @@ -233,6 +278,21 @@ public function remove(Model $model, $sessionKey = null) else { $this->parent->reloadRelations($this->relationName); } + + /** + * @event model.relation.afterRemove + * Called after removing a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterRemove', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * $relatedClass = get_class($relatedModel); + * $modelClass = get_class($model); + * traceLog("{$relatedClass} was removed from {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterRemove', [$this->relationName, $model]); } else { $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); diff --git a/src/Database/Relations/BelongsTo.php b/src/Database/Relations/BelongsTo.php index afd3403a5..4983931cb 100644 --- a/src/Database/Relations/BelongsTo.php +++ b/src/Database/Relations/BelongsTo.php @@ -49,6 +49,90 @@ public function remove(Model $model, $sessionKey = null) } } + /** + * Associate the model instance to the given parent. + * + * @param \Illuminate\Database\Eloquent\Model|int|string $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function associate($model) + { + /** + * @event model.relation.beforeAssociate + * Called before associating a relation to the model (only for BelongsTo/MorphTo relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeAssociate', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'dummyRelation') { + * throw new \Exception("Invalid relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeAssociate', [$this->relationName, $model]); + + $result = parent::associate($model); + + /** + * @event model.relation.afterAssociate + * Called after associating a relation to the model (only for BelongsTo/MorphTo relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterAssociate', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * $relatedClass = get_class($relatedModel); + * $modelClass = get_class($model); + * traceLog("{$relatedClass} was associated as {$relationName} to {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterAssociate', [$this->relationName, $model]); + + return $result; + } + + /** + * Dissociate previously dissociated model from the given parent. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function dissociate() + { + /** + * @event model.relation.beforeDissociate + * Called before dissociating a relation to the model (only for BelongsTo/MorphTo relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeDissociate', function (string $relationName) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'permanentRelation') { + * throw new \Exception("Cannot dissociate a permanent relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeDissociate', [$this->relationName]); + + $result = parent::dissociate(); + + /** + * @event model.relation.afterDissociate + * Called after dissociating a relation to the model (only for BelongsTo/MorphTo relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterDissociate', function (string $relationName) use (\October\Rain\Database\Model $model) { + * $modelClass = get_class($model); + * traceLog("{$relationName} was dissociated from {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterDissociate', [$this->relationName]); + + return $result; + } + /** * Helper for setting this relationship using various expected * values. For example, $model->relation = $value; diff --git a/src/Database/Relations/BelongsToMany.php b/src/Database/Relations/BelongsToMany.php index 29863be9c..475e827cc 100644 --- a/src/Database/Relations/BelongsToMany.php +++ b/src/Database/Relations/BelongsToMany.php @@ -115,9 +115,11 @@ public function create(array $attributes = [], array $pivotData = [], $sessionKe /** * Override attach() method of BelongToMany relation. * This is necessary in order to fire 'model.relation.beforeAttach', 'model.relation.afterAttach' events + * * @param mixed $id * @param array $attributes * @param bool $touch + * @return void */ public function attach($id, array $attributes = [], $touch = true) { @@ -133,17 +135,14 @@ public function attach($id, array $attributes = [], $touch = true) * $model->bindEvent('model.relation.beforeAttach', function (string $relationName, array $attachedIdList, array $insertData) use (\October\Rain\Database\Model $model) { * if (!$model->isRelationValid($attachedIdList)) { * throw new \Exception("Invalid relation!"); - * return false; * } * }); * */ - if ($this->parent->fireEvent('model.relation.beforeAttach', [$this->relationName, $attachedIdList, $insertData], true) === false) { - return; - } + $this->parent->fireEvent('model.relation.beforeAttach', [$this->relationName, $attachedIdList, $insertData]); /** - * @see Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable + * @see \Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable */ parent::attach($id, $attributes, $touch); @@ -164,9 +163,10 @@ public function attach($id, array $attributes = [], $touch = true) /** * Override detach() method of BelongToMany relation. * This is necessary in order to fire 'model.relation.beforeDetach', 'model.relation.afterDetach' events + * * @param null $ids * @param bool $touch - * @return int|void + * @return int */ public function detach($ids = null, $touch = true) { @@ -189,14 +189,13 @@ public function detach($ids = null, $touch = true) * }); * */ - if ($this->parent->fireEvent('model.relation.beforeDetach', [$this->relationName, $attachedIdList], true) === false) { - return; - } + $this->parent->fireEvent('model.relation.beforeDetach', [$this->relationName, $attachedIdList]); /** * @see Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable */ - parent::detach($attachedIdList, $touch); + + $result = parent::detach($ids, $touch); /** * @event model.relation.afterDetach @@ -204,12 +203,14 @@ public function detach($ids = null, $touch = true) * * Example usage: * - * $model->bindEvent('model.relation.afterDetach', function (string $relationName, array $attachedIdList) use (\October\Rain\Database\Model $model) { - * traceLog("Relation {$relationName} was removed", $attachedIdList); + * $model->bindEvent('model.relation.afterDetach', function (string $relationName, array $attachedIdList, int $result) use (\October\Rain\Database\Model $model) { + * traceLog("{$result} entries were detached for Relation {$relationName}"); * }); * */ - $this->parent->fireEvent('model.relation.afterDetach', [$this->relationName, $attachedIdList]); + $this->parent->fireEvent('model.relation.afterDetach', [$this->relationName, $attachedIdList, $result]); + + return $result; } /** diff --git a/src/Database/Relations/HasOneOrMany.php b/src/Database/Relations/HasOneOrMany.php index 7d2325257..fd3283168 100644 --- a/src/Database/Relations/HasOneOrMany.php +++ b/src/Database/Relations/HasOneOrMany.php @@ -56,6 +56,21 @@ public function create(array $attributes = [], $sessionKey = null) public function add(Model $model, $sessionKey = null) { if ($sessionKey === null) { + /** + * @event model.relation.beforeAdd + * Called before adding a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeAdd', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'dummyRelation') { + * throw new \Exception("Invalid relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeAdd', [$this->relationName, $model]); + $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); if (!$model->exists || $model->isDirty()) { @@ -71,6 +86,21 @@ public function add(Model $model, $sessionKey = null) else { $this->parent->reloadRelations($this->relationName); } + + /** + * @event model.relation.afterAdd + * Called after adding a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterAdd', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * $relatedClass = get_class($relatedModel); + * $modelClass = get_class($model); + * traceLog("{$relatedClass} was added as {$relationName} to {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterAdd', [$this->relationName, $model]); } else { $this->parent->bindDeferred($this->relationName, $model, $sessionKey); @@ -95,6 +125,21 @@ public function addMany($models, $sessionKey = null) public function remove(Model $model, $sessionKey = null) { if ($sessionKey === null) { + /** + * @event model.relation.beforeRemove + * Called before removing a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeRemove', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'permanentRelation') { + * throw new \Exception("Cannot dissociate a permanent relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeRemove', [$this->relationName, $model]); + $model->setAttribute($this->getForeignKeyName(), null); $model->save(); @@ -107,6 +152,20 @@ public function remove(Model $model, $sessionKey = null) else { $this->parent->reloadRelations($this->relationName); } + /** + * @event model.relation.afterRemove + * Called after removing a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterRemove', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * $relatedClass = get_class($relatedModel); + * $modelClass = get_class($model); + * traceLog("{$relatedClass} was removed from {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterRemove', [$this->relationName, $model]); } else { $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); diff --git a/src/Database/Relations/MorphOneOrMany.php b/src/Database/Relations/MorphOneOrMany.php index 552b1a1d9..26f763581 100644 --- a/src/Database/Relations/MorphOneOrMany.php +++ b/src/Database/Relations/MorphOneOrMany.php @@ -45,6 +45,21 @@ public function create(array $attributes = [], $sessionKey = null) public function add(Model $model, $sessionKey = null) { if ($sessionKey === null) { + /** + * @event model.relation.beforeAdd + * Called before adding a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeAdd', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'dummyRelation') { + * throw new \Exception("Invalid relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeAdd', [$this->relationName, $model]); + $model->setAttribute($this->getForeignKeyName(), $this->getParentKey()); $model->setAttribute($this->getMorphType(), $this->morphClass); $model->save(); @@ -58,6 +73,21 @@ public function add(Model $model, $sessionKey = null) else { $this->parent->reloadRelations($this->relationName); } + + /** + * @event model.relation.afterAdd + * Called after adding a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterAdd', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * $relatedClass = get_class($relatedModel); + * $modelClass = get_class($model); + * traceLog("{$relatedClass} was added as {$relationName} to {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterAdd', [$this->relationName, $model]); } else { $this->parent->bindDeferred($this->relationName, $model, $sessionKey); @@ -70,6 +100,21 @@ public function add(Model $model, $sessionKey = null) public function remove(Model $model, $sessionKey = null) { if ($sessionKey === null) { + /** + * @event model.relation.beforeRemove + * Called before removing a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeRemove', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'permanentRelation') { + * throw new \Exception("Cannot dissociate a permanent relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeRemove', [$this->relationName, $model]); + $options = $this->parent->getRelationDefinition($this->relationName); if (array_get($options, 'delete', false)) { @@ -93,6 +138,21 @@ public function remove(Model $model, $sessionKey = null) else { $this->parent->reloadRelations($this->relationName); } + + /** + * @event model.relation.afterRemove + * Called after removing a relation to the model (for AttachOneOrMany, HasOneOrMany & MorphOneOrMany relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterRemove', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * $relatedClass = get_class($relatedModel); + * $modelClass = get_class($model); + * traceLog("{$relatedClass} was removed from {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterRemove', [$this->relationName, $model]); } else { $this->parent->unbindDeferred($this->relationName, $model, $sessionKey); diff --git a/src/Database/Relations/MorphTo.php b/src/Database/Relations/MorphTo.php index 39975e0c8..79d41b4c2 100644 --- a/src/Database/Relations/MorphTo.php +++ b/src/Database/Relations/MorphTo.php @@ -22,6 +22,90 @@ public function __construct(Builder $query, Model $parent, $foreignKey, $otherKe $this->addDefinedConstraints(); } + /** + * Associate the model instance to the given parent. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function associate($model) + { + /** + * @event model.relation.beforeAssociate + * Called before associating a relation to the model (only for BelongsTo/MorphTo relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeAssociate', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'dummyRelation') { + * throw new \Exception("Invalid relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeAssociate', [$this->relationName, $model]); + + $result = parent::associate($model); + + /** + * @event model.relation.afterAssociate + * Called after associating a relation to the model (only for BelongsTo/MorphTo relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterAssociate', function (string $relationName, \October\Rain\Database\Model $relatedModel) use (\October\Rain\Database\Model $model) { + * $relatedClass = get_class($relatedModel); + * $modelClass = get_class($model); + * traceLog("{$relatedClass} was associated as {$relationName} to {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterAssociate', [$this->relationName, $model]); + + return $result; + } + + /** + * Dissociate previously dissociated model from the given parent. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function dissociate() + { + /** + * @event model.relation.beforeDissociate + * Called before dissociating a relation to the model (only for BelongsTo/MorphTo relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.beforeDissociate', function (string $relationName) use (\October\Rain\Database\Model $model) { + * if ($relationName === 'permanentRelation') { + * throw new \Exception("Cannot dissociate a permanent relation!"); + * } + * }); + * + */ + $this->parent->fireEvent('model.relation.beforeDissociate', [$this->relationName]); + + $result = parent::dissociate(); + + /** + * @event model.relation.afterDissociate + * Called after dissociating a relation to the model (only for BelongsTo/MorphTo relations) + * + * Example usage: + * + * $model->bindEvent('model.relation.afterDissociate', function (string $relationName) use (\October\Rain\Database\Model $model) { + * $modelClass = get_class($model); + * traceLog("{$relationName} was dissociated from {$modelClass}."); + * }); + * + */ + $this->parent->fireEvent('model.relation.afterDissociate', [$this->relationName]); + + return $result; + } + /** * Helper for setting this relationship using various expected * values. For example, $model->relation = $value;