Skip to content

Commit

Permalink
Merge pull request #27 from thybag/custom-intert-model
Browse files Browse the repository at this point in the history
Allow using custom inertModels in "bonus" relationships.
  • Loading branch information
thybag authored Nov 6, 2024
2 parents 7e5b513 + e6437a6 commit a18d009
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 40 deletions.
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
"require": {
"illuminate/database": "^8 || ^9 || ^10 | ^11"
},
"scripts": {
"test": [
"vendor/bin/phpunit"
],
"lint": [
"vendor/bin/phpcs"
]
},
"require-dev": {
"phpunit/phpunit": "^9.5 || ^9.6",
"orchestra/testbench": "^6",
Expand Down
39 changes: 15 additions & 24 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<php>
<env name="DB_CONNECTION" value="testing"/>
</php>
</phpunit>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory suffix=".php">src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<php>
<env name="DB_CONNECTION" value="testing"/>
</php>
</phpunit>
11 changes: 10 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A selection of weird & wonderful additional relation types for laravel's eloquen
Many of these are experimental and may behave in unexpected & none standard ways.

On the bright side - it's tested!
[![Build Status](https://travis-ci.org/thybag/bonus-laravel-relations.svg?branch=master)](https://travis-ci.org/thybag/bonus-laravel-relations)
![Build Status](https://github.com/thybag/bonus-laravel-relations/actions/workflows/test.yml/badge.svg)

All licensed under the MIT license.

Expand All @@ -17,6 +17,9 @@ All licensed under the MIT license.

The relation traits can also be added individually if you prefer that.

* Run tests with `composer test`
* Run lint with `composer lint`

# Relations

### BelongsToMorph
Expand Down Expand Up @@ -75,3 +78,9 @@ public function latestRating()
return $this->belongsToOne(Rating::class, 'shop_rating')->latest('created_at');
}
```

# InertModel

As the relationships `HasMethod` and `HasAggregate` don't return a traditional model from the database, a special model called type `InertModel` is used. This allows the relationship to fill the model with arbitrary attributes without the risk of causing unexpected behavior.

If you would like to use a custom InertModel rather than the one provided, create a config called `bonus-laravel-relationships.php` in your config folder, then set the value `inertModel` with the class path to the model you would like to use instead. I would recommend whatever model you use inherits from the base IntertModel to avoid unexpected behavior.
8 changes: 8 additions & 0 deletions src/Relations/HasAggregate.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\Relation;
use thybag\BonusLaravelRelations\Traits\InertModelTrait;

/**
* Get Aggregate query data as a relation.
* Allow lazy load and existence checks via whereHas.
*/
class HasAggregate extends Relation
{
use InertModelTrait;

// Key to link relation on
protected $relation_key = null;
// Aggregate sql - can be supplied by selectRaw instead.
Expand All @@ -28,6 +31,11 @@ class HasAggregate extends Relation
*/
public function __construct(Builder $query, Model $parent, string $relation_key, string $sql = null)
{
// The builder provided is for an instance of the real model. We want to swap this out for our
// inert model in order to return custom attributes based on the query. To do this we grab a copy
// of our InertModel and give it the table from the real one.
$query = $this->getInertModelInstance()->setTable($query->getModel()->getTable())->newQuery();

// Set relation key
$this->relation_key = $relation_key;
$this->aggregate_sql = $sql;
Expand Down
15 changes: 3 additions & 12 deletions src/Relations/HasMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use thybag\BonusLaravelRelations\Models\InertModel;
use Illuminate\Database\Eloquent\Relations\Relation;
use thybag\BonusLaravelRelations\Traits\InertModelTrait;

/**
* Allows a method to be returned as if it was a relation.
* Supports both callbacks & public functions.
*/
class HasMethod extends Relation
{
use InertModelTrait;

protected $method;
protected $parent;
protected $withs = [];
Expand All @@ -33,17 +35,6 @@ public function __construct(Model $parent, $method, $collection = false)
$this->returnCollection = $collection;
}

/**
* Get basic model instance to return as result.
*
* @return Model
*/
protected function getInertModelInstance()
{
// @todo allow custom model via config
return new InertModel();
}

/**
* Set the base constraints on the relation query.
*
Expand Down
5 changes: 2 additions & 3 deletions src/Traits/HasAggregateRelationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ trait HasAggregateRelationTrait
*/
public function hasAggregate(string $related, string $relation_key = null, string $sql = null)
{
$base = new $related;
$instance = new InertModel();
$instance = new $related;

if (empty($relation_key)) {
$relation_key = $this->getForeignKey();
}

return new HasAggregate($instance->setTable($base->getTable())->newQuery(), $this, $relation_key, $sql);
return new HasAggregate($instance->newQuery(), $this, $relation_key, $sql);
}
}
20 changes: 20 additions & 0 deletions src/Traits/InertModelTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace thybag\BonusLaravelRelations\Traits;

use thybag\BonusLaravelRelations\Models\InertModel;

trait InertModelTrait
{
/**
* Get basic model instance to return as result.
*
* @return Model
*/
protected function getInertModelInstance()
{
// Attempt to use provided inertModel if one is set.
$model = config('bonus-laravel-relationships.inertModel');
return !empty($model) ? new $model() : new InertModel();
}
}
21 changes: 21 additions & 0 deletions tests/HasAggregateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Orchestra\Testbench\TestCase;
use thybag\BonusLaravelRelations\Test\Models\Shop;
use thybag\BonusLaravelRelations\Test\Models\Product;
use thybag\BonusLaravelRelations\Test\Models\CustomInert;

class TestHasAggregate extends TestCase
{
Expand Down Expand Up @@ -130,4 +131,24 @@ public function testHasAggregateWhereHasOnAttributeProductTotalsViaRaw()
})->count();
$this->assertEquals(1, $count);
}

public function testHasAggregateWithCustomInertModel()
{
// Swap base model to be used by relationship
config(['bonus-laravel-relationships.inertModel' => CustomInert::class]);

$shop = Shop::create(['name' => 'CheeseExpress']);
$shop->products()->save(Product::make(['name' => 'Cheese', 'amount' => 5, 'value' => 5]));
$shop->products()->save(Product::make(['name' => 'Ham', 'amount' => 5, 'value' => 2]));
$shop->products()->save(Product::make(['name' => 'Eggs', 'amount' => 10, 'value' => 2]));

// right class
$this->assertEquals(CustomInert::class, get_class($shop->productTotals));
// Check custom model method exists
$this->assertEquals('hi', $shop->productTotals->hello());
// Still has right data
$this->assertEquals(20, $shop->productTotals->total_products);
$this->assertEquals(3, $shop->productTotals->unique_products);
$this->assertEquals(3, $shop->productTotals->average_product_value);
}
}
22 changes: 22 additions & 0 deletions tests/HasMethodTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace thybag\BonusLaravelRelations\Test;

use Orchestra\Testbench\TestCase;
use thybag\BonusLaravelRelations\Test\Models\CustomInert;
use thybag\BonusLaravelRelations\Test\Models\HasMethodTestModel;

class TestHasMethod extends TestCase
Expand Down Expand Up @@ -77,4 +78,25 @@ public function testHasMethodEagerLoad()
$test->load('callbackAsObject');
$this->assertEquals('one', $test->callbackAsObject->a);
}

public function testHasMethodWithCustomInertModel()
{
// Swap base model to be used by relationship
config(['bonus-laravel-relationships.inertModel' => CustomInert::class]);
$test = HasMethodTestModel::make([
'amount' => 5,
'value' => 2,
'product' => 'cake'
]);

// Check we got custom class
$this->assertEquals(CustomInert::class, get_class($test->asMethod));

// Check custom model method exists
$this->assertEquals('hi', $test->asMethod->hello());

// Check data still happy
$this->assertEquals('Cake', $test->asMethod->product);
$this->assertEquals(10, $test->asMethod->totalValue);
}
}
15 changes: 15 additions & 0 deletions tests/Models/CustomInert.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
namespace thybag\BonusLaravelRelations\Test\Models;

use thybag\BonusLaravelRelations\Models\InertModel;

/**
* Test mode to checkl replacement of inertModel in relationships
*/
class CustomInert extends InertModel
{
public function hello()
{
return "hi";
}
}

0 comments on commit a18d009

Please sign in to comment.