Вложенные ресурсы MySQL (сводные таблицы) для просмотра / обновления / управления MySQL

users transactions tasks +----+--------+ +----+---------------+ +----+--------+ | id | name | | id | name | | id | name | +----+--------+ +----+---------------+ +----+--------+ | 1 | User 1 | | 1 | Transaction 1 | | 1 | Task 1 | | 2 | User 2 | | 2 | Transaction 2 | | 2 | Task 2 | +----+--------+ +----+---------------+ +----+--------+ templates transaction_user task_transaction +----+---------------+ +---------+----------------+ +---------+----------------+ | id | name | | user_id | transaction_id | | task_id | transaction_id | +----+---------------+ +---------+----------------+ +---------+----------------+ | 1 | Template 1 | | 1 | 1 | | 1 | 1 | | 2 | Template 2 | | 2 | 2 | +---------+----------------+ +----+---------------+ +---------+----------------+ task_template +---------+-------------+ | task_id | template_id | +---------+-------------+ | 2 | 2 | +---------+-------------+ 

Мотив: если есть зарегистрированный пользователь, скажем, пользователь с идентификатором 1, и он / она хочет видеть задачу (например, задачу с идентификатором 1), то я хочу убедиться, что задача с идентификатором 1 Belongs to пользователю прежде чем я позволю ему просмотреть его. Также мне нужно каким-то образом показать пользователю все задачи, которые ему принадлежат. Задача – это только одна модель. Мне нужно обрабатывать ее для всех моделей. Я поделился своим кодом ниже, я слишком стараюсь?

Возможно, я пропустил некоторые подробности, поэтому, пожалуйста, не стесняйтесь задавать вопросы. Благодарю.

Код

 <?php namespace SomeProject\Repositories; use User; use Account; use Task; use Document; use Transaction; use Property; use DB; use Respond; abstract class DbRepository { /** * The many to many relationships are handeled using pivot tables * We will use this array to figure out relationships and then get * a particular resource's owner / account */ public $pivot_models = array( 'Task' => array( 'Transaction' => 'task_transaction' ), 'Transaction' => array( 'User' => 'transaction_user' ), 'Document' => array( 'Property' => 'document_property', 'Task' => 'document_task', 'Message' => 'document_message' ) ); public $entity_ids; public function getOwnersByEntity(array $ids, $entity) { $this->entity_ids = []; $user_ids = []; $entity = ucfirst(strtolower($entity)); // arrays keys are case sensitive if( $this->getPivotIds($ids, $entity) ) { foreach ($this->entity_ids as $entity_name => $entity_ids_arr) { $entity_name_lowercase = strtolower($entity_name); if($entity_name_lowercase != 'user') { $user_ids_from_entity = $entity_name::whereIn('id', $entity_ids_arr) ->lists('user_id'); } else { // We already have the IDs if the entity is User $user_ids_from_entity = $entity_ids_arr; } array_push($user_ids, $user_ids_from_entity); } $merged_user_ids = call_user_func_array('array_merge', $user_ids); return array_unique($merged_user_ids); } else { return $entity::whereIn('id', $ids)->lists('user_id'); } } public function getPivotIds(array $ids, $entity) { $entity_lowercase = strtolower($entity); if( array_key_exists($entity, $this->pivot_models) ) { // Its a pivot model foreach ($this->pivot_models[$entity] as $related_model => $table) // Transaction, Template { $related_model_lowercase = strtolower($related_model); $this->entity_ids[$related_model] = DB::table($table) ->whereIn($entity_lowercase . '_id', $ids) ->lists($related_model_lowercase . '_id'); if( $this->getPivotIds($this->entity_ids[$related_model], $related_model) ) { unset($this->entity_ids[$related_model]); } } return true; } return false; } } не <?php namespace SomeProject\Repositories; use User; use Account; use Task; use Document; use Transaction; use Property; use DB; use Respond; abstract class DbRepository { /** * The many to many relationships are handeled using pivot tables * We will use this array to figure out relationships and then get * a particular resource's owner / account */ public $pivot_models = array( 'Task' => array( 'Transaction' => 'task_transaction' ), 'Transaction' => array( 'User' => 'transaction_user' ), 'Document' => array( 'Property' => 'document_property', 'Task' => 'document_task', 'Message' => 'document_message' ) ); public $entity_ids; public function getOwnersByEntity(array $ids, $entity) { $this->entity_ids = []; $user_ids = []; $entity = ucfirst(strtolower($entity)); // arrays keys are case sensitive if( $this->getPivotIds($ids, $entity) ) { foreach ($this->entity_ids as $entity_name => $entity_ids_arr) { $entity_name_lowercase = strtolower($entity_name); if($entity_name_lowercase != 'user') { $user_ids_from_entity = $entity_name::whereIn('id', $entity_ids_arr) ->lists('user_id'); } else { // We already have the IDs if the entity is User $user_ids_from_entity = $entity_ids_arr; } array_push($user_ids, $user_ids_from_entity); } $merged_user_ids = call_user_func_array('array_merge', $user_ids); return array_unique($merged_user_ids); } else { return $entity::whereIn('id', $ids)->lists('user_id'); } } public function getPivotIds(array $ids, $entity) { $entity_lowercase = strtolower($entity); if( array_key_exists($entity, $this->pivot_models) ) { // Its a pivot model foreach ($this->pivot_models[$entity] as $related_model => $table) // Transaction, Template { $related_model_lowercase = strtolower($related_model); $this->entity_ids[$related_model] = DB::table($table) ->whereIn($entity_lowercase . '_id', $ids) ->lists($related_model_lowercase . '_id'); if( $this->getPivotIds($this->entity_ids[$related_model], $related_model) ) { unset($this->entity_ids[$related_model]); } } return true; } return false; } } 

Чтобы проверить, связана ли данная модель с другой, что вы хотите, если я получу вас правильно, все, что вам нужно, это крошечный метод, делающий большую часть Eloquent :

( BaseModel его в BaseModel , Entity или сфере действия, что вам подходит)

 // usage $task->isRelatedTo('transactions.users', $id); // or $template->isRelatedTo('tasks.transactions.users', Auth::user()); // or any kind of relation: // imagine this: User mm Transaction 1-m Item m-1 Group $group->isRelatedTo('items.transaction.users', $id); 

Здесь происходит волшебство:

 /** * Check if it is related to any given model through dot nested relations * * @param string $relations * @param int|\Illuminate\Database\Eloquent\Model $id * @return boolean */ public function isRelatedTo($relations, $id) { $relations = explode('.', $relations); if ($id instanceof Model) { $related = $id; $id = $related->getKey(); } else { $related = $this->getNestedRelated($relations); } // recursive closure $callback = function ($q) use (&$callback, &$relations, $related, $id) { if (count($relations)) { $q->whereHas(array_shift($relations), $callback); } else { $q->where($related->getQualifiedKeyName(), $id); } }; return (bool) $this->whereHas(array_shift($relations), $callback)->find($this->getKey()); } protected function getNestedRelated(array $relations) { $models = []; foreach ($relations as $key => $relation) { $parent = ($key) ? $models[$key-1] : $this; $models[] = $parent->{$relation}()->getRelated(); } return end($models); } 

Эй, но что там происходит?

isRelatedTo() работает следующим образом:

  1. проверьте, прошел ли он $id модель или просто id, и подготавливает $related модель и ее $id для использования в обратном вызове. Если вы не передаете объект, тогда Eloquent должен создать все связанные модели в цепочке $relations ( relation1.relation2.relation3... ), чтобы получить тот, который нам интересен – вот что происходит в getNestedRelated() , довольно простой.

  2. то нам нужно сделать что-то вроде этого:

     // assuming relations 'relation1.relation2.relation3' $this->whereHas('relation1', function ($q) use ($id) { $q->whereHas('relation2', function ($q) use ($id) { $q->whereHas('relation3', function ($q) use ($id) { $q->where('id', $id); }); }); })->find($this->getKey()); // returns new instance of current model or null, thus cast to (bool) 
  3. поскольку мы не знаем, насколько глубоко вложенное отношение, нам нужно использовать повторную обработку. Однако мы передаем закрытие в whereHas , поэтому нам нужно использовать небольшой трюк, чтобы называть себя внутри его тела (на самом деле мы его не называем, а скорее передаем его как $callback методу whereHas , поскольку последний ожидает закрытие как второй параметр) – это может быть полезно для тех незнакомых анонимных рекурсивных функций PHP :

     // save it to the variable and pass it by reference $callback = function () use (&$callback) { if (...) // call the $callback again else // finish; } 
  4. мы также переходим к замыканию $relations (как массив в настоящее время) по ссылке, чтобы не сдвигать его элементы, и когда мы получили их все (что означает, что мы whereHas ), мы, наконец, помещаем предложение where вместо другого whereHas , чтобы искать нашей $related модели.

  5. наконец вернемся bool

Там действительно нет простого и канонического пути, но вот пример того, что я постараюсь сделать.

 class Entity extends Eloquent { public function isRelatedTo($instance, $through) { $column = $instance->joiningTable($through) . '.' . $instance->getForeignKey(); $query = DB::table(''); this->buildJoin($query, $instance, $through); return $query->where($column, '=', $instance->getKey())->exists(); } public function relatesToMany($related, $through) { $that = $this; $related = new $related; return $related->whereIn($related->getKeyName(), function($query) use ($that, $related, $through) { $that->buildJoin($query, $related, $through); })->get(); } protected function buildJoin($query, $related, $through) { $through = new $through; $this_id = $this->getForeignKey(); $related_id = $related->getForeignKey(); $through_id = $through->getForeignKey(); $this_pivot = $this->joiningTable($through); $related_pivot = $related->joiningTable($through); $query->select($related_pivot . '.' . $related_id)->from($related_pivot) ->join($this_pivot, $related_pivot . '.' . $through_id, '=', $this_pivot . '.' . $through_id) ->where($this_pivot . '.' . $this_id, '=', $this->getKey()); } } 

Затем для вашего случая использования:

 class User extends Entity { public function isOwnerOf($task) { return $this->isRelatedTo($task, 'Transaction'); } public function tasks() { return $this->relatesToMany('Task', 'Transaction'); } } 

Отказ от ответственности: код не был протестирован.

Обратите внимание, что в этом очень упрощенном примере relatesToMany напрямую возвращает коллекцию. Чтобы иметь больше преимуществ, он мог вместо этого вернуть экземпляр вашего собственного расширения класса Relay Eloquent, который занимает больше времени, чтобы реализовать его.
Нетрудно добавить поддержку нескольких промежуточных объектов ; вы, вероятно, можете ожидать, что аргумент $through может быть массивом, а затем построить запрос с несколькими соединениями соответственно.

Если это проект Laravel, то да, вы слишком много стараетесь.

Если вы собираетесь использовать Laravel, рекомендуется использовать функции, предоставляемые вам с Laravel, а именно: ORM, Eloquent, и в комплекте Schema. Я бы рекомендовал вам просмотреть страницу «Начало работы с Laravel» в своей документации , чтобы вы могли правильно настроить проект для использования Eloquent.

Было бы также полезно, если бы вы ознакомились с основами того, как Eloquent обрабатывает отношения в своих моделях, поскольку они выполняют всю работу, которую вы пытаетесь сделать.