Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
run: composer config --no-plugins allow-plugins.pestphp/pest-plugin true

- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-progress
run: composer install --prefer-dist --no-interaction --no-progress --ignore-platform-reqs

- name: Copy environment file
run: cp .env.example .env
Expand All @@ -46,4 +46,4 @@ jobs:
run: composer test

- name: PHPStan
run: composer phpstan
run: composer phpstan
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"monolog/monolog": "^2 || ^3",
"vlucas/phpdotenv": "^v5.1",
"league/container": "^4",
"zircote/swagger-php": "^5.0"
"zircote/swagger-php": "^5.0",
"laminas/laminas-db": "^2.20"
},
"require-dev": {
"pestphp/pest": "^3.0",
Expand Down
11 changes: 6 additions & 5 deletions config/containers/database.container.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<?php

use Laminas\Db\Adapter\{Adapter, AdapterInterface};
use League\Container\Container;

return static function(Container $container): void {
$container
->add(PDO::class)
->addArgument('sqlite:'.storage_path('database.sqlite'))
->addMethodCall('setAttribute', [PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION])
->addMethodCall('setAttribute', [PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC])
->addMethodCall('setAttribute', [PDO::ATTR_EMULATE_PREPARES, false]);
->add(AdapterInterface::class, Adapter::class)
->addArgument([
'driver' => 'Pdo_Sqlite',
'dsn' => 'sqlite:'.storage_path('database.sqlite')
]);
};
5 changes: 1 addition & 4 deletions src/Model/Album.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
use OpenApi\Attributes as OA;

#[OA\Schema]
class Album
class Album extends Model
{

#[OA\Property(example: 42, nullable: false)]
public int $id;

#[OA\Property(example: 'Minha História', nullable: false)]
public string $title;

Expand Down
5 changes: 1 addition & 4 deletions src/Model/Artist.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
use OpenApi\Attributes as OA;

#[OA\Schema]
class Artist
class Artist extends Model
{

#[OA\Property(example: 1, nullable: false)]
public int $id;

#[OA\Property(example: 'AC/DC', nullable: false)]
public string $name;
}
13 changes: 13 additions & 0 deletions src/Model/Model.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace App\Model;

use OpenApi\Attributes as OA;

#[OA\Schema]
class Model
{

#[OA\Property(example: 42, nullable: false)]
public int $id;
}
62 changes: 62 additions & 0 deletions src/Repository/AbstractRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace App\Repository;

use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\TableGateway\TableGateway;
use Monolog\Logger;

abstract readonly class AbstractRepository implements RepositoryInterface
{

public const ROW_IDENTIFIER = 'id';

protected TableGateway $table_gateway;

protected Logger $logger;

public function __construct(AdapterInterface $adapter, Logger $logger)
{
$this->table_gateway = new TableGateway($this->getTable(), $adapter);
$this->logger = $logger->withName(__CLASS__);
}

abstract protected function getTable(): string;

/**
* @return array<array<string, mixed>>
*/
public function all(): array
{
return iterator_to_array($this->table_gateway->select());
}

/**
* @return array<string, mixed>|null
*/
public function find(int $id): ?array
{
/** @var ResultSet $results */
$results = $this->table_gateway->select([static::ROW_IDENTIFIER => $id]);

return $results->current();
}

public function create(array $data): int
{
$this->table_gateway->insert($data);

return $this->table_gateway->getLastInsertValue();
}

public function update(int $id, array $data): bool
{
return $this->table_gateway->update($data, [static::ROW_IDENTIFIER => $id]) > 0;
}

public function delete(int $id): bool
{
return $this->table_gateway->delete([static::ROW_IDENTIFIER => $id]) > 0;
}
}
87 changes: 4 additions & 83 deletions src/Repository/AlbumRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,92 +2,13 @@

namespace App\Repository;

use App\Model\Artist;
use InvalidArgumentException;
use Monolog\Logger;
use PDO;
use RuntimeException;

readonly class AlbumRepository implements AlbumRepositoryInterface
readonly class AlbumRepository extends AbstractRepository
{

private Logger $logger;

public function __construct(
private PDO $pdo,
Logger $logger
) {
$this->logger = $logger->withName(__CLASS__);
}

/** @return Artist[] */
public function all(): array
{
return $this
->pdo
->query('SELECT AlbumId AS id, Title AS title, ArtistId as artist_id FROM albums')
->fetchAll(PDO::FETCH_CLASS, Artist::class);
}

public function find(int $id): ?Artist
{
$stmt = $this
->pdo
->prepare('SELECT AlbumId AS id, Title AS title, ArtistId as artist_id FROM albums WHERE AlbumId = ?');

$stmt->execute([$id]);

return $stmt->fetchObject(Artist::class) ?: null;
}

public function create(array $data): int
{
$stmt = $this
->pdo
->prepare('INSERT INTO albums (Title, ArtistId) VALUES (?, ?)');

if (!$stmt->execute([$data['title'], $data['artist_id']])) {
$this->logger->error('An error occurred while trying to create an album with data: {data}', ['{data}' => implode(', ', array_keys($data))]);
throw new RuntimeException('Error creating album', 500);
}

return (int)$this->pdo->lastInsertId();
}
public const ROW_IDENTIFIER = 'AlbumId';

public function update(int $id, array $data): bool
protected function getTable(): string
{
$fields = [];
$values = [];

if (isset($data['title'])) {
$fields[] = 'Title = ?';
$values[] = $data['title'];
}

if (isset($data['artist_id'])) {
$fields[] = 'ArtistId = ?';
$values[] = $data['artist_id'];
}

if (empty($fields)) {
$this->logger->error('No data provided to update Album #{id}', ['{id}' => $id]);
throw new InvalidArgumentException('No data to update');
}

$values[] = $id;
$sql = 'UPDATE albums SET '.implode(', ', $fields).' WHERE AlbumId = ?';

$stmt = $this->pdo->prepare($sql);

return $stmt->execute($values);
}

public function delete(int $id): bool
{
$stmt = $this
->pdo
->prepare('DELETE FROM albums WHERE AlbumId = ?');

return $stmt->execute([$id]);
return 'albums';
}
}
22 changes: 0 additions & 22 deletions src/Repository/AlbumRepositoryInterface.php

This file was deleted.

72 changes: 4 additions & 68 deletions src/Repository/ArtistRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,13 @@

namespace App\Repository;

use App\Model\Artist;
use Monolog\Logger;
use PDO;
use RuntimeException;

readonly class ArtistRepository implements ArtistRepositoryInterface
readonly class ArtistRepository extends AbstractRepository
{

private Logger $logger;

public function __construct(
private PDO $pdo,
Logger $logger
) {
$this->logger = $logger->withName(__CLASS__);
}

/** @return Artist[] */
public function all(): array
{
return $this
->pdo
->query('SELECT ArtistId AS id, Name AS name FROM artists')
->fetchAll(PDO::FETCH_CLASS, Artist::class);
}

public function find(int $id): ?Artist
{
$stmt = $this
->pdo
->prepare('SELECT ArtistId AS id, Name AS name FROM artists WHERE ArtistId = ?');

$stmt->execute([$id]);

return $stmt->fetchObject(Artist::class) ?: null;
}

public function create(array $data): int
{
$stmt = $this
->pdo
->prepare('INSERT INTO artists (Name) VALUES (?)');

if (!$stmt->execute([$data['name']])) {
$this->logger->error('An error occurred while trying to create an artist with data: {data}', ['{data}' => implode(', ', array_keys($data))]);
throw new RuntimeException('Error creating artist', 500);
}
public const ROW_IDENTIFIER = 'ArtistId';

return (int)$this->pdo->lastInsertId();
}

public function update(int $id, array $data): bool
protected function getTable(): string
{
if (!isset($data['name'])) {
$this->logger->error('No data provided to update Artist #{id}', ['{id}' => $id]);
throw new RuntimeException('No fields to update', 400);
}

$stmt = $this
->pdo
->prepare('UPDATE artists SET Name = ? WHERE ArtistId = ?');

return $stmt->execute([$data['name'], $id]);
}

public function delete(int $id): bool
{
$stmt = $this
->pdo
->prepare('DELETE FROM artists WHERE ArtistId = ?');

return $stmt->execute([$id]);
return 'artists';
}
}
22 changes: 0 additions & 22 deletions src/Repository/ArtistRepositoryInterface.php

This file was deleted.

21 changes: 21 additions & 0 deletions src/Repository/Mapper/AlbumMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Repository\Mapper;

use App\Model\Album;
use App\Repository\AlbumRepository;

readonly class AlbumMapper
{

/** @param iterable<string, mixed> $object */
public static function toAlbum(iterable $object): Album
{
$album = new Album();
$album->id = $object[AlbumRepository::ROW_IDENTIFIER] ?? null;
$album->title = $object['Title'] ?? null;
$album->artist_id = $object['ArtistId'] ?? null;

return $album;
}
}
Loading