Phil
Phil

Reputation: 2236

laravel ORM how to access hasOne related table in view

I know this appears to be a simple question but after looking all through the official docs as well as 2 tutorials, it is not clear to me. There is conflicting examples of using foreign_keys or not as well as using the class Model:all() in different locations and for use with hasMany relationss... It seems a pretty simple thing to do so I am frustrated I cannot find the answer easily. I know the answer is probably there someone but in several pieces that is not apparent to a beginner since there is conflicting examples everywhere and i'm even finding some commands that don't work (verified by other posts) but are still documented...so please someone clarify this simple question for me so things become less muddy.

How do I access the related table in my view? I get error "Trying to get property of non-object (View: " When I run this in my view

@foreach $programs as $program
     {{{$program->program_segment->name}}}
@endforeach

program is the model which works to show non-related data. program_segment is the function defined in my program model that has a has_One relation to the related table that I want to access.

Related files:

Model:  
class ProgramEvent extends Eloquent {

        protected $table = 'program_events';
        public $timestamps = true;

        public function program_segment()
        {
            return $this->hasOne('ProgramSegment');
        }
}
class ProgramSegment extends Eloquent {

    protected $table = 'program_segments';
    public $timestamps = true;

}


Migrations:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateProgramEventsTable extends Migration {

    public function up()
    {
        Schema::create('program_events', function(Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->string('name', 32);
            $table->date('date');
            $table->time('start_time');
            $table->time('end_time');
            $table->string('location', 32);
        });
    }

    public function down()
    {
        Schema::drop('program_events');
    }
}

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateProgramSegmentsTable extends Migration {

    public function up()
    {
        Schema::create('program_segments', function(Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->string('name', 32);
            $table->text('description');
            $table->decimal('cost', 3,2);
            $table->integer('program_event_id')->unsigned()->nullable();
            $table->foreign('program_event_id')->references('id')->on('program_events')->onDelete('cascade');

        });
    }

    public function down()
    {
        Schema::drop('program_segments');
    }
}

Controller:
    public function index()
    {
        $programs = ProgramEvent::with('program_segment')->get();
        return View::make('programs.index', compact('programs'));

    }

Upvotes: 1

Views: 2188

Answers (2)

damiani
damiani

Reputation: 7371

The root of the problem here is the lack of foreign key. You always need a foreign key in a relation, otherwise your database has no way to connect one record to another. The ORM has no way of knowing what data is related without a foreign key.

If your foreign key is named to follow the convention model_id where model is the name of the model in which you define the relationship, then you do not need to specify the name of the foreign key in the hasOne statement...but the foreign key still has to exist in the database. So if your foreign key is named program_event_id, your hasOne statement will work as-is. (In your second error message, you can see that it is expecting the column program_event_id.) If, however, you name your foreign key something else, you would add it to the hasOne definition, i.e. return $this->hasOne('ProgramSegment', 'my_foreign_key);'

Adding your foreign key in your migration requires two statements (in this order):

$table->integer('program_event_id')->unsigned();
$table->foreign('program_event_id')->references('id')->on('progam_events');

(Adding ->index() at the end of the first line is not necessary, but the unsigned() is.)


Beyond this, some of your errors might be caused by typos in your names. Your original 'non-object' error might be caused because you controller is passing 'programs' to your view, but in your view, you reference $program->program_segment->name. As far as your view is concerned, $programs exists, but $program does not.

Your foreign key constraint error might also be caused by a typo, since you define your table as progam_events; if this is intentional, then your foreign key definition in your migration must also reference progam_events and not program_events.


Once you get the foreign key set up correctly, using the with operator isn't necessary in order to access your relation—you can get the related model the way you originally did, with $program->program_segment->name. BUT, it is good practice, if you plan on looping over the result set of $program and displaying each related program_segment->name. Using ::with will bring along all the related records using just 2 queries, so you don't need 1 query for each related record. This avoids something called the "N+1" problem, and makes your query more efficient.


Finally, all this might be unnecessary anyway...if your relationship truly is a one-to-one relation (so that only one event has only one segment), you can, under most circumstances, simply combine the two tables and eliminate the hasOne relation altogether. There are some instances where it makes sense to segment one-to-one data into two tables, but those have more to do with performance than data structure. I suspect, though, that the relationship between your two tables should be a one-to-many relation.


Once you get your relationships set up correctly, make sure you add a check to your foreach loop to make sure that a program_segment exists for a given program_event before you try to access it; otherwise, if you hit a program that doesn't have a related program_segment, you'll end up with a "Trying to get property of non-object" error:

@foreach $programs as $program
    @if(!is_null($program->program_segment)) 
        {{{$program->program_segment->name}}}
    @endif
@endforeach

Upvotes: 1

Vit Kos
Vit Kos

Reputation: 5755

It seems that you need to load the relations when quering the model. You can load them using "with" operator:

Controller:

public function index()
{
    $programs = ProgramEvent::with('program_segment')->get();
    return View::make('programs.index', compact('programs'));
}

UPD: Also, it's always a good idea to specify directly the columns that are used in relating the tables. Here what the docs state:

return $this->hasOne('Model', 'foreign_key', 'local_key');

Also, I think you are missing a column in program_segmet for holding the parent id (program_event_id). Add it's good to make a foreign key, but not necessary.

Upvotes: 2

Related Questions