Laravel,简化关系:多个可排序模型


Laravel, simplifying relationships: multiple sortable models

当我在Laravel项目中工作时,我注意到我的模型中有一个重复出现的模式:

  • Model A有许多Model B
  • Model B可以选择隐藏
  • Model B可以选择性排序

示例:

画廊里有图片。您可以选择不显示图片,也可以选择对图片进行排序。

"查看我们的团队"页面。您可以选择不显示某些员工,也可以选择对他们进行排序。

主页上的滑块。您可以选择不显示某些图像,也可以选择对它们进行排序。

我已经实现了所有这些如下:

class ModelA extends Model {
    function modelBs() {
        $this->hasMany('modelB');
    }
}
class modelB extends Model {
    protected $fillable = ['visible', 'order'];
    function modelA() {
        $this->belongsTo('modelA');
    }
}

我还反复地重新实现(复制/粘贴)在刀片模板中显示这些内容的代码:

@foreach( $modelA->modelBs()->sortBy('order') as $modelB )
    @if( $modelB->visible )
        <li>{{ $modelB->output() }}</li>
    @endif
@endforeach

在管理面板中,我必须重复地重新实现(复制/粘贴)jQuery UI可排序小部件,用于修改订单、序列化您的决定并将其提交给服务器,然后保存此订单(通过适当更新每个Model B的订单)

它已经失控了,我想起了Laracasts的格言:

如果你发现自己使用复制粘贴,可能有更好的方法来做到

当我试图想出一个更好的解决方案时,这是我想象的第一种关系:

  • Model A有许多SortThing
  • SortThing变形为sortable
  • Model B有一个sortable

通过这种方式,我知道任何SortThing都可以被排序或隐藏,SortThing可以引用任何可排序的对象(图片、员工、滑块面板等)

问题是这样做并没有真正让我的代码变得更干燥:

class ModelA extends Model {
    function modelBs() {
        $this->hasMany('SortThing');
    }
}
class modelB extends Model {
    function sortable() {
        $this->morphsOne('SortThing', 'sortable');
    }
}
class SortThing extends Model {
    protected $fillable = ['visible', 'order'];
    function sortable() {
        $this->morphTo();
    }
}
@foreach( $modelA->modelBs()->sortBy('order') as $modelB )
    @if( $modelB->visible )
        <li>{{ $modelB->sortable->output() }}</li>
    @endif
@endforeach

我添加了一个额外的类,并且在输出中需要sortable->,并且我仍在复制/粘贴代码。

任何关于如何清理我的代码的建议都将不胜感激。仍然有点像拉拉威尔的新手。

如果在重新排序对象时,生成的关系不需要我更新18个数据库行,则会获得额外的分数,因为这可能会导致一些丑陋的开销,因为列表会变得非常长。

更新

尝试@gpopoteur下面的答案(在修复了renderItems函数声明中的拼写错误后),我得到了以下错误:

[2015-03-24 13:08:52] production.ERROR: exception 'Symfony'Component'Debug'Exception'FatalErrorException' with message 'App'Slider and App'HasSortableItemsTrait define the same property ($sortItemClass) in the composition of App'Slider. However, the definition differs and is considered incompatible. Class was composed' in /var/www/example.com/app/Slider.php:26

Slider.php看起来是这样的:

<?php namespace App;
use Illuminate'Database'Eloquent'Model;
class Slider extends Model {
    use HasSortableItemsTrait;
    protected $sortItemClass = 'App'SliderPanel';
    //
    public function sliderable() {
        return $this->morphTo();
    }
    public function panels() {
        return $this->hasMany('App'SliderPanel');
    }
    public function output() {
        $panels = $this->panels();
        return "Test?";
    }
} // Line 26

HasSortableItemsTrait.php看起来是这样的:

<?php namespace App;
trait HasSortableItemsTrait {
    protected $sortItemClass; // Also tried $sortItemClass = ''; and $sortItemClass = null;
    public function items() {
        $this->hasMany($this->sortItemClass)->sortyBy('order')->where('visible', '=', true);
    }
    public function renderItems($htmlTag = '<li>:item</li>') {
        $render = '';
        foreach( $this->items() as $item ){
            $render .= str_replace($item->render(), ':item', $htmlTag);
        }
        return $render;
    }
}

更新2

我已经发现,注释以下行可以解决我的问题:

protected $sortItemClass;

当然,我仍然需要确保任何使用该特性的东西都定义了$sortItemClass,否则在调用items() 时会失败

现在我得到了一个新错误:

[2015-03-24 13:34:50] production.ERROR: exception 'BadMethodCallException' with message 'Call to undefined method Illuminate'Database'Query'Builder::sortBy()' in /var/www/example.com/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:1992

我仔细检查了Laravel文档,我90%确信sortBy应该是查询生成器上的有效方法。。。

您可以尝试在一个特性中添加所有内容,然后在您的Model上只添加use SortableTrait;。然后将该类设置为它具有许多其他可排序项的protected属性。

!!!以下代码未经测试

让我们为具有可排序项目的类定义一个特征。

    trait HasSortableItemsTrait {
        // define this in your class
        // protected $sortItemClass
        public function items() {
            $this->hasMany($sortItemClass)->orderBy('order');
        }
        public function renderItems($htmlTag = '<li>:item</li>']) {
            return $this->items->map(function($item) use ($htmlTag) {
                if( $item->visible ){
                    return str_replace($item->render(), ':item', $htmlTag);
                }
            });
        }
    }

用于可排序项目的另一个特性。

    trait IsSortableTrait {
        // define this in your class
        // protected $sortItemParent
        function items() {
            $this->belongsTo($sortItemParent);
        }
        function render(){
            return $this->output();
        }
    }

让我们用一个具有可排序照片的库做一个例子。App'Gallery应该是这样的:

    class Gallery extends Model {
        use HasSortableItemsTrait;
        protected $sortItemClass = 'App'Photo';
    }

这就是App'Photo类的样子:

    class Photo extends Model {
        use IsSortableTrait;
        protected $sortItemParent = 'App'Gallery';
    }

然后,您只需要获取具有许多可排序项目的Item,在本例中为库。

    $gallery = Gallery::find(1);

在视图中,您只需要调用视图的renderItems()方法。

    {{ $gallery->renderItems() }}

我使renderItems方法能够接收您想要如何包装$item->render()将作为输出给出的内容。例如,在<p></p>之间,您只需要调用这样的方法:

    {{ $gallery->renderItems('<p>:item</p>') }}