Laravel 5.5中Eloquent模型包装类引发无限循环问题求助
我太懂你现在的困扰了——互相引用的模型包装器直接引发了无限递归循环,这在实体转换的场景里真的很常见。咱们来梳理几个实用的解决思路,每个都带代码示例,适配你Laravel 5.5的项目:
方案1:延迟加载关联实体
核心思路是不要在包装初期就加载所有关联,而是给实体添加延迟加载的方法,当真正需要用到关联数据时才触发包装逻辑,从根源上避免循环触发。
修改UserEntity,加入原始模型的引用和延迟加载方法:
class UserEntity { private $id; private $name; private $products; // 保存原始模型用于延迟加载 private $model; // 调整构造函数,接收原始模型 public function __construct($model) { $this->model = $model; } // 延迟加载Products public function getProducts() { if (is_null($this->products)) { $this->products = (new ProductEntityWrapper($this->model->products))->entity; } return $this->products; } // 其他getter/setter保持不变... }
对应的UserEntityWrapper修改makeEntity方法,不再主动设置Products:
class UserEntityWrapper extends EntityWrapper { protected $entityClass = UserEntity::class; protected function makeEntity($userEntity, $model) { // 直接传入模型初始化实体 $userEntity = new UserEntity($model); $userEntity->setId($model->user_id); $userEntity->setName($model->name); // 移除主动调用setProducts的代码 return $userEntity; } }
ProductEntity和ProductEntityWrapper做完全相同的调整,只有当调用getUser()或getProducts()时才会触发关联包装,不会在初始化时就陷入循环。
方案2:限制包装层级
给包装器添加一个「最大递归层级」参数,当层级耗尽时停止包装关联,适合你明确知道需要多少层关联数据的场景。
首先修改基类EntityWrapper,加入层级控制:
abstract class EntityWrapper { protected $collection; protected $entityClass; protected $depth; public $entity; // 新增$depth参数,默认值设为2(按需调整) public function __construct($collection, $depth = 2) { $this->collection = $collection; $this->depth = $depth; $this->entity = $this->buildEntity(); } // 其他代码保持不变... }
然后修改UserEntityWrapper的关联包装逻辑:
class UserEntityWrapper extends EntityWrapper { protected $entityClass = UserEntity::class; protected function makeEntity($userEntity, $model) { $userEntity->setId($model->user_id); $userEntity->setName($model->name); // 只有层级>0时才包装关联,且传递层级-1给下一级 if ($this->depth > 0) { $userEntity->setProducts((new ProductEntityWrapper($model->products, $this->depth - 1))->entity); } return $userEntity; } }
ProductEntityWrapper同理处理:
class ProductEntityWrapper extends EntityWrapper { protected $entityClass = ProductEntity::class; protected function makeEntity($productEntity, $model) { $productEntity->setId($model->product_id); $productEntity->setName($model->name); if ($this->depth > 0) { // 注意User是单个模型,用collect包装后取first() $userWrapper = new UserEntityWrapper(collect([$model->user]), $this->depth - 1); $productEntity->setUser($userWrapper->entity->first()); } return $productEntity; } }
调用时可以指定层级:
// 只会包装User -> Product,不会继续包装Product的User (new UserEntityWrapper(User::all(), 1))->entity
方案3:跟踪已包装实体,避免重复包装
维护一个已包装实体的缓存池,用模型ID作为唯一标识,当某个模型已经被包装过,直接返回缓存的实体,彻底打破循环链。
修改基类EntityWrapper,加入缓存池:
abstract class EntityWrapper { protected $collection; protected $entityClass; protected $cachedEntities; public $entity; // 新增$cachedEntities参数,默认初始化空集合 public function __construct($collection, $cachedEntities = null) { $this->collection = $collection; $this->cachedEntities = $cachedEntities ?? collect([]); $this->entity = $this->buildEntity(); } // 调整buildEntity,传递缓存池给makeEntity protected function buildEntity() { $tempEntity = new $this->entityClass; $Entities = collect([]); foreach ($this->collection as $model) { $Entities->push($this->makeEntity($tempEntity, $model, $this->cachedEntities)); } return $Entities; } // 更新抽象方法,加入$cachedEntities参数 abstract protected function makeEntity($entity, $model, $cachedEntities); }
修改UserEntityWrapper的makeEntity方法:
class UserEntityWrapper extends EntityWrapper { protected $entityClass = UserEntity::class; protected function makeEntity($userEntity, $model, $cachedEntities) { // 检查缓存,存在则直接返回 $cacheKey = 'user_' . $model->user_id; if ($cachedEntities->has($cacheKey)) { return $cachedEntities->get($cacheKey); } $userEntity->setId($model->user_id); $userEntity->setName($model->name); // 缓存当前实体 $cachedEntities->put($cacheKey, $userEntity); // 传递缓存池给Product包装器 $userEntity->setProducts((new ProductEntityWrapper($model->products, $cachedEntities))->entity); return $userEntity; } }
ProductEntityWrapper同理实现:
class ProductEntityWrapper extends EntityWrapper { protected $entityClass = ProductEntity::class; protected function makeEntity($productEntity, $model, $cachedEntities) { $cacheKey = 'product_' . $model->product_id; if ($cachedEntities->has($cacheKey)) { return $cachedEntities->get($cacheKey); } $productEntity->setId($model->product_id); $productEntity->setName($model->name); $cachedEntities->put($cacheKey, $productEntity); $userWrapper = new UserEntityWrapper(collect([$model->user]), $cachedEntities); $productEntity->setUser($userWrapper->entity->first()); return $productEntity; } }
这种方案能完整保留关联关系,同时避免重复包装和循环,适合需要完整实体关联的场景。
内容的提问来源于stack exchange,提问作者Babaktrad

