You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 9 Livewire报错:尝试读取数组的id属性问题排查

问题解决:Laravel 9 Livewire "attempt to read property 'id' on array" 错误

核心问题分析

  1. 对象与数组访问混用:代码中同时使用对象属性访问(->)和数组索引访问([])处理查询结果,导致类型不一致报错。
  2. 属性类型不统一:$polyclinics初始化为空数组,但后续赋值为Eloquent Collection对象,Livewire渲染时类型冲突触发错误。
  3. SQL硬编码参数:loadSchedule中直接拼接$id到SQL语句,存在注入风险且可能导致语法异常。
  4. dd调试的误导:dd()会终止代码执行,掩盖了后续可能的类型转换问题,移除后错误显现。

具体修复步骤

1. 统一查询结果的访问方式

在addItem方法中,Service::get()返回的是Eloquent Collection,元素为Model对象,需用对象属性访问:

public function addItem($id)
{
    $BPJS = "Bpjs";
    $this->stateService = Service::where('id', $id)->get();
    // 修复对象属性访问,替换数组索引
    if ($this->stateService[0]->clinic == 1) {
        if (stripos($this->stateService[0]->name, $BPJS)) {
            $this->isBPJS = 1;
            $this->loadPolyClinic($this->isBPJS);
        } else {
            $this->isBPJS = 0;
            $this->loadPolyClinic($this->isBPJS);
        }
    } else {
        $this->loadPolyClinic();
    }
}

2. 统一属性类型为Collection

初始化$polyclinics为Eloquent Collection,避免数组与对象的类型冲突:

// 组件属性初始化
public $state, $stateService, $schedules, $polyclinics;

public function mount($id)
{
    if ($id !== null) {
        $this->state = MonitorGetQueueItem::where('id', $id)->get();
        $this->monitorId = $id;
        $this->schedules = collect();
        $this->polyclinics = collect(); // 初始化为空Collection
    } else {
        $this->state = collect();
        $this->schedules = collect();
        $this->polyclinics = collect();
    }
}

// 修改loadPolyClinic的空值处理
public function loadPolyClinic($isBPJS = null)
{
    if ($isBPJS !== null) {
        // 原查询逻辑不变
    } else {
        $this->polyclinics = collect(); // 设为空Collection而非数组
    }
}

3. 修复SQL查询的参数绑定

重构loadSchedule中的查询,使用参数绑定替代硬编码,避免注入风险:

public function loadSchedule($id = null)
{
    if ($id) {
        $this->schedules = DB::connection("sqlsrv2")
            ->table(DB::raw("(SELECT
                    rtds.id AS schedule_id, 
                    mmmp.full_name,
                    mmp.name as polyclinic,
                    CASE WHEN rtds.bpjs_starttime-CAST(LTRIM(DATEDIFF(MINUTE, 0, CONVERT(VARCHAR(5),GETDATE(),108))) AS INT) <= 120 THEN 1 ELSE 0 END AS isStart,
                    CASE WHEN rtds.bpjs_starttime = 0 THEN 1 ELSE 0 END AS isBpjs,
                    RIGHT('0' + CAST(rtds.cash_startime / 60 AS VARCHAR),2)  + ':' +
                    RIGHT('0' + CAST(rtds.cash_startime % 60 AS VARCHAR),2) as [CashStart],
                    RIGHT('0' + CAST(rtds.cash_endtime / 60 AS VARCHAR),2)  + ':' +
                    RIGHT('0' + CAST(rtds.cash_endtime % 60 AS VARCHAR),2) as [CashEnd],
                    RIGHT('0' + CAST(rtds.insurance_starttime / 60 AS VARCHAR),2)  + ':' +
                    RIGHT('0' + CAST(rtds.insurance_starttime % 60 AS VARCHAR),2) as [InsuranceStart],
                    RIGHT('0' + CAST(rtds.insuranace_endtime / 60 AS VARCHAR),2)  + ':' +
                    RIGHT('0' + CAST(rtds.insuranace_endtime % 60 AS VARCHAR),2) as [InsuranceEnd],
                    RIGHT('0' + CAST(rtds.bpjs_starttime / 60 AS VARCHAR),2)  + ':' +
                    RIGHT('0' + CAST(rtds.bpjs_starttime % 60 AS VARCHAR),2) as [BpjsStart],
                    RIGHT('0' + CAST(rtds.bpjs_endtime / 60 AS VARCHAR),2)  + ':' +
                    RIGHT('0' + CAST(rtds.bpjs_endtime % 60 AS VARCHAR),2) as [BpjsEnd],
                    rtds.cash_allocated_offline_quota + rtds.insurance_allocated_offline_quota AS totalOffNb,
                    rtds.cash_used_offline_quota + rtds.insurance_used_offline_quota AS usedOffNb,
                    rtds.bpjs_allocated_offline_quota AS totalOffB, rtds.bpjs_used_offline_quota AS usedOffB,
                    rtds.cash_allocated_offline_quota + rtds.insurance_allocated_offline_quota + rtds.bpjs_allocated_offline_quota AS totalOff,
                    rtds.cash_used_offline_quota + rtds.insurance_used_offline_quota + rtds.bpjs_used_offline_quota AS usedOff
                    FROM reg_txn_doctor_schedule AS rtds 
                    LEFT OUTER JOIN mktg_mst_med_pract AS mmmp ON mmmp.id = rtds.doctor_id
                    LEFT OUTER JOIN mktg_mst_clinic AS mmc ON mmc.id = rtds.clinic_id
                    LEFT OUTER JOIN mktg_mst_polyclinic AS mmp ON mmp.id = mmc.polyclinic_id
                    WHERE 
                    CONVERT(DATE,rtds.[date]) = CONVERT(DATE,GETDATE())
                    AND rtds.[disabled] = 0
                    AND rtds.inactive = 0) as b"))
            ->where('b.polyclinic_id', $id) // 移到外部用参数绑定
            ->orderByRaw('b.CashStart ASC,b.CashEnd ASC')
            ->get();
    } else {
        $this->schedules = collect();
    }
}

4. 保持视图中属性访问一致

视图中继续使用$polyclinic->id和$polyclinic->name,因为现在$polyclinics始终是Collection对象:

@foreach ($polyclinics as $polyclinic)
<tr>
    <td wire:click="addSchedule({{ $polyclinic->id }})">
        <h6 class="fs--1">{{ $polyclinic->name }}</h6>
    </td>
</tr>
@endforeach

额外优化建议

  • 删除mount中无效代码:$this->state->toArray();未赋值给变量,无实际作用,可直接移除。
  • 添加空值判断:在视图遍历$polyclinics和$schedules前增加@empty分支,避免空集合导致的渲染异常。

内容的提问来源于stack exchange,提问作者J S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 06:12:02