Fix PHP ActiveRecord Has Many Association Errors

Hello there! Today, I want to share an interesting challenge I faced while working on a simple TV Show/Season management project using php-activerecord. I encountered a fatal error that left me scratching my head:

Fatal error: Type of App\Models\TvShow::$has_many must be array (as in class ActiveRecord\Model)

After some digging, I figured out why this happens and how to fix it. Let’s walk through my journey, see the corrected code, and then explore how you can leverage php-activerecord for creating, reading, updating, and deleting your data in a very straightforward manner.

Why the Error Occurs

I learned that the php-activerecord library expects all relationship definitions (like $has_many$belongs_to$has_one$has_and_belongs_to_many) to be declared as an array of arrays. That means each relationship should be wrapped in its own array. For instance:

static $has_many = [
    ['seasons']
];

However, I initially wrote something like:

static $has_many = [
    'seasons' => []
];

That’s when everything broke because 'seasons' => [] doesn’t match what php-activerecord expects. It’s supposed to look more like [['seasons']] or [['seasons', 'class_name' => 'Season']] if you need extra options like foreign keys or custom class names.

So, that’s the root cause of the fatal error. Thankfully, it’s also an easy fix.

Corrected Code

TvShow.php

<?php
namespace App\Models;

require 'vendor/autoload.php';

use ActiveRecord\Model;

class TvShow extends Model
{
    // Correct usage: array of arrays
    static $has_many = [
        ['seasons']
    ];
}

Season.php

<?php
namespace App\Models;

require 'vendor/autoload.php';

use ActiveRecord\Model;

class Season extends Model
{
    // Correct usage: array of arrays
    static $belongs_to = [
        ['tv_show']
    ];
}

Optional – Adding Relationship Options

If your seasons table uses a column named something else (instead of the default tv_show_id) or if you want to specify the class name, you can write:

static $has_many = [
    ['seasons', 'class_name' => 'App\\Models\\Season', 'foreign_key' => 'tv_show_id']
];

Adding Practical Functionality

Now that the relationship syntax is fixed, here’s how I actually used it in my project.

Example Usage

use App\Models\TvShow;
use App\Models\Season;

// 1. Create a new TV show
$show = new TvShow();
$show->title = "My Awesome Show";
$show->created_at = date('Y-m-d H:i:s');
$show->save();

// 2. Create a new Season and attach it to a TV Show
$season1 = new Season();
$season1->number = 1;
$season1->tv_show_id = $show->id; 
$season1->save();

// 3. Retrieve the TV show with all its seasons
$retrievedShow = TvShow::find($show->id);
echo "Show Title: " . $retrievedShow->title . PHP_EOL;

foreach ($retrievedShow->seasons as $season) {
    echo "Season #: " . $season->number . PHP_EOL;
}

Here, the $show variable is an instance of the TvShow model. Once you create it, you can attach Season instances to it by referencing the foreign key (tv_show_id).

Finding and Updating Data

// Find a TvShow by primary key
$tvShow = TvShow::find(1);

// Update title
$tvShow->title = "Updated Title";
$tvShow->save();

// Find multiple shows with conditions
$shows = TvShow::find('all', [
    'conditions' => ['title LIKE ?', '%Awesome%']
]);

foreach ($shows as $s) {
    echo $s->title . PHP_EOL;
}

This is handy if you want to refine your searches (for example, searching by partial titles).

Deleting Records

$season = Season::find(10);
$season->delete();

You can easily remove records from your database once you have the object

Tips and Best Practices

Use Namespaced Classes

I placed mine under App\Models. Make sure your Composer autoload is configured properly for these namespaces.

Migrations / DB Setup

Ensure your database has the right tables and columns:

  • tv_shows might have columns like idtitlecreated_at.
  • seasons might have idnumbertv_show_id, etc.

Relationship Options

If your primary or foreign keys differ from id and tv_show_id, specify them in the relationship arrays. Example:

static $has_many = [
    ['seasons', 'foreign_key' => 'my_custom_foreign_key']
];

Validation

php-activerecord also supports built-in validations, for example:

static $validates_presence_of = [
    ['title', 'message' => 'Title cannot be blank']
];

Timestamps

If you have created_at and updated_at columns in your tables, php-activerecord can automatically populate them when you enable:

static $timestamps = true;

Final Thoughts

The error stemmed from a small, yet critical detail in relationship definitions. Once fixed, php-activerecord becomes a smooth and intuitive ORM for your projects. Leverage validations, timestamps, and proper namespace setups for a clean codebase. Add migrations or schema definitions to safeguard data integrity.

Related blog posts