Skip to content

🦍 App created with PHP, PostgreSQL, Angular, PrimeNG, SOLID Principles, Package-By-Feature and more...

License

Notifications You must be signed in to change notification settings

EricNeves/myFavoriteBooks

Repository files navigation



My Favorite Books

Aplicação desenvolvida com PHP e Angular, porém, com o foco direcionado ao app em PHP que foi criado baseado nos princípios SOLID e na arquitetura package-by-feature, garantindo maior legibilidade e organização do código, bem como a implementação de testes automatizados.

Data de criação: Jun 9, 2024

Github

Intro 📜

A aplicação com PHP adota princípios de design de software, como os princípios SOLID, e uma arquitetura modular baseada em funcionalidades (package-by-feature). Essa abordagem garante que o código seja bem estruturado, fácil de entender e manter, além de permitir uma escalabilidade e extensibilidade mais simples.

Note

O upload das imagens é relativamente simples, com validação e salvas no formato BLOB (Binary Large Object), num cenário real, seriam salvas em uma CDN (Content Delivery Network).

Features 💡

  • 📁 Padrão Package By Feature
    • 🙍 User
      • Criar usuário
      • Autenticação - JWT
      • Informações do Usuário
      • Editar Usuário
    • 📚 Book
      • Criar livro
      • Editar livro
      • Informações de um livro
      • Todos os livros
      • Remover um livro
  • ⚡ Dependencies:
    • phpunit/phpunit: ^10.5
    • vlucas/phpdotenv: ^5.6
    • @angular/cli: ^17.3.8
    • primeng: ^17.18.1,
    • and more...
  • 📡 DevOps
    • NGINX
    • Docker

Doc 📑

Routes

Note

Para adicionar uma nova rota, deve-se levar em consideração os use cases, controllers e as factories.

|-- routes
|   |-- api.php
|-- config
|   |-- factories.php
|-- UseCases
|   |-- Intro
|   |   |-- WelcomeMessage
|   |   |   |-- WelcomeMessageController.php
|   |   |   |-- WelcomeMessageFactory.php
|   |   |   |-- IWelcomeMessageUseCase.php
|   |   |   |-- WelcomeMessageUseCase.php

Em routes/api.php, referencie o controller através do namespace. Porém, deve-se remover o App\UseCases\, mantendo apenas o restante, nesse caso, Intro\WelcomeMessage\WelcomeMessageController.

Exemplo:

<?php 

use App\Http\Router;

Route::get('/', 'Intro\WelcomeMessage\WelcomeMessageController');

Agora, em config/factories.php, associe o controller passado na rota com a factory do use case.

Note

A factory é responsável por criar as instâncias e injetar as depedências.

return [
  'Intro\WelcomeMessage\WelcomeMessageController' => App\UseCases\Intro\WelcomeMessage\WelcomeMessageFactory::class
];

Use Cases

De modo geral, será exemplificado a criação de um use case incluindo recursos como banco de dados.

|-- Providers
|   |-- IUserPostgresProvider.php
|   |-- Implementation
|   |   |-- UserPostgresProvider.php
|-- Repositories
|   |-- UserRepository.php
|-- UseCases
|   |-- User
|   |   |-- FetchUser
|   |   |   |-- FetchUserController.php
|   |   |   |-- FetchUserFactory.php
|   |   |   |-- IFetchUserUseCase.php
|   |   |   |-- FetchUserUseCase.php

Note

IUserPostgresProvider será responsável por definir os contratos de consultas SQL.

<?php 

namespace App\Providers;

interface IUserPostgresProvider
{
  public function fetch(int $id): array;
}

Note

UserPostgresProvider será responsável pela implementação das consultas SQL (IUserPostgresProvider). No construtor de UserPostgresProvider é passado como injeção de depedência a classe PDO.

<?php

namespace App\Providers\Implementations;

use App\Providers\IUserPostgresProvider;
use PDO;

class UserPostgresProvider implements IUserPostgresProvider
{
  public function __construct(private PDO $pdo)
  {
  }

  public function fetch(int $id): array 
  {
    return $this->pdo->query("...");
  }
}

Note

IUserRepository será responsável por definir o contrato de persistência dos dados.

<?php

namespace App\Repositories;

interface IUserRepository
{
  public function fetchUser(int $id): array;
}

Note

UserRepository será responsável por implementar os contratos definidos por IUserRepository. No construtor de UserRepository é passado como inversão de depedência a interface IUserPostgresProvider.

<?php

namespace App\Repositories\Implementations;

use App\Providers\IUserPostgresProvider;
use App\Repositories\IUserRepository;

class UserRepository implements IUserRepository
{
  public function __construct(private IUserPostgresProvider $database)
  {
  }

  public function fetchUser(int $id): array
  {
    return $this->database->fetch($id);
  }  
}

Note

IFetchUserUseCase será responsável por definir o contrato do use case.

<?php

namespace App\UseCases\User\FetchUser;

interface IFetchUserUseCase
{
  public function execute(int $userId): array;
}

Note

FetchUserUseCase será responsável por implementar IFetchUserUseCase, bem como as regras de negócio e nesse caso realizar operações através do repository. No construtor de FetchUserUseCase é passado como inversão de depedência o IUserRepository.

<?php

namespace App\UseCases\User\FetchUser;

use App\Repositories\IUserRepository;
use App\UseCases\User\FetchUser\IFetchUserUseCase;
use Exception;

class FetchUserUseCase implements IFetchUserUseCase
{
  public function __construct(private IUserRepository $userRepository)
  {
  }

  public function execute(int $userId): array
  {
    $user = $this->userRepository->fetchUser($userId);

    if (!$user) {
      throw new Exception('Sorry, user not found.');
    }

    return $user;
  }
}

Note

No construtor do controller é passado como inversão de depedência a interface IFetchUserUseCase.

<?php

namespace App\UseCases\User\FetchUser;

use App\Http\Request;
use App\Http\Response;
use App\UseCases\User\FetchUser\IFetchUserUseCase;

class FetchUserController
{
  public function __construct(private IFetchUserUseCase $fetchUserUseCase)
  {
  }

  public function handle(Request $request, Response $response): Response
  {
    return $response->json([
      "data" => $this->fetchUserUseCase->execute($request->user()->id),
    ]);
  }
}

Note

Por último deve-se passar as implementações dos contratos na factory do use case.

<?php

namespace App\UseCases\User\FetchUser;

use App\Infrastructure\Postgres;
use App\Providers\Implementations\UserPostgresProvider;
use App\Repositories\Implementations\UserRepository;
use App\UseCases\User\FetchUser\FetchUserController;
use App\UseCases\User\FetchUser\FetchUserUseCase;

class FetchUserFactory
{
    public function generateInstance(array $databaseConfig): FetchUserController
    {
        $postgres            = new Postgres();
        $postgresProvider    = new UserPostgresProvider($postgres::connect($databaseConfig));
        $userRepository      = new UserRepository($postgresProvider);
        $fetchUserUseCase    = new FetchUserUseCase($userRepository);
        $fetchUserController = new FetchUserController($fetchUserUseCase);

        return $fetchUserController;
    }
}

Note

$databaseConfig que é passado como paramêtro em generateInstance, é o array definido em config/database.php. Se quiser mudar o provider de postgresql para mysql por exemplo, será necessário definir as configurações do novo banco.

No exemplo será mostrado a configuração do mysql com PDO, mas, caso deseje usar mysqlli ou algum ORM, basta criar a conexão como no exemplo abaixo e implementar o provider.

config/database.php

<?php

return [
  'pgsql' => [
    'host'     => $_ENV['PG_HOST'],
    'port'     => $_ENV['PG_PORT'],
    'database' => $_ENV['PG_DATABASE'],
    'username' => $_ENV['PG_USERNAME'],
    'password' => $_ENV['PG_PASSWORD'],
  ],
  'mysql' => [
    'host'     => '...',
    'database' => '...',
    'username' => '...',
    'password' => '...'   
  ]
];

Infrastructure/Mysql.php

<?php

namespace App\Infrastructure;

use PDO;

class Mysql
{
    public static function connect(array $config): PDO
    {
        $host     = $config['mysql']['host'];
        $dbname   = $config['mysql']['database'];
        $username = $config['mysql']['username'];
        $password = $config['mysql']['password'];

        $dns = "mysql:host=$host;dbname=$dbname;";

        $pdo = new PDO($dns, $username, $password);

        return $pdo;
    }
}

Providers/IUserMysqlProvider.php

<?php

namespace App\Providers;

interface IUserMysqlProvider
{
  public function save(array $fields): bool;
}

Providers/Implementations/UserMysqlProvider.php

<?php

namespace App\Providers\Implementations;

use App\Providers\IUserMysqlProvider;
use PDO;

class UserMysqlProvider implements IUserMysqlProvider
{
    public function __construct(private PDO $pdo)
    {
    }

    public function save(array $fields): bool
    {
      // ...
    }
}

Pronto, após isso é só passar as depedências na factory do use case como nos exemplos já mostrados.

Middlewares

Para adicionar um novo middleware segue-se o exemplo abaixo:

|-- Middlewares
|   |-- NewMiddleware.php
<?php

namespace App\Middlewares;

use App\Http\JWT;
use App\Http\Request;
use App\Http\Response;

class NewMiddleware
{
    public function handle(Request $request, Response $respose)
    {
        $a = 1;
        $b = 2;

        if ($a !== $b) {
          return $respose->json(['message' => 'Unauthorized'], 401);
        }
    }
}

O próximo passado será associar o middleware com uma chave única em config/middlewares.php.

<?php 

return [
  'auth'  => App\Middlewares\EnsureAuthenticatedMiddleware::class,
  'equal' => App\Middlewares\NewMiddleware::class,
]

Por último é só usar o middleware na rota.

Router::get('/users/fetch', 'User\FetchUser\FetchUserController')->middlewares('auth', 'equal');

Execution ⚙️

Note

Siga os passos abaixo para a execução do projeto em ambiente de desenvolvimento.

O primeiro passo, é renomear o arquivo .env.example para .env, o mesmo se encontra em /www.

# Install deps www/
$ cd www && composer install

# Install deps web/
$ cd web && pnpm install

# Docker
$ docker compose -f "docker-compose-dev.yml" up -d --build

# Tests
$ cd www && composer test

Author 🦆


Eric Neves

License 📋

About

🦍 App created with PHP, PostgreSQL, Angular, PrimeNG, SOLID Principles, Package-By-Feature and more...

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published