We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
<?php namespace Askedio\SoftCascade; use Askedio\SoftCascade\Contracts\SoftCascadeable; use Askedio\SoftCascade\Exceptions\SoftCascadeLogicException; use Askedio\SoftCascade\Exceptions\SoftCascadeNonExistentRelationActionException; use Askedio\SoftCascade\Exceptions\SoftCascadeRestrictedException; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\MorphOneOrMany; use Illuminate\Support\Facades\DB; class SoftCascade implements SoftCascadeable { protected $direction; protected $directionData; protected $availableActions = ['update', 'restrict']; protected $fnGetForeignKey = ['getQualifiedForeignKeyName', 'getQualifiedOwnerKeyName', 'getForeignPivotKeyName']; protected $dbsToTransact = []; /** * Cascade over Eloquent items. * * @param Illuminate\Database\Eloquent\Model $models * @param string $direction update|delete|restore * @param array $directionData * * @return void */ public function cascade($models, $direction, array $directionData = []) { try { $this->direction = $direction; $this->directionData = $directionData; $this->run($models); //All ok we commit all database queries foreach ($this->dbsToTransact as $connectionToTransact) { DB::connection($connectionToTransact)->commit(); } } catch (\Exception $e) { //Rollback the transaction before throw exception foreach ($this->dbsToTransact as $connectionToTransact) { DB::connection($connectionToTransact)->rollBack(); } throw new SoftCascadeLogicException($e->getMessage(), null, $e); } } /** * Run the cascade. * * @param Illuminate\Database\Eloquent\Model $models * * @return void */ protected function run($models) { $models = collect($models); if ($models->count() > 0) { $model = $models->first(); if (!is_object($model)) { return; } if (!$this->isCascadable($model)) { return; } if (!in_array($model->getConnectionName(), $this->dbsToTransact)) { $this->dbsToTransact[] = $model->getConnectionName(); DB::connection($model->getConnectionName())->beginTransaction(); } $this->relations($model, $models->pluck($model->getKeyName())); } } /** * Iterate over the relations. * * @param Illuminate\Database\Eloquent\Model $model * @param array $foreignKeyIds * @param array $foreignKeyIds * * @return mixed */ protected function relations($model, $foreignKeyIds) { $relations = $model->getSoftCascade(); if (empty($relations)) { return; } foreach ($relations as $relation) { extract($this->relationResolver($relation)); $this->validateRelation($model, $relation); $modelRelation = $model->$relation(); /** * Maintains compatibility fot get foreign key name on laravel old and new methods. * * @link https://github.com/laravel/framework/issues/20869 */ $fnUseGetForeignKey = array_intersect($this->fnGetForeignKey, get_class_methods($modelRelation)); $fnUseGetForeignKey = reset($fnUseGetForeignKey); //Get foreign key and foreign key ids $foreignKeyUse = $modelRelation->{$fnUseGetForeignKey}(); $foreignKeyIdsUse = $foreignKeyIds; //Many to many relations need to get related ids and related local key if ($modelRelation instanceof BelongsToMany) { extract($this->getBelongsToManyData($modelRelation, $foreignKeyUse, $foreignKeyIds)); } elseif ($modelRelation instanceof MorphOneOrMany) { extract($this->getMorphManyData($modelRelation, $foreignKeyIds)); } $affectedRows = $this->affectedRows($modelRelation, $foreignKeyUse, $foreignKeyIdsUse); if ($action === 'restrict' && $affectedRows > 0) { DB::rollBack(); //Rollback the transaction before throw exception throw (new SoftCascadeRestrictedException())->setModel(get_class($modelRelation->getModel()), $foreignKeyUse, $foreignKeyIdsUse->toArray()); } $this->execute($modelRelation, $foreignKeyUse, $foreignKeyIdsUse, $affectedRows); } } /** * Get many to many related key ids and key use. * * @param Illuminate\Database\Eloquent\Relations\Relation $relation * @param string $relationForeignKey * @param array $foreignKeyIds * * @return array */ protected function getBelongsToManyData($relation, $relationForeignKey, $foreignKeyIds) { $relationConnection = $relation->getConnection()->getName(); $relationTable = $relation->getTable(); $relationRelatedKey = $relation->getQualifiedRelatedPivotKeyName(); //Get related ids $foreignKeyIdsUse = DB::connection($relationConnection) ->table($relationTable) ->whereIn($relationForeignKey, $foreignKeyIds) ->select([$relationRelatedKey]) ->get()->toArray(); $foreignKeyUse = explode('.', $relationRelatedKey); $foreignKeyUse = end($foreignKeyUse); $foreignKeyIdsUse = array_column($foreignKeyIdsUse, $foreignKeyUse); return [ 'foreignKeyIdsUse' => collect($foreignKeyIdsUse), 'foreignKeyUse' => $relation->getRelated()->getKeyName(), ]; } /** * Get morph many related key ids and key use. * * @param Illuminate\Database\Eloquent\Relations\Relation $relation * @param array $foreignKeyIds * * @return array */ protected function getMorphManyData($relation, $foreignKeyIds) { $relatedClass = $relation->getRelated(); $foreignKeyUse = $relatedClass->getKeyName(); $baseQuery = $this->direction === 'delete' ? $relatedClass::query() : $relatedClass::withTrashed(); $foreignKeyIdsUse = $baseQuery->where($relation->getMorphType(), $relation->getMorphClass()) ->whereIn($relation->getQualifiedForeignKeyName(), $foreignKeyIds) ->select($foreignKeyUse) ->get()->toArray(); $foreignKeyIdsUse = array_column($foreignKeyIdsUse, $foreignKeyUse); return [ 'foreignKeyIdsUse' => collect($foreignKeyIdsUse), 'foreignKeyUse' => $relation->getRelated()->getKeyName(), ]; } /** * Execute delete or restore or forcedelete. * * @param Illuminate\Database\Eloquent\Relations\Relation $relation * @param string $foreignKey * @param array $foreignKeyIds * @param int $affectedRows * * @return void */ protected function execute($relation, $foreignKey, $foreignKeyIds, $affectedRows) { $relationModel = $relation->getQuery()->getModel(); $relationModel = new $relationModel(); if ($affectedRows > 0) { if ($this->direction !== 'delete') { $relationModel = $relationModel->withTrashed(); } $relationModel = $relationModel->whereIn($foreignKey, $foreignKeyIds)->limit($affectedRows); $this->run($relationModel->get([$relationModel->getModel()->getKeyName()])); // COMMIT : force delete when parent model "isForceDeleting = true" if ($this->isForceDeleting($relation)) { $relationModel->forceDelete(); } else { $relationModel->{$this->direction}($this->directionData); } } } /** * Validate the relation method exists and is a type of Eloquent Relation. * * @param Illuminate\Database\Eloquent\Model $model * @param string $relation * * @return void */ protected function validateRelation($model, $relation) { $class = get_class($model); if (!method_exists($model, $relation)) { DB::rollBack(); //Rollback the transaction before throw exception throw new \LogicException(sprintf('%s does not have method \'%s\'.', $class, $relation)); } if (!$model->$relation() instanceof \Illuminate\Database\Eloquent\Relations\Relation) { DB::rollBack(); //Rollback the transaction before throw exception throw new \LogicException(sprintf('%s \'%s\' is not an instance of Illuminate\Database\Eloquent\Relations\Relation.', $class, $relation)); } } /** * Check if the model is enabled to cascade. * * @param Illuminate\Database\Eloquent\Model $model * * @return bool */ protected function isCascadable($model) { return method_exists($model, 'getSoftCascade'); } /** * Affected rows if we do execute. * * @param Illuminate\Database\Eloquent\Relations\Relation $relation * @param string $foreignKey * @param array $foreignKeyIds * * @return void */ protected function affectedRows($relation, $foreignKey, $foreignKeyIds) { $relationModel = $relation->getQuery()->getModel(); $relationModel = new $relationModel(); // COMMIT : retreive relation trashed items when parent model "isForceDeleting = true" if ($this->direction !== 'delete' || $this->isForceDeleting($relation)) { $relationModel = $relationModel->withTrashed(); } return $relationModel->whereIn($foreignKey, $foreignKeyIds)->count(); } /** * Resolve relation string. * * @param string $relation * * @return array */ protected function relationResolver($relation) { $parsedAction = explode('@', $relation); $return['relation'] = $parsedAction[0]; $return['action'] = isset($parsedAction[1]) ? $parsedAction[1] : 'update'; if (!in_array($return['action'], $this->availableActions)) { DB::rollBack(); //Rollback the transaction before throw exception throw (new SoftCascadeNonExistentRelationActionException())->setRelation(implode('@', $return)); } return $return; } /** * COMMIT * Check if parent has a force delete enabled * @return boolean */ protected function isForceDeleting($relation) { $parent = $relation->getParent(); return property_exists($parent, 'forceDeleting') && $parent->isForceDeleting(); } }
The text was updated successfully, but these errors were encountered:
feat: #145 ForceDelete functionality added
0707dcf
gcphost
maguilar92
Successfully merging a pull request may close this issue.
Description: Implementation of forceDelete
Edits :
The text was updated successfully, but these errors were encountered: