MySQL嵌套资源(数据透视表)查看/更新/管理权限

时间:2014-06-27 08:14:44

标签: php mysql database database-design laravel

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 |
+---------+-------------+

动机: 如果有登录用户,说ID为1的用户,并且他/她想要查看任务(比如ID为1的任务),那么我想确保ID为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;
    }
}

3 个答案:

答案 0 :(得分:6)

要检查给定的模型是否与另一个模型相关,如果我找到你就是你想要的,你需要的只是这个微小的方法充分利用Eloquent

(在BaseModelEntity或范围内实施,适用于您的任何内容)

// usage
$task->isRelatedTo('transactions.users', $id);
// or
$template->isRelatedTo('tasks.transactions.users', Auth::user());

// or any kind of relation:
// imagine this: User m-m 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需要在$relationsrelation1.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. 因为我们不知道嵌套关系有多深,所以我们需要使用并发。然而我们将一个Closure传递给whereHas,所以我们需要使用小技巧来调用它自己的体内(事实上我们不会调用它,而是将它作为$callback传递给它whereHas方法,因为后者希望Closure成为第二个参数) - 这可能对那些不熟悉的Anonymous recursive PHP functions 有用:

    // 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

答案 1 :(得分:1)

真的没有简单或规范的方式,但这是我尝试做的一个原始例子。

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直接返回一个Collection。为了获得更多优势,它可以返回一个你自己的Eloquent的Relation类扩展的实例 - 这显然需要更长的时间来实现。
添加对多个中间实体的支持应该不难;您可能希望$through参数可能是一个数组,然后相应地构建多连接查询。

答案 2 :(得分:-2)

如果这是一个Laravel项目,那么是的,你的尝试太难了。

如果您打算使用Laravel,建议您使用Laravel提供的功能,即ORM,Eloquent,以及它捆绑的Schema工具。我建议您查看Laravel's Getting Started page in their documentation,以便正确设置项目以使用Eloquent。

如果你在他们的模型中阅读the basics of how Eloquent handles relations,那也是有益的,因为他们完成了你正在尝试的所有工作。