Closed23

ランキング収集機能の実装

いのはいのは

ランキング収集機能を実装したのでご紹介。

いのはいのは

何種類のランキングを保存したいか?
=> 多くても数十種類くらい
=> unsignedTinyIntegerの範囲(0~255)で保存できればよい
=> 実際には、1~255の範囲でランキングIDを割り当てる

上記の内容をコードで表現すると、以下のようになった。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Entities;

final readonly class RankingId
{
    private const int UINT8_MAX = 255;

    public const int MIN = 1;

    public const int MAX = self::UINT8_MAX;

    public function __construct(
        private int $value,
    ) {
        if ($value < self::MIN) {
            throw new \InvalidArgumentException('The provided value must be greater than or equal to '.self::MIN.'.');
        }
        if ($value > self::MAX) {
            throw new \InvalidArgumentException('The provided value must be less than or equal to '.self::MAX.'.');
        }
    }

    public function value(): int
    {
        return $this->value;
    }
}
いのはいのは

RankingIdクラスについて、次のように動作することをテストしたい。
・入力された値が1~255の範囲内であれば、正常に初期化されること
・下限値1を超えた値が入力されると、例外(InvalidArgumentException)をスローすること
・上限値255を超えた値が入力されると、例外(InvalidArgumentException)をスローすること
・valueメソッドが意図した値を返すこと

上記の内容をコードで表現すると、以下のようになった。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Tests\Unit\Entities;

use Lulubell\Ranking\Entities\RankingId;
use PHPUnit\Framework\TestCase;

final class RankingIdTest extends TestCase
{
    public function test_instantiate_with_value_at_min(): void
    {
        $this->assertInstanceOf(
            RankingId::class,
            new RankingId(RankingId::MIN),
        );
    }

    public function test_instantiate_with_value_at_max(): void
    {
        $this->assertInstanceOf(
            RankingId::class,
            new RankingId(RankingId::MAX),
        );
    }

    public function test_instantiate_with_value_less_than_min(): void
    {
        $this->expectException(\InvalidArgumentException::class);

        new RankingId(RankingId::MIN - 1);
    }

    public function test_instantiate_with_value_greater_than_max(): void
    {
        $this->expectException(\InvalidArgumentException::class);

        new RankingId(RankingId::MAX + 1);
    }

    public function test_value(): void
    {
        $this->assertSame(
            1,
            new RankingId(1)->value(),
        );
    }
}
いのはいのは

ちなみに、ランキングIDを割り当ては以下のようにした。

<?php

declare(strict_types=1);

namespace App\Entities\Ranking;

use App\Repositories\Ranking\XxxxRankingRepository;
use App\Repositories\Ranking\YyyyRankingRepository;
use Lulubell\Ranking\Entities\RankingId;

enum RankingList: string
{
    case XxxxRanking = XxxxRankingRepository::class;
    case YyyyRanking = YyyyRankingRepository::class;

    public function id(): RankingId
    {
        return new RankingId(match ($this) {
            self::XxxxRanking => 1,
            self::YyyyRanking => 2,
        });
    }
}
いのはいのは

RankingRepositoryインターフェイスの実装クラスで

RankingList::from(self::class)->id()

あるいは

RankingList::from(static::class)->id()

のように利用する想定。

いのはいのは

ランク入りした作品に対して、次のような事柄は最低限知っておきたい。
・何のランキング?
・順位は何位?
・何というタイトル?
・いつ収集した?

また、それ以外の事柄(作者やリンクなど)もオプションで保存できるようにしたい。

上記の内容をコードで表現すると、以下のようになった。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Entities;

use Carbon\CarbonImmutable;

final readonly class RankedTitle
{
    public function __construct(
        private RankingId $rankingId,
        private Rank $rank,
        private string $title,
        private CarbonImmutable $storedAt,
        private ?int $id = null,
        /** @var ?array<string, mixed> */
        private ?array $params = null,
    ) {}

    public function id(): ?int
    {
        return $this->id;
    }

    public function rankingId(): int
    {
        return $this->rankingId->value();
    }

    public function rank(): int
    {
        return $this->rank->value();
    }

    public function title(): string
    {
        return $this->title;
    }

    public function storedAt(): CarbonImmutable
    {
        return $this->storedAt;
    }

    /** @return ?array<string, mixed> */
    public function params(): ?array
    {
        return $this->params;
    }

    /** @return array{id: ?int, ranking_id: int, rank: int, title: string, stored_at: string, params: ?array<string, mixed>} */
    public function toArray(): array
    {
        return [
            'id' => $this->id(),
            'ranking_id' => $this->rankingId(),
            'rank' => $this->rank(),
            'title' => $this->title(),
            'stored_at' => $this->storedAt()->toIso8601String(),
            'params' => $this->params(),
        ];
    }
}
いのはいのは

RankedTitleクラスについて、次のように動作することをテストしたい。
・各メソッドが意図した値を返すこと

上記の内容をコードで表現すると、以下のようになった。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Tests\Unit\Entities;

use Carbon\CarbonImmutable;
use Lulubell\Ranking\Entities\Rank;
use Lulubell\Ranking\Entities\RankedTitle;
use Lulubell\Ranking\Entities\RankingId;
use PHPUnit\Framework\TestCase;

final class RankedTitleTest extends TestCase
{
    private RankingId $rankingId;

    private CarbonImmutable $storedAt;

    protected function setUp(): void
    {
        parent::setUp();

        CarbonImmutable::setTestNow(CarbonImmutable::now());

        $this->rankingId = new RankingId(1);
        $this->storedAt = CarbonImmutable::now();
    }

    protected function tearDown(): void
    {
        CarbonImmutable::setTestNow();

        parent::tearDown();
    }

    public function test_values(): void
    {
        $rankedTitle = new RankedTitle(
            rankingId: $this->rankingId,
            rank: new Rank(1),
            title: 'title_1',
            storedAt: $this->storedAt,
            id: 1,
            params: [
                'author' => 'author_1',
                'link' => 'link_1',
            ],
        );

        $this->assertSame(
            1,
            $rankedTitle->id(),
        );

        $this->assertSame(
            1,
            $rankedTitle->rankingId(),
        );

        $this->assertSame(
            1,
            $rankedTitle->rank(),
        );

        $this->assertSame(
            'title_1',
            $rankedTitle->title(),
        );

        $this->assertEquals(
            CarbonImmutable::now(),
            $rankedTitle->storedAt(),
        );

        $this->assertSame(
            [
                'author' => 'author_1',
                'link' => 'link_1',
            ],
            $rankedTitle->params(),
        );
    }

    public function test_to_array(): void
    {
        $this->assertSame(
            [
                'id' => null,
                'ranking_id' => 1,
                'rank' => 1,
                'title' => 'title_1',
                'stored_at' => CarbonImmutable::now()->toIso8601String(),
                'params' => null,
            ],
            new RankedTitle($this->rankingId, new Rank(1), 'title_1', $this->storedAt)->toArray(),
        );
    }
}
いのはいのは

「順位」もクラスとして表現したほうがいいように思うので、
順番は前後してしまうが、このタイミングで実装する(RankedTitleとRankedTitleTestは対応済み)

いのはいのは

最大で何位までの順位を保存したいか?
=> 最大で100位までにしたい

上記の内容をコードで表現すると、以下のようになった。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Entities;

final readonly class Rank
{
    public const int MIN = 1;

    public const int MAX = 100;

    public function __construct(
        private int $value,
    ) {
        if ($value < self::MIN) {
            throw new \InvalidArgumentException('The provided value must be greater than or equal to '.self::MIN.'.');
        }
        if ($value > self::MAX) {
            throw new \InvalidArgumentException('The provided value must be less than or equal to '.self::MAX.'.');
        }
    }

    public function value(): int
    {
        return $this->value;
    }
}
いのはいのは

Rankクラスについて、次のように動作することをテストしたい。
・入力された値が1~100の範囲内であれば、正常に初期化されること
・下限値1を超えた値が入力されると、例外(DomainException)をスローすること
・上限値100を超えた値が入力されると、例外(DomainException)をスローすること
・valueメソッドが意図した値を返すこと

上記の内容をコードで表現すると、以下のようになった。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Tests\Unit\Entities;

use Lulubell\Ranking\Entities\Rank;
use PHPUnit\Framework\TestCase;

final class RankTest extends TestCase
{
    public function test_instantiate_with_value_at_min(): void
    {
        $this->assertInstanceOf(
            Rank::class,
            new Rank(Rank::MIN),
        );

    }

    public function test_instantiate_with_value_at_max(): void
    {
        $this->assertInstanceOf(
            Rank::class,
            new Rank(Rank::MAX),
        );
    }

    public function test_instantiate_with_value_less_than_min(): void
    {
        $this->expectException(\InvalidArgumentException::class);

        new Rank(Rank::MIN - 1);
    }

    public function test_instantiate_with_value_greater_than_max(): void
    {
        $this->expectException(\InvalidArgumentException::class);

        new Rank(Rank::MAX + 1);
    }

    public function test_value(): void
    {
        $this->assertSame(
            1,
            new Rank(1)->value(),
        );
    }
}
いのはいのは
<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Entities;

final readonly class RankedTitleList
{
    private const int MIN_COUNT = 1;

    private const int MAX_COUNT = Rank::MAX - Rank::MIN + 1;

    /** @var list<RankedTitle> */
    private array $rankedTitles;

    private int $count;

    /** @no-named-arguments */
    public function __construct(
        RankedTitle ...$rankedTitles,
    ) {
        $this->rankedTitles = $rankedTitles;
        $this->count = count($rankedTitles);

        if ($this->count < self::MIN_COUNT) {
            throw new \LengthException('The number of ranked titles provided must be greater than or equal to '.self::MIN_COUNT.'.');
        }
        if ($this->count > self::MAX_COUNT) {
            throw new \LengthException('The number of ranked titles provided must be less than or equal to '.self::MAX_COUNT.'.');
        }
        if (! $this->hasSameRankingId()) {
            throw new \DomainException('The provided ranked titles must have the same ranking ID.');
        }
        if (! $this->hasSequentialRanks()) {
            throw new \DomainException('The provided ranked titles must have sequential ranks.');
        }
        if (! $this->hasUniqueTitles()) {
            throw new \DomainException('The provided ranked titles must have unique titles.');
        }
        if (! $this->hasSameStoredAt()) {
            throw new \DomainException('The provided ranked titles must have the same storedAt value.');
        }
    }

    /** @return list<RankedTitle> */
    public function all(): array
    {
        return $this->rankedTitles;
    }

    /** @return list<array{id: ?int, ranking_id: int, rank: int, title: string, stored_at: string, params: ?array<string, mixed>}> */
    public function toArray(): array
    {
        return array_map(
            fn (RankedTitle $rankedTitle) => $rankedTitle->toArray(),
            $this->rankedTitles,
        );
    }

    public function count(): int
    {
        return $this->count;
    }

    private function hasSameRankingId(): bool
    {
        return count(array_unique(array_map(
            fn (RankedTitle $rankedTitle) => $rankedTitle->rankingId(),
            $this->rankedTitles,
        ))) === 1;
    }

    private function hasSequentialRanks(): bool
    {
        return ($ranks = array_map(
            fn (RankedTitle $rankedTitle) => $rankedTitle->rank(),
            $this->rankedTitles,
        )) === range(Rank::MIN, max(Rank::MIN, ...$ranks));
    }

    private function hasUniqueTitles(): bool
    {
        return count(array_unique(array_map(
            fn (RankedTitle $rankedTitle) => $rankedTitle->title(),
            $this->rankedTitles,
        ))) === $this->count;
    }

    private function hasSameStoredAt(): bool
    {
        return count(array_unique(array_map(
            fn (RankedTitle $rankedTitle) => $rankedTitle->storedAt(),
            $this->rankedTitles,
        ))) === 1;
    }
}
いのはいのは
<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Tests\Unit\Entities;

use Carbon\CarbonImmutable;
use Lulubell\Ranking\Entities\Rank;
use Lulubell\Ranking\Entities\RankedTitle;
use Lulubell\Ranking\Entities\RankedTitleList;
use Lulubell\Ranking\Entities\RankingId;
use PHPUnit\Framework\TestCase;

final class RankedTitleListTest extends TestCase
{
    private RankingId $rankingId;

    private CarbonImmutable $storedAt;

    private array $rankedTitles;

    protected function setUp(): void
    {
        parent::setUp();

        CarbonImmutable::setTestNow(CarbonImmutable::now());

        $this->rankingId = new RankingId(1);
        $this->storedAt = CarbonImmutable::now();

        $this->rankedTitles = [
            new RankedTitle($this->rankingId, new Rank(1), 'title_1', $this->storedAt),
            new RankedTitle($this->rankingId, new Rank(2), 'title_2', $this->storedAt),
        ];
    }

    protected function tearDown(): void
    {
        CarbonImmutable::setTestNow();

        parent::tearDown();
    }

    public function test_instantiate_with_ranked_titles_at_min_count(): void
    {
        $this->assertInstanceOf(
            RankedTitleList::class,
            new RankedTitleList(
                new RankedTitle($this->rankingId, new Rank(Rank::MIN), 'title_'.Rank::MIN, $this->storedAt),
            ),
        );
    }

    public function test_instantiate_with_ranked_titles_at_max_count(): void
    {
        $this->assertInstanceOf(
            RankedTitleList::class,
            new RankedTitleList(
                ...array_map(
                    fn (int $i) => new RankedTitle($this->rankingId, new Rank($i), "title_{$i}", $this->storedAt),
                    range(Rank::MIN, Rank::MAX),
                ),
            ),
        );
    }

    public function test_instantiate_with_ranked_titles_less_than_min_count(): void
    {
        $this->expectException(\LengthException::class);

        new RankedTitleList;
    }

    public function test_instantiate_with_ranked_titles_greater_than_max_count(): void
    {
        $this->expectException(\LengthException::class);

        new RankedTitleList(
            ...array_map(
                fn (int $i) => new RankedTitle($this->rankingId, new Rank(Rank::MIN), 'title_'.Rank::MIN, $this->storedAt),
                range(Rank::MIN, Rank::MAX + 1),
            ),
        );
    }

    public function test_instantiate_with_ranked_titles_having_non_same_ranking_id(): void
    {
        $this->expectException(\DomainException::class);

        new RankedTitleList(
            new RankedTitle(new RankingId(1), new Rank(1), 'title_1', $this->storedAt),
            new RankedTitle(new RankingId(2), new Rank(2), 'title_2', $this->storedAt),
        );
    }

    public function test_instantiate_with_ranked_titles_having_non_sequential_ranks(): void
    {
        $this->expectException(\DomainException::class);

        new RankedTitleList(
            new RankedTitle($this->rankingId, new Rank(1), 'title_1', $this->storedAt),
            new RankedTitle($this->rankingId, new Rank(3), 'title_3', $this->storedAt),
        );
    }

    public function test_instantiate_with_ranked_titles_having_non_unique_titles(): void
    {
        $this->expectException(\DomainException::class);

        new RankedTitleList(
            new RankedTitle($this->rankingId, new Rank(1), 'title', $this->storedAt),
            new RankedTitle($this->rankingId, new Rank(2), 'title', $this->storedAt),
        );
    }

    public function test_instantiate_with_ranked_titles_having_non_same_stored_at(): void
    {
        $this->expectException(\DomainException::class);

        new RankedTitleList(
            new RankedTitle($this->rankingId, new Rank(1), 'title_1', $this->storedAt),
            new RankedTitle($this->rankingId, new Rank(2), 'title_2', $this->storedAt->addSeconds()),
        );
    }

    public function test_all(): void
    {
        $this->assertSame(
            $this->rankedTitles,
            new RankedTitleList(...$this->rankedTitles)->all(),
        );
    }

    public function test_to_array(): void
    {
        $this->assertSame(
            [
                [
                    'id' => null,
                    'ranking_id' => 1,
                    'rank' => 1,
                    'title' => 'title_1',
                    'stored_at' => CarbonImmutable::now()->toIso8601String(),
                    'params' => null,
                ],
                [
                    'id' => null,
                    'ranking_id' => 1,
                    'rank' => 2,
                    'title' => 'title_2',
                    'stored_at' => CarbonImmutable::now()->toIso8601String(),
                    'params' => null,
                ],
            ],
            new RankedTitleList(...$this->rankedTitles)->toArray(),
        );
    }

    public function test_count(): void
    {
        $this->assertSame(
            count($this->rankedTitles),
            new RankedTitleList(...$this->rankedTitles)->count(),
        );
    }
}
いのはいのは

ランキング収集(store)には、ランキングの取得(getAll)と保存(add)が必要。
取得と保存でデータソースが異なるので、Repositoryのインターフェースも分けておく。

上記の内容をコードで表現すると、以下のようになった。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Repositories;

use Lulubell\Ranking\Entities\RankedTitleList;

interface RankingRepository
{
    public function getAll(): RankedTitleList;
}
<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Repositories;

use Lulubell\Ranking\Entities\RankedTitle;

interface RankedTitleRepository
{
    public function add(RankedTitle ...$rankedTitles): void;
}
いのはいのは

RankingRepositoryの実装としてXxxxRankingRepositoryを作成したり、
RankedTitleRepositoryの実装としてRankedTitleEloquentRepositoryを作成したりする。

いのはいのは

RankingServiceクラスでは、RankingRepositoryのgetAllを実行して、
戻り値をそのまま返す。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Services;

use Lulubell\Ranking\Entities\RankedTitleList;
use Lulubell\Ranking\Repositories\RankingRepository;

final readonly class RankingService
{
    public function __construct(
        private RankingRepository $rankingRepository,
    ) {}

    public function getAll(): RankedTitleList
    {
        return $this->rankingRepository->getAll();
    }
}
いのはいのは

RankingServiceクラスのgetAllについて、次のように動作することをテストしたい。
・RankingRepositoryのgetAllを一度だけ実行すること
・RankingRepositoryのgetAllの戻り値をそのまま返すこと

上記の内容をコードで表現すると、以下のようになった。

<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Tests\Unit\Services;

use Carbon\CarbonImmutable;
use Lulubell\Ranking\Entities\Rank;
use Lulubell\Ranking\Entities\RankedTitle;
use Lulubell\Ranking\Entities\RankedTitleList;
use Lulubell\Ranking\Entities\RankingId;
use Lulubell\Ranking\Repositories\RankingRepository;
use Lulubell\Ranking\Services\RankingService;
use PHPUnit\Framework\TestCase;

final class RankingServiceTest extends TestCase
{
    private RankedTitleList $rankedTitleList;

    protected function setUp(): void
    {
        parent::setUp();

        $rankingId = new RankingId(1);
        $storedAt = CarbonImmutable::now();

        $this->rankedTitleList = new RankedTitleList(
            new RankedTitle($rankingId, new Rank(1), 'title_1', $storedAt),
            new RankedTitle($rankingId, new Rank(2), 'title_2', $storedAt),
        );
    }

    public function test_get_all(): void
    {
        $rankingRepositoryMock = $this->createMock(RankingRepository::class);

        $rankingRepositoryMock->expects($this->once())
            ->method('getAll')
            ->willReturn($this->rankedTitleList);

        $this->assertSame(
            $this->rankedTitleList,
            new RankingService($rankingRepositoryMock)->getAll(),
        );
    }
}
いのはいのは
<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Services;

use Lulubell\Ranking\Entities\RankedTitleList;
use Lulubell\Ranking\Repositories\RankedTitleRepository;

final readonly class RankedTitleService
{
    public function __construct(
        private RankedTitleRepository $rankedTitleRepository,
    ) {}

    public function add(RankedTitleList $rankedTitleList): void
    {
        $this->rankedTitleRepository->add(...$rankedTitleList->all());
    }
}
いのはいのは
<?php

declare(strict_types=1);

namespace Lulubell\Ranking\Tests\Unit\Services;

use Carbon\CarbonImmutable;
use Lulubell\Ranking\Entities\Rank;
use Lulubell\Ranking\Entities\RankedTitle;
use Lulubell\Ranking\Entities\RankedTitleList;
use Lulubell\Ranking\Entities\RankingId;
use Lulubell\Ranking\Repositories\RankedTitleRepository;
use Lulubell\Ranking\Services\RankedTitleService;
use PHPUnit\Framework\TestCase;

final class RankedTitleServiceTest extends TestCase
{
    private RankedTitleList $rankedTitleList;

    protected function setUp(): void
    {
        parent::setUp();

        $rankingId = new RankingId(1);
        $storedAt = CarbonImmutable::now();

        $this->rankedTitleList = new RankedTitleList(
            new RankedTitle($rankingId, new Rank(1), 'title_1', $storedAt),
            new RankedTitle($rankingId, new Rank(2), 'title_2', $storedAt),
        );
    }

    public function test_add(): void
    {
        $rankedTitleRepositoryMock = $this->createMock(RankedTitleRepository::class);

        $rankedTitleRepositoryMock->expects($this->once())
            ->method('add');

        new RankedTitleService($rankedTitleRepositoryMock)->add($this->rankedTitleList);
    }
}
いのはいのは

ここまでの(主にPHPに依存した)Lulubell\Rankingモジュールを利用して、
ここからはLaravelに依存したLulubell\LaravelRankingモジュールを実装する。

いのはいのは
<?php

declare(strict_types=1);

namespace Lulubell\LaravelRanking\Repositories;

use Lulubell\LaravelRanking\Models\RankedTitle;
use Lulubell\Ranking\Entities\RankedTitle as RankedTitleEntity;
use Lulubell\Ranking\Repositories\RankedTitleRepository;

final class RankedTitleEloquentRepository implements RankedTitleRepository
{
    private const int CHUNK_SIZE = 1000;

    public function add(RankedTitleEntity ...$rankedTitles): void
    {
        $rankedTitles = array_map(fn (RankedTitleEntity $rankedTitle) => [
            ...$rankedTitle->toArray(),
            'params' => json_encode($rankedTitle->params(), JSON_THROW_ON_ERROR),
        ], $rankedTitles);

        foreach (array_chunk($rankedTitles, self::CHUNK_SIZE) as $chunk) {
            RankedTitle::query()->upsert($chunk, 'id');
        }
    }
}
いのはいのは
<?php

declare(strict_types=1);

use Carbon\CarbonImmutable;
use Lulubell\LaravelRanking\Repositories\RankedTitleEloquentRepository;
use Lulubell\LaravelRanking\Tests\TestCase;
use Lulubell\Ranking\Entities\Rank;
use Lulubell\Ranking\Entities\RankedTitle;
use Lulubell\Ranking\Entities\RankingId;

final class RankedTitleEloquentRepositoryTest extends TestCase
{
    private RankedTitleEloquentRepository $rankedTitleRepository;

    private RankingId $rankingId;

    private CarbonImmutable $storedAt;

    private array $rankedTitles;

    protected function setUp(): void
    {
        parent::setUp();

        $this->rankedTitleRepository = new RankedTitleEloquentRepository;

        $this->rankingId = new RankingId(1);
        $this->storedAt = CarbonImmutable::now();

        $this->rankedTitles = [
            new RankedTitle($this->rankingId, new Rank(1), 'title_1', $this->storedAt),
            new RankedTitle($this->rankingId, new Rank(2), 'title_2', $this->storedAt),
        ];
    }

    public function test_add(): void
    {
        $this->rankedTitleRepository->add(...$this->rankedTitles);

        $this->assertDatabaseCount(
            'ranked_titles',
            count($this->rankedTitles),
        );
    }

    public function test_add_with_no_ranked_titles(): void
    {
        $this->rankedTitleRepository->add();

        $this->assertDatabaseEmpty('ranked_titles');
    }

    public function test_add_with_many_ranked_titles(): void
    {
        $rankedTitles = array_map(
            fn (int $i) => new RankedTitle($this->rankingId, new Rank(Rank::MIN), 'title_'.Rank::MIN, $this->storedAt),
            range(1, 100000),
        );

        $this->rankedTitleRepository->add(...$rankedTitles);

        $this->assertDatabaseCount(
            'ranked_titles',
            count($rankedTitles),
        );
    }
}
いのはいのは
<?php

declare(strict_types=1);

namespace Lulubell\LaravelRanking\UseCases;

interface StoreRankingUseCase
{
    public function handle(): void;
}
いのはいのは
<?php

declare(strict_types=1);

namespace Lulubell\LaravelRanking\UseCases;

use Illuminate\Support\Facades\DB;
use Lulubell\Ranking\Services\RankedTitleService;
use Lulubell\Ranking\Services\RankingService;

abstract readonly class StoreRankingInteractor implements StoreRankingUseCase
{
    public function __construct(
        private RankingService $rankingService,
        private RankedTitleService $rankedTitleService,
    ) {}

    public function handle(): void
    {
        $rankedTitleList = $this->rankingService->getAll();

        DB::transaction(function () use ($rankedTitleList) {
            $this->rankedTitleService->add($rankedTitleList);
        });
    }
}
このスクラップは4ヶ月前にクローズされました