0

In my app I have Games and Tournaments, with a Game optionally belonging to a Tournament. Both the Game and the Tournament can have a belongsTo relationship with a PhotoAlbum.

What I want now is if a Game doesn't have a PhotoAlbum it will try and return it's Tournament's PhotoAlbum (if those two things exist). I also want it to take this route if I do a query using withCount.

I tried adding the checks into the Game's album relationship but that didn't seem to work:

class Game extends Model {

    public function album()
    {
        if ($this->album_id == null && $this->tournament_id != null ) {
            return $this->tournament->album();
        }

        return $this->belongsTo('App\Models\PhotoAlbum');
    }
}

Needs to work with withCount() in the query builder , and not result in me having to write a whole bunch of new if/then checks throughout the existing code.

--

Less concerned about the withCount working since I can make a custom property getter that works on album_count.

aron.duby
  • 2,072
  • 2
  • 15
  • 21

3 Answers3

0

You may do like this below

$game = Game::find(1);

if(! isset($game->album)) { // you can different if's here to check if not album exist
   return $game->tournament->album;
} 
Jesus Erwin Suarez
  • 1,571
  • 16
  • 17
0

Did you try a HasOneThrough relation?

public function tournamentAlbum(): HasOneThrough
{
    return $this->hasOneThrough(Album::class, Tournament::class);
}

I guess there is no easy way to augment the current relation and prevent refactoring. This is because eager loads are partially resolved using empty model instances.

Olivenbaum
  • 971
  • 1
  • 6
  • 17
0

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);
aron.duby
  • 2,072
  • 2
  • 15
  • 21