Laravel,简化关系:多个可排序模型
Laravel, simplifying relationships: multiple sortable models
当我在做一个 Laravel 项目时,我注意到我的模型中有一个重复出现的模式:
Model A
有很多 Model B
Model B
可以选择隐藏
Model B
可以随意排序
示例:
画廊有图片。您可以选择不显示图片,也可以选择排序。
一个"view our team"页面。您可以选择不显示某些员工,也可以选择对他们进行排序。
主页上的滑块。您可以选择不显示某些图片,也可以选择对它们进行排序。
我一直在实施所有这些,如下所示:
class ModelA extends Model {
function modelBs() {
$this->hasMany('modelB');
}
}
class modelB extends Model {
protected $fillable = ['visible', 'order'];
function modelA() {
$this->belongsTo('modelA');
}
}
我也在反复re-implementing(copying/pasting)在blade模板中显示这些的代码:
@foreach( $modelA->modelBs()->sortBy('order') as $modelB )
@if( $modelB->visible )
<li>{{ $modelB->output() }}</li>
@endif
@endforeach
并且在管理面板中,我必须重复 re-implement (copy/paste) jQuery-UI 可排序小部件来修改顺序,序列化您的决定,并将其提交到服务器然后保存此订单(通过适当更新每个 Model B
的订单)
已经失控了,我想起了 Laracasts 的格言:
If you find yourself using copy and paste, there's probably a better way to do it
当我试图想出更好的解决方案时,这是我想象的第一个关系:
Model A
有很多 SortThing
SortThing
morphsTo sortable
Model B
有一个 sortable
这样我就知道了any SortThing
可以排序或者隐藏,一个SortThing
可以引用任何可排序的object(图片、员工、滑块面板等)
问题是这样做并没有真正让我的代码变得更干:
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
我添加了一个额外的 class 并在我的输出中需要 sortable->
,但我仍然是 copying/pasting 代码。
任何关于如何清理我的代码的建议都将不胜感激。还是有点Laravel新手。
如果在 object 为 re-ordered 时生成的关系不需要我更新 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
我 double-checked Laravel 文档,我 90% 确定 sortBy
应该是查询构建器的有效方法...
您可以尝试在特征中添加所有内容,然后在您的模型中添加 use SortableTrait;
。然后将 class 设置为它有很多的其他可排序项目的 protected
属性。
!!!以下代码未经测试!!!
让我们为具有可排序项目的 Class 定义特征。
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 的样子:
class Photo extends Model {
use IsSortableTrait;
protected $sortItemParent = 'App\Gallery';
}
然后您只需要获取具有许多可排序项目的项目,在本例中为画廊。
$gallery = Gallery::find(1);
而在视图中你只需要调用视图的renderItems()
方法。
{{ $gallery->renderItems() }}
我使 renderItems
方法能够接收您想要包装 $item->render()
将作为输出给出的内容的方式。例如,在 <p></p>
之间,您只需调用如下方法:
{{ $gallery->renderItems('<p>:item</p>') }}
当我在做一个 Laravel 项目时,我注意到我的模型中有一个重复出现的模式:
Model A
有很多Model B
Model B
可以选择隐藏Model B
可以随意排序
示例:
画廊有图片。您可以选择不显示图片,也可以选择排序。
一个"view our team"页面。您可以选择不显示某些员工,也可以选择对他们进行排序。
主页上的滑块。您可以选择不显示某些图片,也可以选择对它们进行排序。
我一直在实施所有这些,如下所示:
class ModelA extends Model {
function modelBs() {
$this->hasMany('modelB');
}
}
class modelB extends Model {
protected $fillable = ['visible', 'order'];
function modelA() {
$this->belongsTo('modelA');
}
}
我也在反复re-implementing(copying/pasting)在blade模板中显示这些的代码:
@foreach( $modelA->modelBs()->sortBy('order') as $modelB )
@if( $modelB->visible )
<li>{{ $modelB->output() }}</li>
@endif
@endforeach
并且在管理面板中,我必须重复 re-implement (copy/paste) jQuery-UI 可排序小部件来修改顺序,序列化您的决定,并将其提交到服务器然后保存此订单(通过适当更新每个 Model B
的订单)
已经失控了,我想起了 Laracasts 的格言:
If you find yourself using copy and paste, there's probably a better way to do it
当我试图想出更好的解决方案时,这是我想象的第一个关系:
Model A
有很多SortThing
SortThing
morphsTosortable
Model B
有一个sortable
这样我就知道了any SortThing
可以排序或者隐藏,一个SortThing
可以引用任何可排序的object(图片、员工、滑块面板等)
问题是这样做并没有真正让我的代码变得更干:
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
我添加了一个额外的 class 并在我的输出中需要 sortable->
,但我仍然是 copying/pasting 代码。
任何关于如何清理我的代码的建议都将不胜感激。还是有点Laravel新手。
如果在 object 为 re-ordered 时生成的关系不需要我更新 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
我 double-checked Laravel 文档,我 90% 确定 sortBy
应该是查询构建器的有效方法...
您可以尝试在特征中添加所有内容,然后在您的模型中添加 use SortableTrait;
。然后将 class 设置为它有很多的其他可排序项目的 protected
属性。
!!!以下代码未经测试!!!
让我们为具有可排序项目的 Class 定义特征。
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 的样子:
class Photo extends Model {
use IsSortableTrait;
protected $sortItemParent = 'App\Gallery';
}
然后您只需要获取具有许多可排序项目的项目,在本例中为画廊。
$gallery = Gallery::find(1);
而在视图中你只需要调用视图的renderItems()
方法。
{{ $gallery->renderItems() }}
我使 renderItems
方法能够接收您想要包装 $item->render()
将作为输出给出的内容的方式。例如,在 <p></p>
之间,您只需调用如下方法:
{{ $gallery->renderItems('<p>:item</p>') }}