Подскажите по архитектуре моего кода, по солиду, слоям, что улучшить?

Пробую писать в парадигме слоистых архитектур, или чистых, как у дяди Боба.

Сделал на yii2 игру крестики нолики. Можете подсказать, все ли делаю так как надо? По солид, по слоям?

Вот код:

<?php
declare(strict_types=1);

namespace app\modules\crosses\controllers;

use yii\web\Controller;
use app\modules\crosses\models\ARGame;
use Yii;
use app\modules\crosses\useCases\GameUser;
use yii\web\NotFoundHttpException;
use app\modules\crosses\repository\GameRepository;
use yii\data\ActiveDataProvider;

/**
 * Default controller for the `crosses` module
 */
class DefaultController extends Controller
{
    private $gameUser;

    private $repository;

    public function __construct($id, $module, GameUser $players, GameRepository $repository, $config = [])
    {
        parent::__construct($id, $module, $config);
        $this->gameUser = $players;
        $this->repository = $repository;
    }

    /**
     * Renders the index view for the module
     * @return string
     */
    public function actionIndex()
    {
        return $this->render('index');
    }

    public function actionAllGames()
    {
        $query = ARGame::find();

        $provider = new ActiveDataProvider([
            'query' => $query,
            'pagination' => [
                'pageSize' => 5,
            ],
            'sort' => [
                'defaultOrder' => [
                    'id' => SORT_DESC,
                ]
            ],
        ]);

        return $this->render('all-games', ['provider' => $provider]);
    }

    public function actionStartGame()
    {
        $request = Yii::$app->request;

        if ($request->post('start_game') == 'yes') {
            try {
                $id = $this->gameUser->startNewGame();
                Yii::$app->session->setFlash('success', 'Игра успешно создана, начинаем играть');
                return $this->redirect(['play-game', 'id' => $id]);
            } catch (\Throwable $e) {
                Yii::error('unable to create a game, because - ' . $e->getMessage());
                Yii::$app->session->setFlash('error', $e->getMessage());

            }
        }
        return $this->render('start-game');
    }

    public function actionPlayGame(int $id)
    {
        $arGame = $this->findGame($id);
        $isOver = $arGame->getIsOver();
        $game = $this->repository->getGame($arGame);

        $request = Yii::$app->request;

        if (!$isOver && ($move = $request->post('make_move'))) {
            try {
                $inputs = explode('_', $move);
                $inputs = array_map('intval', $inputs);
                if (count($inputs) != 2) {
                    throw new \LogicException('invalid move input');
                }
                $this->gameUser->playGame($id, $game, $inputs[0], $inputs[1]);
            } catch (\Throwable $e) {
                Yii::error('unable to save a game, because - ' . $e->getMessage());
                Yii::$app->session->setFlash('error', $e->getMessage());

            }
        }

        return $this->render('play-game', ['game' => $game]);
    }

    protected function findGame(int $id)
    {
        if ($game = ARGame::findOne($id)) {
            return $game;
        }
        throw new NotFoundHttpException();
    }
}
?>

<?php
declare(strict_types=1);

namespace app\modules\crosses\models;

use Yii;

/**
 * Active Record модель, ассоциированная с сохраненной в базе игрой
 *
 * @property int $id
 * @property int|null $status
 * @property string|null $board
 * @property string|null $player
 * @property string|null $winner
 */
class ARGame extends \yii\db\ActiveRecord
{
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return 'crosses';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['status'], 'integer'],
            [['board'], 'string'],
            [['winner', 'player'], 'string', 'max' => 1],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'status' => 'Status',
            'board' => 'Board',
            'winner' => 'Winner',
        ];
    }

    public function getIsOver(): bool
    {
        return $this->status == Game::STATUS_GAME_OVER;
    }
}

?>

<?php
declare(strict_types=1);

namespace app\modules\crosses\models;

/**
 * Доска или поле для игры
 *
 * @package app\modules\crosses\models
 */
class Board
{
    /**
     * @var array Массив 3 на 3 с данными о доске.
     * Пустая строка, значит клетка не занята.
     */
    private $data = [];

    public function __construct($data = null)
    {
        if ($data) {
            $this->data = $data;
        } else {
            $this->setEmpty();
        }


    }

    public function getData() : array
    {
        return $this->data;
    }

    public function occupyCell(string $who, int $x, int $y)
    {
        if ($this->data[$x][$y]) {
            throw new \LogicException('Wrong input for move, cell is already taken');
        }
        $this->data[$x][$y] = $who;

    }

    /**
     * Проверяем , победил ли кто то. Диагонали не проверяем пока
     * @return bool|mixed
     */
    public function getWinnerIfThereIs()
    {
        $data = $this->data;
        for ($i = 0; $i < 3; $i++)
        {
            if ($res = $this->checkRow($i)) {
                return $res;
            }
            if ($res = $this->checkColumn($i)) {
                return $res;
            }
        }
        return false;
    }

    protected function checkRow($i)
    {
        $data = $this->data;
        if (!($k = $data[$i][0])) {
            return false;
        }
        for ($j = 1; $j < 3; $j++)
        {
            if ($k !== $data[$i][$j]) {
                return false;
            }
        }
        return $k;
    }

    protected function checkColumn($i)
    {
        $data = $this->data;
        if (!($k = $data[0][$i])) {
            return false;
        }
        for ($j = 1; $j < 3; $j++)
        {
            if ($k !== $data[$j][$i]) {
                return false;
            }
        }
        return $k;
    }

    public function getHasEmptyCells()
    {
        $data = $this->data;
        for ($i = 0; $i < 3; $i++)
        {
            for ($j = 0; $j < 3; $j++)
            {
                if (!$data[$i][$j]) {
                    return true;
                }
            }
        }
        return false;
    }

    private function setEmpty()
    {
        $this->data = [
            ['', '', ''],
            ['', '', ''],
            ['', '', ''],
        ];
    }
}
?>

<?php

declare(strict_types=1);

namespace app\modules\crosses\models;

/**
 * Игра
 * @package app\modules\crosses\models
 */
class Game
{
    const STATUS_GAME_PLAY = 0;
    const STATUS_GAME_OVER = 1;

    const PLAYER_CROSS = 'x';
    const PLAYER_NAUGHT = 'o';

    private int $status = self::STATUS_GAME_PLAY;

    private $player;

    private $board;

    private $hasWinner = false;

    public function __construct($status, Board $board, Player $player, $hasWinner = false)
    {
        $this->status = $status;
        $this->board = $board;
        $this->player = $player;
        $this->hasWinner = $hasWinner;
    }

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

    public function getBoard() : Board
    {
        return $this->board;
    }

    public function getPlayer() : Player
    {
        return $this->player;
    }

    public function getIsOver() : bool
    {
        return $this->status == self::STATUS_GAME_OVER;
    }

    public function setIsOver(bool $hasWinner): void
    {
        $this->hasWinner = $hasWinner;
        $this->status = self::STATUS_GAME_OVER;
    }

    public function getHasWinner(): bool
    {
        return $this->hasWinner;
    }

    public function swapPlayers() :void
    {
        $this->player = new Player($this->getPlayer()->getName() == self::PLAYER_CROSS ? self::PLAYER_NAUGHT : self::PLAYER_CROSS);
    }

    public function playerMakeMove(int $x, int $y)
    {
        $this->board->occupyCell($this->player->getName(), $x, $y);
    }

    public static function getRandomPlayerName() : string
    {
        return mt_rand(0, 1) == 1 ? self::PLAYER_CROSS : self::PLAYER_NAUGHT;
    }
}
?>

<?php
declare(strict_types=1);

namespace app\modules\crosses\models;

/**
 * Текущий Игрок
 * @package app\modules\crosses\models
 */
class Player
{
    private $who;

    public function __construct($who)
    {
        $this->who = $who;
    }

    public function getName()
    {
        return $this->who;
    }
}
?>

<?php

declare(strict_types=1);

namespace app\modules\crosses\repository;
use app\modules\crosses\models\ARGame;
use app\modules\crosses\models\Game;
use app\modules\crosses\models\Board;
use app\modules\crosses\models\Player;

/**
 * Репозиторий
 */
class GameRepository
{
    public function saveNewGame(Game $game) : int
    {
        $ar = new ARGame();
        $ar->status = $game->getStatus();
        $ar->board = serialize($game->getBoard()->getData());
        $ar->player = $game->getPlayer()->getName();

        if (!$ar->save()) {
            throw new \DomainException('Unable to save a new game');
        }

        return $ar->id;
    }

    public function saveGame($id, Game $game) : int
    {
        $ar = ARGame::findOne($id);
        if (!$ar) {
            throw new \LogicException('Game not found');
        }
        $ar->status = $game->getStatus();
        $ar->board = serialize($game->getBoard()->getData());
        $ar->player = $game->getPlayer()->getName();
        if ($game->getIsOver() && $game->getHasWinner()) {
            $ar->winner = $ar->player;
        }

        if (!$ar->save()) {
            throw new \DomainException('Unable to save a game');
        }

        return $ar->id;
    }

    public function getGame(ARGame $ar) : Game
    {
        $board = new Board(unserialize($ar->board));
        $player = new Player($ar->player);
        return new Game(intval($ar->status), $board,$player, boolval($ar->winner));
    }
}
?>

<?php
declare(strict_types=1);

namespace app\modules\crosses\useCases;

use app\modules\crosses\models\{Board,Game,Player};
use app\modules\crosses\repository\GameRepository;

class GameUser
{
    public function startNewGame() : int
    {
        $board = new Board();
        $player = new Player(Game::getRandomPlayerName());
        $game = new Game(Game::STATUS_GAME_PLAY, $board,$player);

        $repository = new GameRepository();
        $id = $repository->saveNewGame($game);
        return $id;
    }

    public function playGame(int $idGame, Game $game, int $x, int $y)
    {
        $board = $game->getBoard();
        $game->playerMakeMove($x, $y);
        if ($res = $board->getWinnerIfThereIs()) {
            $game->setIsOver(true);
        } else {
            if ($board->getHasEmptyCells()) {
                $game->swapPlayers();
            } else {
                $game->setIsOver(false);
            }
        }
        $repository = new GameRepository();

        $repository->saveGame($idGame, $game);
    }
}

?>

<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;

/** @var yii\web\View $this */
/** @var app\modules\crosses\models\Game $game */
/** @var yii\widgets\ActiveForm $form */

$who = $game->getPlayer()->getName();
$board = $game->getBoard()->getData();
?>

<div class="article-form">

    <?php $form = ActiveForm::begin(); ?>
    <p>
        <?php
            $message = '';
            if ($game->getIsOver()) {
                $message .= 'Игра завершена.';
                $message .= !$game->getHasWinner() ? ' Без Победителя.' : ' Победил - ' . '<strong>' .  $who . '</strong>';
            } else {
                $message .= 'Текущий ход - игрок ' . '<strong>' .  $who . '</strong>';
            }
            print $message;
        ?>
    </p>
    <table class="table table-bordered">
        <?php
            for ($i = 0; $i < 3; $i++) {
                print '<tr>';
                for ($j = 0; $j < 3; $j++) {
                    $cell = $board[$i][$j];
                    $content = $cell ? $cell :
                        ($game->getIsOver() ? '--' : Html::submitButton('&nbsp;', ['class' => 'btn btn-success', 'name' => 'make_move', 'value' => $i . '_' . $j]));
                    print '<td>' . $content . '</td>';
                }
                print '</tr>';

            }
        ?>
    </table>


    <?php ActiveForm::end(); ?>

</div>

Ответы (0 шт):