I hate to be that guy that answers his own question, but I hate to be the guy that doesn't share what worked more.
I ended up creating a view that does the join and check at the data level. That made this a simple join
and an ifnull
and then changing my model to use that table instead of the base. That works greating for reading and querying against, but obviously not for CRUD operations.
Since this view isn't updatable I ended up tapping into the models event system to switch the model to the base table when preparing to do any writes and then back when it's done. I went with the observer pattern since it keeps everything nice and clean.
The Observer:
<?php
namespace App\Models\Observers;
use App\Models\Contracts\IPersistTo;
/**
* Class PersistToObserver
*
*
* @package App\Models\Observers
*/
class PersistToObserver
{
protected function useReadTable(IPersistTo $model)
{
$model->setTable($model->getReadTable());
}
protected function useWriteTable(IPersistTo $model)
{
$model->setTable($model->getWriteTable());
}
/**
* Switch the model to use the write table before it goes to the DB
* @param IPersistTo $model
*/
public function creating(IPersistTo $model)
{
$this->useWriteTable($model);
}
/**
* Switch the model to use the write table before it goes to the DB
* @param IPersistTo $model
*/
public function updating(IPersistTo $model)
{
$this->useWriteTable($model);
}
/**
* Switch the model to use the write table before it goes to the DB
* @param IPersistTo $model
*/
public function saving(IPersistTo $model)
{
$this->useWriteTable($model);
}
/**
* Switch the model to use the write table before it goes to the DB
* @param IPersistTo $model
*/
public function deleting(IPersistTo $model)
{
$this->useWriteTable($model);
}
/**
* Switch the model to use the write table before it goes to the DB
* @param IPersistTo $model
*/
public function restoring(IPersistTo $model)
{
$this->useWriteTable($model);
}
/**
* Model has been written to the BD, switch back to the read table
* @param IPersistTo $model
*/
public function created(IPersistTo $model)
{
$this->useReadTable($model);
}
/**
* Model has been written to the BD, switch back to the read table
* @param IPersistTo $model
*/
public function updated(IPersistTo $model)
{
$this->useReadTable($model);
}
/**
* Model has been written to the BD, switch back to the read table
* @param IPersistTo $model
*/
public function saved(IPersistTo $model)
{
$this->useReadTable($model);
}
/**
* Model has been written to the BD, switch back to the read table
* @param IPersistTo $model
*/
public function deleted(IPersistTo $model)
{
$this->useReadTable($model);
}
/**
* Model has been written to the BD, switch back to the read table
* @param IPersistTo $model
*/
public function restored(IPersistTo $model)
{
$this->useReadTable($model);
}
}
The IPersistTo contract/interface
<?php
namespace App\Models\Contracts;
interface IPersistTo
{
/**
* @return string - the name of the table to read from (should be the same as the default $table)
*/
public function getReadTable();
/**
* @return string - the name of the table to write to
*/
public function getWriteTable();
/**
* Set the table associated with the model. Fulfilled by Model.
*
* @param string $table
* @return $this
*/
public function setTable($table);
}
Setup in my model (greatly truncated to be just the relavant info)
class Game extends Model implements IPersistTo {
protected $table = 'game_with_album_fallback';
/**
* @return string - the name of the table to read from (should be the same as the default $table)
*/
public function getReadTable()
{
return 'game_with_album_fallback';
}
/**
* @return string - the name of the table to write to
*/
public function getWriteTable()
{
return 'games';
}
}
And finally to wire all of this together add the following line to the AppServiceProvider's boot method:
Game::Observe(PersistToObserver::class);