{"id":20772225,"url":"https://github.com/ericneves/myfavoritebooks","last_synced_at":"2026-04-14T06:34:01.229Z","repository":{"id":246956723,"uuid":"812745958","full_name":"EricNeves/myFavoriteBooks","owner":"EricNeves","description":"App created with PHP, PostgreSQL, Angular, PrimeNG, SOLID Principles, Package-By-Feature and more...","archived":false,"fork":false,"pushed_at":"2024-07-06T15:21:53.000Z","size":664,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-01T18:39:35.014Z","etag":null,"topics":["angular","package-by-feature","php","postgresql","primeng","use-cases"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EricNeves.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-09T18:59:33.000Z","updated_at":"2025-02-17T15:09:23.000Z","dependencies_parsed_at":"2024-07-06T01:15:10.945Z","dependency_job_id":"400980e1-6613-41d8-9ec6-004295f22b03","html_url":"https://github.com/EricNeves/myFavoriteBooks","commit_stats":null,"previous_names":["ericneves/myfavoritebooks"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/EricNeves/myFavoriteBooks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EricNeves%2FmyFavoriteBooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EricNeves%2FmyFavoriteBooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EricNeves%2FmyFavoriteBooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EricNeves%2FmyFavoriteBooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EricNeves","download_url":"https://codeload.github.com/EricNeves/myFavoriteBooks/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EricNeves%2FmyFavoriteBooks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31785677,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","package-by-feature","php","postgresql","primeng","use-cases"],"created_at":"2024-11-17T12:19:47.552Z","updated_at":"2026-04-14T06:34:01.197Z","avatar_url":"https://github.com/EricNeves.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch4 align=\"center\"\u003e\n  \u003cbr /\u003e\n  \u003cimg src=\"resources/screenshots/icon.png\"\u003e\n  \u003cbr /\u003e\n    My Favorite Books\n  \u003cbr /\u003e\n\u003c/h4\u003e\n\n\u003cp align=\"center\"\u003eAplicação desenvolvida com \u003cstrong\u003ePHP\u003c/strong\u003e e \u003cstrong\u003eAngular\u003c/strong\u003e, porém, com o foco direcionado ao app em \u003cstrong\u003ePHP\u003c/strong\u003e que foi criado baseado nos princípios \u003cstrong\u003eSOLID\u003c/strong\u003e e na arquitetura \u003cstrong\u003epackage-by-feature\u003c/strong\u003e, garantindo maior legibilidade e organização do código, bem como a implementação de \u003cstrong\u003etestes automatizados\u003c/strong\u003e.\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eData de criação: Jun 9, 2024\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/last-commit/ericneves/myFavoriteBooks?display_timestamp=author\u0026style=for-the-badge\u0026logo=github\" alt=\"Github\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/languages/count/ericneves/myFavoriteBooks?style=for-the-badge\u0026logo=rocket\u0026color=%23F5455C\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/languages/top/ericneves/myFavoriteBooks?style=for-the-badge\u0026logo=PHP\u0026logoColor=%23fff\u0026labelColor=%23777BB4\u0026color=%23333\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/license/ericneves/myFavoriteBooks?style=for-the-badge\u0026logo=git\u0026logoColor=%23C71D23\u0026color=%23C71D23\"\u003e\n\u003c/p\u003e\n\n\u003cimg src=\"resources/screenshots/screenshot.png\"\u003e\n\n#### Intro 📜\n\nA 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.\n\n\u003e [!NOTE]\n\u003e 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**).\n\n#### Features 💡\n\n- 📁 Padrão Package By Feature\n  - 🙍 User\n    - Criar usuário\n    - Autenticação - JWT\n    - Informações do Usuário\n    - Editar Usuário\n  - 📚 Book\n    - Criar livro\n    - Editar livro\n    - Informações de um livro\n    - Todos os livros\n    - Remover um livro\n- ⚡ Dependencies:\n  - phpunit/phpunit: `^10.5`\n  - vlucas/phpdotenv: `^5.6`\n  - @angular/cli: `^17.3.8`\n  - primeng: `^17.18.1`,\n  - and more...\n- 📡 DevOps\n  - NGINX\n  - Docker\n\n#### Doc 📑\n\n### Routes \n\n\u003e\n\u003e [!NOTE]\n\u003e Para adicionar uma nova **rota**, deve-se levar em consideração os **use cases**, **controllers** e as **factories**. \n\u003e\n\n```\n|-- routes\n|   |-- api.php\n|-- config\n|   |-- factories.php\n|-- UseCases\n|   |-- Intro\n|   |   |-- WelcomeMessage\n|   |   |   |-- WelcomeMessageController.php\n|   |   |   |-- WelcomeMessageFactory.php\n|   |   |   |-- IWelcomeMessageUseCase.php\n|   |   |   |-- WelcomeMessageUseCase.php\n```\n\nEm `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`.\n\nExemplo:\n\n```php \n\n\u003c?php \n\nuse App\\Http\\Router;\n\nRoute::get('/', 'Intro\\WelcomeMessage\\WelcomeMessageController');\n\n```\n\nAgora, em `config/factories.php`, associe o **controller** passado na rota com a **factory** do **use case**.\n\n\u003e\n\u003e [!NOTE]\n\u003e A `factory` é responsável por criar as **instâncias** e injetar as **depedências**.\n\u003e\n\n```php \n\nreturn [\n  'Intro\\WelcomeMessage\\WelcomeMessageController' =\u003e App\\UseCases\\Intro\\WelcomeMessage\\WelcomeMessageFactory::class\n];\n\n```\n\n### Use Cases\n\nDe modo geral, será exemplificado a criação de um **use case** incluindo recursos como **banco de dados**.\n\n```\n|-- Providers\n|   |-- IUserPostgresProvider.php\n|   |-- Implementation\n|   |   |-- UserPostgresProvider.php\n|-- Repositories\n|   |-- UserRepository.php\n|-- UseCases\n|   |-- User\n|   |   |-- FetchUser\n|   |   |   |-- FetchUserController.php\n|   |   |   |-- FetchUserFactory.php\n|   |   |   |-- IFetchUserUseCase.php\n|   |   |   |-- FetchUserUseCase.php\n```\n\u003e [!NOTE]\n\u003e `IUserPostgresProvider` será responsável por definir os contratos de consultas SQL.\n\u003e \n\n```php \n\n\u003c?php \n\nnamespace App\\Providers;\n\ninterface IUserPostgresProvider\n{\n  public function fetch(int $id): array;\n}\n\n```\n\n\u003e [!NOTE]\n\u003e `UserPostgresProvider` será responsável pela implementação das consultas SQL (`IUserPostgresProvider`).\n\u003e No **construtor** de `UserPostgresProvider` é passado como **injeção de depedência** a classe `PDO`.\n\u003e \n\n```php \n\n\u003c?php\n\nnamespace App\\Providers\\Implementations;\n\nuse App\\Providers\\IUserPostgresProvider;\nuse PDO;\n\nclass UserPostgresProvider implements IUserPostgresProvider\n{\n  public function __construct(private PDO $pdo)\n  {\n  }\n\n  public function fetch(int $id): array \n  {\n    return $this-\u003epdo-\u003equery(\"...\");\n  }\n}\n\n```\n\n\u003e [!NOTE]\n\u003e `IUserRepository` será responsável por definir o contrato de persistência dos dados.\n\u003e\n\n```php\n\n\u003c?php\n\nnamespace App\\Repositories;\n\ninterface IUserRepository\n{\n  public function fetchUser(int $id): array;\n}\n\n```\n\n\u003e [!NOTE]\n\u003e `UserRepository` será responsável por implementar os contratos definidos por `IUserRepository`.\n\u003e No **construtor** de `UserRepository` é passado como **inversão de depedência** a interface `IUserPostgresProvider`.\n\u003e \n\n```php \n\n\u003c?php\n\nnamespace App\\Repositories\\Implementations;\n\nuse App\\Providers\\IUserPostgresProvider;\nuse App\\Repositories\\IUserRepository;\n\nclass UserRepository implements IUserRepository\n{\n  public function __construct(private IUserPostgresProvider $database)\n  {\n  }\n\n  public function fetchUser(int $id): array\n  {\n    return $this-\u003edatabase-\u003efetch($id);\n  }  \n}\n\n```\n\n\u003e [!NOTE]\n\u003e `IFetchUserUseCase` será responsável por definir o contrato do **use case**.\n\u003e \n\n```php \n\n\u003c?php\n\nnamespace App\\UseCases\\User\\FetchUser;\n\ninterface IFetchUserUseCase\n{\n  public function execute(int $userId): array;\n}\n\n```\n\n\u003e [!NOTE]\n\u003e `FetchUserUseCase` será responsável por implementar `IFetchUserUseCase`, bem como as regras de negócio e nesse caso realizar operações através do **repository**.\n\u003e No **construtor** de `FetchUserUseCase` é passado como **inversão de depedência** o `IUserRepository`.\n\u003e \n\n```php \n\n\u003c?php\n\nnamespace App\\UseCases\\User\\FetchUser;\n\nuse App\\Repositories\\IUserRepository;\nuse App\\UseCases\\User\\FetchUser\\IFetchUserUseCase;\nuse Exception;\n\nclass FetchUserUseCase implements IFetchUserUseCase\n{\n  public function __construct(private IUserRepository $userRepository)\n  {\n  }\n\n  public function execute(int $userId): array\n  {\n    $user = $this-\u003euserRepository-\u003efetchUser($userId);\n\n    if (!$user) {\n      throw new Exception('Sorry, user not found.');\n    }\n\n    return $user;\n  }\n}\n\n\n```\n\n\u003e [!NOTE]\n\u003e No construtor do **controller** é passado como **inversão de depedência** a interface `IFetchUserUseCase`.\n\n```php\n\n\u003c?php\n\nnamespace App\\UseCases\\User\\FetchUser;\n\nuse App\\Http\\Request;\nuse App\\Http\\Response;\nuse App\\UseCases\\User\\FetchUser\\IFetchUserUseCase;\n\nclass FetchUserController\n{\n  public function __construct(private IFetchUserUseCase $fetchUserUseCase)\n  {\n  }\n\n  public function handle(Request $request, Response $response): Response\n  {\n    return $response-\u003ejson([\n      \"data\" =\u003e $this-\u003efetchUserUseCase-\u003eexecute($request-\u003euser()-\u003eid),\n    ]);\n  }\n}\n\n\n```\n\n\u003e [!NOTE]\n\u003e Por último deve-se passar as implementações dos contratos na **factory** do **use case**.\n\u003e \n\n```php \n\n\u003c?php\n\nnamespace App\\UseCases\\User\\FetchUser;\n\nuse App\\Infrastructure\\Postgres;\nuse App\\Providers\\Implementations\\UserPostgresProvider;\nuse App\\Repositories\\Implementations\\UserRepository;\nuse App\\UseCases\\User\\FetchUser\\FetchUserController;\nuse App\\UseCases\\User\\FetchUser\\FetchUserUseCase;\n\nclass FetchUserFactory\n{\n    public function generateInstance(array $databaseConfig): FetchUserController\n    {\n        $postgres            = new Postgres();\n        $postgresProvider    = new UserPostgresProvider($postgres::connect($databaseConfig));\n        $userRepository      = new UserRepository($postgresProvider);\n        $fetchUserUseCase    = new FetchUserUseCase($userRepository);\n        $fetchUserController = new FetchUserController($fetchUserUseCase);\n\n        return $fetchUserController;\n    }\n}\n\n\n```\n\n\u003e [!NOTE]\n\u003e `$databaseConfig` que é passado como paramêtro em `generateInstance`, é o array definido em `config/database.php`. \n\u003e Se quiser mudar o provider de **postgresql** para **mysql** por exemplo, será necessário definir as configurações do **novo banco**.\n\u003e \n\nNo 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**.\n\n`config/database.php`\n\n```php\n\n\u003c?php\n\nreturn [\n  'pgsql' =\u003e [\n    'host'     =\u003e $_ENV['PG_HOST'],\n    'port'     =\u003e $_ENV['PG_PORT'],\n    'database' =\u003e $_ENV['PG_DATABASE'],\n    'username' =\u003e $_ENV['PG_USERNAME'],\n    'password' =\u003e $_ENV['PG_PASSWORD'],\n  ],\n  'mysql' =\u003e [\n    'host'     =\u003e '...',\n    'database' =\u003e '...',\n    'username' =\u003e '...',\n    'password' =\u003e '...'   \n  ]\n];\n\n\n```\n\n`Infrastructure/Mysql.php`\n\n```php\n\n\u003c?php\n\nnamespace App\\Infrastructure;\n\nuse PDO;\n\nclass Mysql\n{\n    public static function connect(array $config): PDO\n    {\n        $host     = $config['mysql']['host'];\n        $dbname   = $config['mysql']['database'];\n        $username = $config['mysql']['username'];\n        $password = $config['mysql']['password'];\n\n        $dns = \"mysql:host=$host;dbname=$dbname;\";\n\n        $pdo = new PDO($dns, $username, $password);\n\n        return $pdo;\n    }\n}\n\n\n```\n\n`Providers/IUserMysqlProvider.php`\n\n```php\n\n\u003c?php\n\nnamespace App\\Providers;\n\ninterface IUserMysqlProvider\n{\n  public function save(array $fields): bool;\n}\n\n```\n\n`Providers/Implementations/UserMysqlProvider.php`\n\n```php\n\n\u003c?php\n\nnamespace App\\Providers\\Implementations;\n\nuse App\\Providers\\IUserMysqlProvider;\nuse PDO;\n\nclass UserMysqlProvider implements IUserMysqlProvider\n{\n    public function __construct(private PDO $pdo)\n    {\n    }\n\n    public function save(array $fields): bool\n    {\n      // ...\n    }\n}\n\n```\n\nPronto, após isso é só passar as depedências na **factory** do **use case** como nos exemplos já mostrados.\n\n### Middlewares\n\nPara adicionar um novo **middleware** segue-se o exemplo abaixo:\n\n```\n|-- Middlewares\n|   |-- NewMiddleware.php\n```\n\n```php \n\n\u003c?php\n\nnamespace App\\Middlewares;\n\nuse App\\Http\\JWT;\nuse App\\Http\\Request;\nuse App\\Http\\Response;\n\nclass NewMiddleware\n{\n    public function handle(Request $request, Response $respose)\n    {\n        $a = 1;\n        $b = 2;\n\n        if ($a !== $b) {\n          return $respose-\u003ejson(['message' =\u003e 'Unauthorized'], 401);\n        }\n    }\n}\n\n```\n\nO próximo passado será associar o **middleware** com uma **chave única** em `config/middlewares.php`.\n\n```php \n\n\u003c?php \n\nreturn [\n  'auth'  =\u003e App\\Middlewares\\EnsureAuthenticatedMiddleware::class,\n  'equal' =\u003e App\\Middlewares\\NewMiddleware::class,\n]\n\n```\n\nPor último é só usar o **middleware** na **rota**.\n\n```php \n\nRouter::get('/users/fetch', 'User\\FetchUser\\FetchUserController')-\u003emiddlewares('auth', 'equal');\n\n```\n\n#### Execution ⚙️\n\n\u003e\n\u003e [!NOTE]\n\u003e Siga os passos abaixo para a execução do projeto em **ambiente de desenvolvimento**.\n\nO primeiro passo, é renomear o arquivo `.env.example` para `.env`, o mesmo se encontra em `/www`.\n\n```sh \n\n# Install deps www/\n$ cd www \u0026\u0026 composer install\n\n# Install deps web/\n$ cd web \u0026\u0026 pnpm install\n\n# Docker\n$ docker compose -f \"docker-compose-dev.yml\" up -d --build\n\n# Tests\n$ cd www \u0026\u0026 composer test\n\n```\n\n#### Author 🦆\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\n      \u003ca href=\"https://www.instagram.com/ericneves_dev/\"\u003e\n        \u003cimg src=\"https://avatars.githubusercontent.com/u/32256029\" width=\"100px;\" alt=\"\"/\u003e\n        \u003cbr /\u003e\n        \u003csub\u003e\n          \u003cb\u003eEric Neves\u003c/b\u003e\n        \u003c/sub\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\n      \u003ca href=\"https://www.instagram.com/ericneves_dev/\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/Instagram-E4405F?style=for-the-badge\u0026logo=instagram\u0026logoColor=white\" width=\"100%\"\u003e\n      \u003c/a\u003e \n      \u003cbr /\u003e\n      \u003ca href=\"https://linkedin.com/in/ericnevesrr\"\u003e \n        \u003cimg src=\"https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge\u0026logo=linkedin\u0026logoColor=white\" width=\"100%\"\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n#### License 📋\n\n\u003cimg src=\"https://img.shields.io/github/license/ericneves/myFavoriteBooks?style=for-the-badge\u0026logo=git\u0026logoColor=%23C71D23\u0026color=%23C71D23\"\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericneves%2Fmyfavoritebooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericneves%2Fmyfavoritebooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericneves%2Fmyfavoritebooks/lists"}