Trying to get property ‘address’ of non-object
Laravelで複数のモデル/テーブルを関連付けて表示させようとしたところ、「オブジェクトでもないものから属性を得ようとした」というエラーが表示された。
エラー発生の流れ
以下のような構造の顧客とメールアドレスのテーブルを(マイグレーションで)準備して、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
mysql> DESCRIBE customers; +-------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------+------+-----+---------+----------------+ | id | bigint unsigned | NO | PRI | NULL | auto_increment | | name | varchar(255) | NO | | NULL | | +-------+-----------------+------+-----+---------+----------------+ 2 rows in set (0.01 sec) mysql> DESCRIBE emails; +-------------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+-----------------+------+-----+---------+----------------+ | id | bigint unsigned | NO | PRI | NULL | auto_increment | | customer_id | bigint unsigned | NO | MUL | NULL | | | address | varchar(255) | YES | | NULL | | +-------------+-----------------+------+-----+---------+----------------+ 3 rows in set (0.01 sec) |
以下の様にデータを準備して、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
mysql> SELECT * FROM customers; +----+-----------+ | id | name | +----+-----------+ | 1 | customer1 | | 2 | customer2 | | 3 | customer3 | +----+-----------+ 3 rows in set (0.00 sec) mysql> SELECT * FROM emails; +----+-------------+--------------------+ | id | customer_id | address | +----+-------------+--------------------+ | 1 | 1 | customer1@mail.com | | 2 | 2 | customer2@mail.com | +----+-------------+--------------------+ 2 rows in set (0.00 sec) |
モデルでリレーションを定義して、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# Customerモデルの内容 class Customer extends Model { public $timestamps = false; public function email() { return $this->hasOne('App\Email'); } } # Emailモデルの内容 class Email extends Model { public $timestamps = false; public function customer() { return $this->belongsTo('App\Customer'); } } |
コントローラーでCustomer
の全データをビューに渡して、
1 2 3 4 5 6 7 8 |
public function query_relation() { $customers = Customer::all(); return view('queries.query_relation', [ 'title' => 'Query Test', 'customers' => $customers, ]); } |
ビューで各customers
に関連付けられたemails
のaddress
を読もうとすると、
1 2 3 |
@foreach ($customers as $customer) <p>{{ $customer->email->address }}</p> @endforeach |
ブラウザーにエラーが表示された。
1 2 3 |
ErrorException Trying to get property 'address' of non-object (View: ....resources/views/queries/query_relation.blade.php) http://localhost:3000/query_relation |
原因
以下の様に、customer3
に対してはemails
のレコードがなく、戻り値がnull
となるので、そこからaddress
を得ることができないため。
1 2 3 4 5 6 7 8 9 |
mysql> SELECT * FROM customers LEFT JOIN emails ON customers.id = customer_id; +----+-----------+------+-------------+--------------------+ | id | name | id | customer_id | address | +----+-----------+------+-------------+--------------------+ | 1 | customer1 | 1 | 1 | customer1@mail.com | | 2 | customer2 | 2 | 2 | customer2@mail.com | | 3 | customer3 | NULL | NULL | NULL | +----+-----------+------+-------------+--------------------+ 3 rows in set (0.00 sec) |
対処法
@issetディレクティブ
@isset
で参照結果がnull
かどうかを判定。
1 2 3 4 5 6 7 8 |
@foreach ($customers as $customer) @isset($customer->email) <p> {{ $customer->name}}: {{ $customer->email['address'] }} </p> @endisset @endforeach |
emails
のレコードを持たないcusutomers
のレコードは、@isset
によってnull
が無視されるため表示されないが、email
に関する2行目だけを@isset
で囲めばname
だけは表示される。
1 2 3 |
customer1: customer1@mail.com customer2: customer2@mail.com |
連想配列の要素として指定
オブジェクトの属性ではなく、連想配列の要素として読み出す。
1 2 3 4 5 6 |
@foreach ($customers as $customer) <p> {{ $customer->name}}: {{ $customer->email['address'] }} </p> @endforeach |
参照しようとする配列がnull
の場合、要素指定して読みだした結果はnull
となる。customers
のレコードは全て表示され、emails
のレコードがnull
の場合はemails
の情報だけ表示されない。
1 2 3 4 5 |
customer1: customer1@mail.com customer2: customer2@mail.com customer3: |
optional()ヘルパー
optional()
の引数の内容がnull
の場合、その属性参照はエラーとならずに属性の参照結果がnull
となる。
1 2 3 4 5 6 |
@foreach ($customers as $customer) <p> {{ $customer->name}}: {{ optional($customer->email)->address }} </p> @endforeach |
ブラウザーの表示結果は上と同じ。
1 2 3 4 5 |
customer1: customer1@mail.com customer2: customer2@mail.com customer3: |
なお、optional()
は1段先の参照結果までは保証できるが、3段目以降には作用しない。必要ならoptional()
を入れ子にしなければならない。
null合体演算子との組み合わせ
PHPのnull合体演算子を使って、結果がnull
の場合の内容を定義できる。
1 2 3 4 5 6 |
@foreach ($customers as $customer) <p> {{ $customer->name}}: {{ optional($customer->email)->address ?? '未定義です' }} </p> @endforeach |
以下の様に、ブラウザー上で適切な表現が可能になる。
1 2 3 4 5 |
customer1: customer1@mail.com customer2: customer2@mail.com customer3: 未定義です |