Laravel中ShapeContainer与多态形状子类的关联建模优化咨询
解决方案
你有两种方式可以实现需求,既保留中间表,又不用让开发者关注shapeable关联:
方案一:用魔术方法封装中间模型的调用(保留现有中间表和Shape模型)
既然你已经搭好了中间的Shape模型,只需要给它加两个魔术方法,把对Shape实例的方法调用和属性访问自动转发给关联的shapeable模型就行,开发者完全不用管shapeable这一层:
class Shape extends Model { protected $table = 'shapes'; // 替换成你的中间表实际表名 public function shapeable() { return $this->morphTo(); } // 自动转发方法调用到关联的形状模型 public function __call($method, $parameters) { if ($this->shapeable && method_exists($this->shapeable, $method)) { return $this->shapeable->$method(...$parameters); } return parent::__call($method, $parameters); } // 自动转发属性访问到关联的形状模型 public function __get($key) { if ($this->shapeable && property_exists($this->shapeable, $key)) { return $this->shapeable->$key; } return parent::__get($key); } }
之后调用的时候直接写:
// 直接调用getArea,无需写shapeable $area = $shapeContainer->shapes->first()->getArea(); // 直接访问形状属性,比如Circle的radius $radius = $shapeContainer->shapes->first()->radius;
方案二:移除中间模型,用Laravel多态多对多关联直接关联形状
如果不想保留多余的Shape模型,直接利用Laravel的多态多对多关联,把你的中间表作为关联的 pivot 表,让ShapeContainer直接关联所有实现Shape接口的模型:
首先确保中间表结构(假设表名为shape_container_shape):
| 字段名 | 类型 |
|---|---|
| id | unsigned int |
| shape_container_id | unsigned int |
| shapeable_type | varchar |
| shapeable_id | unsigned int |
| created_at | timestamp |
| updated_at | timestamp |
然后在ShapeContainer模型里定义关联:
class ShapeContainer extends Model { public function shapes() { return $this->morphToMany( AbstractShape::class, 'shapeable', 'shape_container_shape', // 中间表名 'shape_container_id', // 关联ShapeContainer的外键 'shapeable_id', // 关联形状模型的外键 'id', // ShapeContainer的主键 'id' // 形状模型的主键 ); } }
创建一个抽象基类AbstractShape,让所有形状模型继承它并实现Shape接口:
abstract class AbstractShape extends Model implements Shape { // 可在此添加所有形状通用的逻辑 } class Circle extends AbstractShape { public function getArea(): float { return pi() * $this->radius * $this->radius; } public function getPerimeter(): float { return 2 * pi() * $this->radius; } } class Rectangle extends AbstractShape { public function getArea(): float { return $this->width * $this->height; } public function getPerimeter(): float { return 2 * ($this->width + $this->height); } }
调用时直接通过$shapeContainer->shapes获取所有形状实例,直接调用方法:
foreach ($shapeContainer->shapes as $shape) { echo '面积:' . $shape->getArea() . PHP_EOL; echo '周长:' . $shape->getPerimeter() . PHP_EOL; }
两种方案各有优劣:方案一适合你已经搭好现有结构,不想大幅改动;方案二更贴合Laravel的关联设计,没有多余的中间模型。
内容的提问来源于stack exchange,提问作者Simeon Borisov
相关产品推荐
相关产品推荐

