From 3de02566bf199f3a49fcfe2b2d4119a287eb26bb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 19 May 2021 23:37:23 +0100 Subject: [PATCH] Started building system for cross-model queries --- app/Auth/Permissions/PermissionService.php | 3 +- .../Tools/RelationMultiModelQuery.php | 154 ++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 app/Entities/Tools/RelationMultiModelQuery.php diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index c5bdc8070..1c82f8e08 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -580,8 +580,9 @@ class PermissionService /** * Filter items that have entities set as a polymorphic relation. + * @param Builder|\Illuminate\Database\Query\Builder $query */ - public function filterRestrictedEntityRelations(Builder $query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view'): Builder + public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view') { $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn]; diff --git a/app/Entities/Tools/RelationMultiModelQuery.php b/app/Entities/Tools/RelationMultiModelQuery.php new file mode 100644 index 000000000..68a90acd0 --- /dev/null +++ b/app/Entities/Tools/RelationMultiModelQuery.php @@ -0,0 +1,154 @@ + */ + protected $lookupModels = []; + + /** @var Model */ + protected $relation; + + /** @var string */ + protected $polymorphicFieldName; + + public function __construct(Model $relation, string $polymorphicFieldName) + { + $this->relation = $relation; + $this->polymorphicFieldName = $polymorphicFieldName; + } + + /** + * Set the query to look up the given entity type. + */ + public function forEntity(string $class, array $columns): self + { + $this->lookupModels[$class] = $columns; + return $this; + } + + /** + * Set the query to look up all entity types. + */ + public function forAllEntities(): self + { + $this->lookupModels[Page::class] = ['id', 'name', 'slug', 'book_id', 'chapter_id', 'text']; + $this->lookupModels[Chapter::class] = ['id', 'name', 'slug', 'book_id', 'description']; + $this->lookupModels[Book::class] = ['id', 'name', 'slug', 'description', 'image_id']; + $this->lookupModels[Bookshelf::class] = ['id', 'name', 'slug', 'description', 'image_id']; + return $this; + } + + /** + * Build the core query to run. + */ + protected function build(): Builder + { + $query = $this->relation->newQuery()->toBase(); + $relationTable = $this->relation->getTable(); + $modelTables = []; + + // Load model selects & joins + foreach ($this->lookupModels as $lookupModel => $columns) { + /** @var Entity $model */ + $model = (new $lookupModel); + $table = $model->getTable(); + $modelTables[] = $table; + $query->addSelect($this->tableColumnsToSelectArray($table, $columns)); + $query->leftJoin($table, function (JoinClause $join) use ($table, $relationTable, $model) { + $polyPrefix = $relationTable . '.' . $this->polymorphicFieldName; + $join->on($polyPrefix . '_id', '=', $table . '.id'); + $join->where($polyPrefix . '_type', '=', $model->getMorphClass()); + $join->whereNull($table . '.deleted_at'); + }); + } + + // Where we have a model result + $query->where(function (Builder $query) use ($modelTables) { + foreach ($modelTables as $table) { + $query->orWhereNotNull($table . '.id'); + } + }); + + $this->applyPermissionsToQuery($query, 'view'); + + return $query; + } + + protected function applyPermissionsToQuery(Builder $query, string $action) + { + $permissions = app()->make(PermissionService::class); + $permissions->filterRestrictedEntityRelations( + $query, + $this->relation->getTable(), + $this->polymorphicFieldName . '_id', + $this->polymorphicFieldName . '_type', + $action, + ); + } + + /** + * Create an array of select statements from the given table and column. + */ + protected function tableColumnsToSelectArray(string $table, array $columns): array + { + $selectArray = []; + foreach ($columns as $column) { + $selectArray[] = $table . '.' . $column . ' as '. $table . '_' . $column; + } + return $selectArray; + } + + /** + * Get the SQL from the core query being ran. + */ + public function toSql(): string + { + return $this->build()->toSql(); + } + + /** + * Run the query and get the results. + */ + public function run(): Collection + { + return $this->build()->get(); + } +} \ No newline at end of file