diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..26c5802 --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,11 @@ +name: "Continuous Integration" + +on: + pull_request: + push: + branches: + tags: + +jobs: + ci: + uses: laminas/workflow-continuous-integration/.github/workflows/continuous-integration.yml@1.x diff --git a/.github/workflows/cs-tests.yml b/.github/workflows/cs-tests.yml deleted file mode 100644 index ccd3f74..0000000 --- a/.github/workflows/cs-tests.yml +++ /dev/null @@ -1,47 +0,0 @@ -on: - - push - -name: Run phpcs checks - -jobs: - mutation: - name: PHP ${{ matrix.php }}-${{ matrix.os }} - - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: - - ubuntu-latest - - php: - - "8.2" - - "8.3" - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "${{ matrix.php }}" - tools: composer:v2, cs2pr - coverage: none - - - name: Determine composer cache directory - run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - - - name: Cache dependencies installed with composer - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php }}-composer- - - - name: Install dependencies with composer - run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - - name: Run phpcs checks - run: vendor/bin/phpcs diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml new file mode 100644 index 0000000..1a7aa24 --- /dev/null +++ b/.github/workflows/docs-build.yml @@ -0,0 +1,16 @@ +name: docs-build + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build-deploy: + runs-on: ubuntu-latest + steps: + - name: Build Docs + uses: dotkernel/documentation-theme/github-actions/docs@main + env: + DEPLOY_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index 7db2f86..0000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,55 +0,0 @@ -on: - - push - -name: Run PHPUnit tests - -jobs: - mutation: - name: PHP ${{ matrix.php }}-${{ matrix.os }} - - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: - - ubuntu-latest - - php: - - "8.2" - - "8.3" - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "${{ matrix.php }}" - tools: composer:v2, cs2pr - coverage: none - - - name: Determine composer cache directory - run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - - - name: Cache dependencies installed with composer - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php }}-composer- - - - name: Install dependencies with composer - run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - - name: Setup project - run: | - mv config/autoload/local.php.dist config/autoload/local.php - mv config/autoload/mail.local.php.dist config/autoload/mail.local.php - mv config/autoload/local.test.php.dist config/autoload/local.test.php - - - name: Run unit tests - run: vendor/bin/phpunit --testsuite=UnitTests --testdox --colors=always - - name: Run functional tests - run: vendor/bin/phpunit --testsuite=FunctionalTests --testdox --colors=always diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml deleted file mode 100644 index 8d7d667..0000000 --- a/.github/workflows/static-analysis.yml +++ /dev/null @@ -1,47 +0,0 @@ -on: - - push - -name: Run static analysis - -jobs: - mutation: - name: PHP ${{ matrix.php }}-${{ matrix.os }} - - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: - - ubuntu-latest - - php: - - "8.2" - - "8.3" - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "${{ matrix.php }}" - tools: composer:v2, cs2pr - coverage: none - - - name: Determine composer cache directory - run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - - - name: Cache dependencies installed with composer - uses: actions/cache@v3 - with: - path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php }}-composer- - - - name: Install dependencies with composer - run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - - name: Static analysis - run: vendor/bin/psalm --output-format=github --show-info=false --threads=4 --php-version="${{ matrix.php }}" diff --git a/.laminas-ci/pre-run.sh b/.laminas-ci/pre-run.sh new file mode 100755 index 0000000..f6351b8 --- /dev/null +++ b/.laminas-ci/pre-run.sh @@ -0,0 +1,15 @@ +JOB=$3 +PHP_VERSION=$4 +COMMAND=$(echo "${JOB}" | jq -r '.command') + +echo "Running $COMMAND" + +if [[ ${COMMAND} =~ phpunit ]];then + + apt-get install php"${PHP_VERSION}"-sqlite3 + + cp config/autoload/local.php.dist config/autoload/local.php + cp config/autoload/mail.local.php.dist config/autoload/mail.local.php + cp config/autoload/local.test.php.dist config/autoload/local.test.php + +fi diff --git a/README.md b/README.md index 6fa2d9c..f82fd80 100644 --- a/README.md +++ b/README.md @@ -10,28 +10,27 @@ Based on Enrico Zimuel's [Zend Expressive API - Skeleton example](https://github [![GitHub stars](https://img.shields.io/github/stars/dotkernel/api)](https://github.com/dotkernel/api/stargazers) [![GitHub license](https://img.shields.io/github/license/dotkernel/api)](https://github.com/dotkernel/api/blob/4.0/LICENSE.md) -[![Build Static](https://github.com/dotkernel/api/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/api/actions/workflows/static-analysis.yml) +[![Build Static](https://github.com/dotkernel/api/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/api/actions/workflows/continuous-integration.yml) [![Build Static](https://github.com/dotkernel/api/actions/workflows/run-tests.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/api/actions/workflows/run-tests.yml) [![codecov](https://codecov.io/gh/dotkernel/api/graph/badge.svg?token=53FN78G5CK)](https://codecov.io/gh/dotkernel/api) [![Qodana](https://github.com/dotkernel/api/actions/workflows/qodana_code_quality.yml/badge.svg)](https://github.com/dotkernel/api/actions/workflows/qodana_code_quality.yml) [![SymfonyInsight](https://insight.symfony.com/projects/7f9143cc-5e3c-4cfc-992c-377a001fde3e/big.svg)](https://insight.symfony.com/projects/7f9143cc-5e3c-4cfc-992c-377a001fde3e) - ## Getting Started -### Step 1: Clone the project +## Step 1: Clone the project + Using your terminal, navigate inside the directory you want to download the project files into. Make sure that the directory is empty before proceeding to the download process. Once there, run the following command: git clone https://github.com/dotkernel/api.git . - -### Step 2: Install project's dependencies +## Step 2: Install project's dependencies composer install +## Step 3: Development mode -### Step 3: Development mode If you're installing the project for development, make sure you have development mode enabled, by running: composer development-enable @@ -44,22 +43,21 @@ You can check if you have development mode enabled by running: composer development-status +## Step 4: Prepare config files -### Step 4: Prepare config files * duplicate `config/autoload/cors.local.php.dist` as `config/autoload/cors.local.php` <- if your API will be consumed by another application, make sure configure the `allowed_origins` * duplicate `config/autoload/local.php.dist` as `config/autoload/local.php` * duplicate `config/autoload/mail.local.php.dist` as `config/autoload/mail.local.php` <- if your API will send emails, make sure you fill in SMTP connection params * **optional**: in order to run/create tests, duplicate `config/autoload/local.test.php.dist` as `config/autoload/local.test.php` <- this creates a new in-memory database that your tests will run on +## Step 5: Setup database -### Step 5: Setup database +## Running migrations -#### Running migrations: * create a new MySQL database - set collation to `utf8mb4_general_ci` * fill out the database connection params in `config/autoload/local.php` under `$databases['default']` * run the database migrations by using the following command: - php vendor/bin/doctrine-migrations migrate This command will prompt you to confirm that you want to run it: @@ -68,16 +66,17 @@ This command will prompt you to confirm that you want to run it: Hit `Enter` to confirm the operation. -#### Executing fixtures: -**Fixtures are used to seed the database with initial values and should be executed after migrating the database.** +## Executing fixtures -To list all the fixtures, run: +**Fixtures are used to seed the database with initial values and should be executed after migrating the database.** + +To list all the fixtures, run: php bin/doctrine fixtures:list This will output all the fixtures in the order of execution. -To execute all fixtures, run: +To execute all fixtures, run: php bin/doctrine fixtures:execute @@ -87,22 +86,22 @@ To execute a specific fixture, run: More details on how fixtures work can be found here: https://github.com/dotkernel/dot-data-fixtures#creating-fixtures -### Step 6: Test the installation +## Step 6: Test the installation php -S 0.0.0.0:8080 -t public -Sending a GET request to the [home page](http://localhost:8080/) should output the following message: -```json -{ - "message": "Welcome to DotKernel API!" -} -``` +Sending a GET request to the [home page](http://0.0.0.0:8080/) should output the following message: + { + "message": "Welcome to DotKernel API!" + } ## Documentation + In order to access DotKernel API documentation, check the provided [readme file](documentation/README.md). Additionally, each CLI command available has it's own documentation: + * [Create admin account](documentation/command/admin-create.md) * [Generate database migrations](documentation/command/migrations-diff.md) * [Display available endpoints](documentation/command/route-list.md) diff --git a/composer.json b/composer.json index ea0dddf..d9c4f10 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "require": { "php": "~8.2.0 || ~8.3.0", "ext-json": "*", + "ext-gd": "*", "dotkernel/dot-annotated-services": "^4.1.7", "dotkernel/dot-cache": "^4.0", "dotkernel/dot-cli": "^3.5.0", diff --git a/docs/book/index.md b/docs/book/index.md new file mode 100644 index 0000000..2eceefd --- /dev/null +++ b/docs/book/index.md @@ -0,0 +1 @@ +# ../../README.md diff --git a/docs/book/v4/introduction/file-structure.md b/docs/book/v4/introduction/file-structure.md new file mode 100644 index 0000000..6ac3990 --- /dev/null +++ b/docs/book/v4/introduction/file-structure.md @@ -0,0 +1,54 @@ +# File structure + +It is a good practice to standardize the file structure of projects. This way it’s easier to keep a clean overview of multiple projects, and less time is wasted trying to find the correct class. + +When using DotKernel API the following structure is recommended: + +## Main directories + +* `src` - should contain the source code files +* `templates` - should contain the page templates and layouts +* `data` - should contain project-related data (AVOID storing sensitive data on VCS) +* `docs` - should contain project-related documentation + +These directories reside in one of the following directories: + +* if the Module is a composer package where the directories above are stored in the package’s root path, eg.: `/vendor/my-name/my-project-name/` +* if the Module is an extension/component for the project, eg.: `/src/MyProjectName` + +## The `src` directory + +This directory contains all source code related to the Module. It should contain following directories, if they’re not empty: + +* Handler - Action classes (similar to Controllers but can only perform one action) +* Entity - For database entities +* Service - Service classes +* Collection - Database entities collections +* Repository - Entity repository folder + +> The above example is just some of the directories a project may include, but these should give you an idea of how the structure should look like. + +Other classes in the `src` directory may include `InputFilter`, `EventListener`, `Helper`, `Command`, `Factory` etc. + +The `src` directory should also contain 2 files: + +* `ConfigProvider.php` - Provides configuration data +* `RoutesDelegator.php` - Module main routes entry file + +## The `templates` directory + +This directory contains the template files, used for example to help render e-mail templates. + +> DotKernel API uses twig as Templating Engine. All template files have the extension .html.twig + +## The `data` directory + +This directory contains project-related data (such as cache, file uploads) + +We recommend using the following directory structure: + +* `data/cache` - location where caches are stored +* `data/oauth` - encryption, private and public keys needed for authentication. +* `data/lock` - folder where lock files generated by commands are stored, if enabled +* `data/doctrine/fixtures` - folder for doctrine data fixtures +* `data/doctrine/migrations` - folder for doctrine migrations diff --git a/docs/book/v4/introduction/getting-started.md b/docs/book/v4/introduction/getting-started.md new file mode 100644 index 0000000..db745aa --- /dev/null +++ b/docs/book/v4/introduction/getting-started.md @@ -0,0 +1,5 @@ +# Clone the project + +Using your terminal, navigate inside the directory you want to download the project files into. Make sure that the directory is empty before proceeding to the download process. Once there, run the following command: + + git clone https://github.com/dotkernel/api.git . diff --git a/docs/book/v4/introduction/installation.md b/docs/book/v4/introduction/installation.md new file mode 100644 index 0000000..2e11b81 --- /dev/null +++ b/docs/book/v4/introduction/installation.md @@ -0,0 +1,87 @@ +# Installation + +## Install dependencies + + composer install + +## Development mode + +If you're installing the project for development, make sure you have development mode enabled, by running: + + composer development-enable + +You can disable development mode by running: + + composer development-disable + +You can check if you have development mode enabled by running: + + composer development-status + +## Prepare config files + +* duplicate `config/autoload/cors.local.php.dist` as `config/autoload/cors.local.php` <- if your API will be consumed by another application, make sure configure the `allowed_origins` +* duplicate `config/autoload/local.php.dist` as `config/autoload/local.php` +* duplicate `config/autoload/mail.local.php.dist` as `config/autoload/mail.local.php` <- if your API will send emails, make sure you fill in SMTP connection params +* **optional**: in order to run/create tests, duplicate `config/autoload/local.test.php.dist` as `config/autoload/local.test.php` <- this creates a new in-memory database that your tests will run on. + +## Setup database + +Make sure you fill out the database credentials in `config/autoload/local.php` under `$databases['default']`. + +## Running migrations + +* create a new MySQL database - set collation to `utf8mb4_general_ci` +* run the database migrations by using the following command: + + php vendor/bin/doctrine-migrations migrate + +This command will prompt you to confirm that you want to run it. + + WARNING! You are about to execute a migration in database "..." that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]: + +Hit `Enter` to confirm the operation. + +## Executing fixtures + +**Fixtures are used to seed the database with initial values and should be executed after migrating the database.** + +To list all the fixtures, run: + + php bin/doctrine fixtures:list + +This will output all the fixtures in the order of execution. + +To execute all fixtures, run: + + php bin/doctrine fixtures:execute + +To execute a specific fixture, run: + + php bin/doctrine fixtures:execute --class=FixtureClassName + +More details on how fixtures work can be found here: https://github.com/dotkernel/dot-data-fixtures#creating-fixtures + +## Test the installation + + php -S 0.0.0.0:8080 -t public + +Sending a GET request to the [home page](http://0.0.0.0:8080/) should output the following message: + + { + "message": "Welcome to DotKernel API!" + } + +## Running tests + +The project has 2 types of tests : functional and unit tests, you can run both types at the same type by executing this command: + + php vendor/bin/phpunit + +## Running unit tests + + vendor/bin/phpunit --testsuite=UnitTests --testdox --colors=always + +## Running functional tests + + vendor/bin/phpunit --testsuite=FunctionalTests --testdox --colors=always diff --git a/docs/book/v4/introduction/introduction.md b/docs/book/v4/introduction/introduction.md new file mode 100644 index 0000000..9bba1b7 --- /dev/null +++ b/docs/book/v4/introduction/introduction.md @@ -0,0 +1,105 @@ +# Introduction + +Based on Enrico Zimuel’s Zend Expressive API – Skeleton example, DotKernel API runs on Laminas and Mezzio components and implements standards like PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15. + +Here is a list of the core components: + +* Middleware Microframework (mezzio/mezzio) +* Error Handler (dotkernel/dot-errorhandler) +* Problem Details (mezzio-problem-details) +* CORS (mezzio-cors) +* Routing (mezzio/mezzio-fastroute) +* Authentication (mezzio-authentication) +* Authorization (mezzio-authorization) +* Config Aggregator (laminas/laminas-config-aggregator) +* Container (roave/psr-container-doctrine) +* Implicit Head, Options and Method Not Allowed +* Annotations (dotkernel/dot-annotated-services) +* Input Filter (laminas/laminas-inputfilter) +* Doctrine +* Hydrator (laminas/laminas-hydrator) +* Paginator (laminas/laminas-paginator) +* HAL (mezzio-hal) +* CLI (dotkernel/dot-cli) +* TwigRenderer (mezzio/mezzio-twigrenderer) +* Fixtures (dotkernel/dot-data-fixtures) +* UUID (ramsey/uuid-doctrine) + +## Doctrine 2 ORM + +For the persistence in a relational database management system we chose Doctrine ORM (object-relational mapper) . + +The benefit of Doctrine for the programmer is the ability to focus on the object-oriented business logic and worry about persistence only as a secondary priority. + +## Documentation + +Our documentation is Postman based. We use the following files in which we store information about every available endpoint ready to be tested: + + documentation/DotKernel_API.postman_collection.json + documentation/DotKernel_API.postman_environment.json + +## Hypertext Application Language + +For our API payloads ( a value object for describing the API resource, its relational links and any embedded/child resources related to it ) we chose mezzio-hal. + +## CORS + +By using `MezzioCorsMiddlewareCorsMiddleware`, the CORS preflight will be recognized and the middleware will start to detect the proper CORS configuration. The Router is used to detect every allowed request method by executing a route match with all possible request methods. Therefore, for every preflight request, there is at least one Router request. + +## OAuth 2 + +OAuth 2 is an authorization framework that enables applications to obtain limited access to user accounts on your DotKernel API. We are using mezzio/mezzio-authentication-oauth2 which provides OAuth2 authentication for Mezzio and PSR-7/PSR-15 applications by using league/oauth2-server package. + +## Email + +It is not unlikely for an API to send emails depending on the use case. Here is another area where DotKernel API shines. Using `DotMailServiceMailService` provided by dotkernel/dot-mail you can easily send custom email templates. + +## Configuration + +From authorization at request route level to API keys for your application, you can find every configuration variable in the config directory. + +Registering a new module can be done by including its ConfigProvider.php in config.php. + +Brand new middlewares should go into pipeline.php. Here you can edit the order in which they run and find more info about the currently included ones. + +You can further customize your api within the autoload directory where each configuration category has its own file. + +## Routing + +Each Module has a `RoutesDelegator.php` file for managing existing routes inside that specific module. It also allows a quick way of adding new routes by providing the route path, Middlewares that the route will use and the route name. + +You can allocate permissions per route name in order to restrict access for a user role to a specific route in `config/autoload/authorization.global.php`. + +## Commands + +For registering new commands first make sure your command class extends `SymfonyComponentConsoleCommandCommand`. Then you can enable it by registering it in `config/autoload/cli.global.php`. + +## File locker + +Here you will also find our brand-new file locker configuration, so you can easily turn it on or off ( by default: `'enabled' => true` ) + +Note: The File Locker System will create a `command-{command-default-name}.lock` file which will not let another instance of the same command to run until the previous one has finished. + +## PSR Standards + +* PSR-3: Logger Interface – the application uses `LoggerInterface` for error logging +* PSR-4: Autoloader – the application locates classes using an autoloader +* PSR-7: HTTP message interfaces – the handlers return `ResponseInterface` +* PSR-11: Container interface – the application is container-based +* PSR-15: HTTP Server Request Handlers – the handlers implement `RequestHandlerInterface` + +## Tests + +One of the best ways to ensure the quality of your product is to create and run functional and unit tests. You can find factory-made tests in the tests/AppTest/ folder, and you can also register your own. + +We have 2 types of tests: functional and unit tests, you can run both types at the same type by executing this command: + + php vendor/bin/phpunit + +## Running unit tests + + vendor/bin/phpunit --testsuite=UnitTests --testdox --colors=always + +## Running functional tests + + vendor/bin/phpunit --testsuite=FunctionalTests --testdox --colors=always diff --git a/docs/book/v4/introduction/packages.md b/docs/book/v4/introduction/packages.md new file mode 100644 index 0000000..d64ca86 --- /dev/null +++ b/docs/book/v4/introduction/packages.md @@ -0,0 +1,30 @@ +# Packages + +* `dotkernel/dot-annotated-services` - Dependency injection component using class attributes. +* `dotkernel/dot-cache` - Cache component extending symfony-cache +* `dotkernel/dot-cli` - Component for creating console applications based on laminas-cli +* `dotkernel/dot-data-fixtures` - Provides a CLI interface for listing & executing doctrine data fixtures +* `dotkernel/dot-errorhandler` - Logging Error Handler for Middleware Applications +* `dotkernel/dot-mail` - Mail component based on laminas-mail +* `dotkernel/dot-response-header` - Middleware for setting custom response headers. +* `laminas/laminas-component-installer` - Composer plugin for injecting modules and configuration providers into application configuration +* `laminas/laminas-config` - Provides a nested object property based user interface for accessing this configuration data within application code +* `laminas/laminas-config-aggregator` - Lightweight library for collecting and merging configuration from different sources +* `laminas/laminas-http` - Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests +* `laminas/laminas-hydrator` - Serialize objects to arrays, and vice versa +* `laminas/laminas-inputfilter` - Normalize and validate input sets from the web, APIs, the CLI, and more, including files +* `laminas/laminas-paginator` - Paginate collections of data from arbitrary sources +* `laminas/laminas-stdlib` - SPL extensions, array utilities, error handlers, and more +* `laminas/laminas-text` - Create FIGlets and text-based tables +* `mezzio/mezzio` - PSR-15 Middleware Microframework +* `mezzio/mezzio-authentication-oauth2` - OAuth2 (server) authentication middleware for Mezzio and PSR-7 applications +* `mezzio/mezzio-authorization-acl` - laminas-permissions-acl adapter for mezzio-authorization +* `mezzio/mezzio-authorization-rbac` - mezzio authorization rbac adapter for laminas/laminas-permissions-rbac +* `mezzio/mezzio-cors` - CORS component for Mezzio and other PSR-15 middleware runners +* `mezzio/mezzio-fastroute` - FastRoute integration for Mezzio +* `mezzio/mezzio-hal` - Hypertext Application Language implementation for PHP and PSR-7 +* `mezzio/mezzio-problem-details` - Problem Details for PSR-7 HTTP APIs addressing the RFC 7807 standard +* `mezzio/mezzio-twigrenderer` - Twig integration for Mezzio +* `ramsey/uuid-doctrine` - Use ramsey/uuid as a Doctrine field type +* `roave/psr-container-doctrine` - Doctrine Factories for PSR-11 Containers +* `symfony/filesystem` - Provides basic utilities for the filesystem diff --git a/docs/book/v4/introduction/server-requirements.md b/docs/book/v4/introduction/server-requirements.md new file mode 100644 index 0000000..789238b --- /dev/null +++ b/docs/book/v4/introduction/server-requirements.md @@ -0,0 +1,35 @@ +# Server Requirements + +For production, we highly recommend a *nix based system. + +## Webserver + +* Apache >= 2.2 **or** Nginx +* mod_rewrite +* .htaccess support `(AllowOverride All)` + +## PHP >= 8.2 + +Both mod_php and FCGI (FPM) are supported. + +## Required Settings and Modules & Extensions + +* memory_limit >= 128M +* upload_max_filesize and post_max_size >= 100M (depending on your data) +* mbstring +* CLI SAPI (for Cron Jobs) +* Composer (added to $PATH) + +## RDBMS + +* MySQL / MariaDB >= 5.5.3 + +## Recommended extensions + +* opcache +* pdo_mysql or mysqli (if using MySQL or MariaDB as RDBMS) +* dom - if working with markup files structure (html, xml, etc) +* simplexml - working with xml files +* gd, exif - if working with images +* zlib, zip, bz2 - if compessing files +* curl (required if APIs are used) diff --git a/docs/book/v4/tutorials/create-book-module.md b/docs/book/v4/tutorials/create-book-module.md new file mode 100644 index 0000000..eb1fac9 --- /dev/null +++ b/docs/book/v4/tutorials/create-book-module.md @@ -0,0 +1,606 @@ +# Implementing a book module in DotKernel API + +## File structure + +The below file structure is just an example, you can have multiple components such as event listeners, wrappers, etc. + + /src/ + /Book/ + /src/ + /Collection/ + /BookCollection.php + /Entity/ + /Book.php + /Handler/ + /BookHandler.php + /Repository/ + /BookRepository.php + /Service/ + /BookService.php + /InputFilter/ + /Input/ + /NameInput.php + /AuthorInput.php + /ReleaseDateInput.php + /BookInputFilter.php + ConfigProvider.php + RoutesDelegator.php + +* `src/Book/src/Collection/BookCollection.php` - a collection refers to a container for a group of related objects, typically used to manage sets of related entities fetched from a database +* `src/Book/src/Entity/Book.php` - an entity refers to a PHP class that represents a persistent object or data structure +* `src/Book/src/Handler/BookHandler.php` - handlers are middleware that can handle requests based on an action +* `src/Book/src/Repository/BookRepository.php` - a repository is a class responsible for querying and retrieving entities from the database +* `src/Book/src/Service/BookService.php` - is a class or component responsible for performing a specific task or providing functionality to other parts of the application +* `src/Book/src/ConfigProvider.php` - is a class that provides configuration for various aspects of the framework or application +* `src/Book/src/RoutesDelegator.php` - a routes delegator is a delegator factory responsible for configuring routing middleware based on routing configuration provided by the application +* `src/Book/src/InputFilter/BookInputFilter.php` - input filters and validators +* `src/Book/src/InputFilter/Input/*` - input filters and validator configurations + +## File creation and contents + +* `src/Book/src/Collection/BookCollection.php` + + setName($name); + $this->setAuthor($author); + $this->setReleaseDate($releaseDate); + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getAuthor(): string + { + return $this->author; + } + + public function setAuthor(string $author): self + { + $this->author = $author; + + return $this; + } + + public function getReleaseDate(): DateTimeImmutable + { + return $this->releaseDate; + } + + public function setReleaseDate(DateTimeImmutable $releaseDate): self + { + $this->releaseDate = $releaseDate; + + return $this; + } + + public function getArrayCopy(): array + { + return [ + 'uuid' => $this->getUuid()->toString(), + 'name' => $this->getName(), + 'author' => $this->getAuthor(), + 'releaseDate' => $this->getReleaseDate(), + ]; + } + } + +* `src/Book/src/Repository/BookRepository.php` + + + */ + class BookRepository extends EntityRepository + { + public function saveBook(Book $book): Book + { + $this->getEntityManager()->persist($book); + $this->getEntityManager()->flush(); + + return $book; + } + + public function getBooks(array $filters = []): BookCollection + { + $page = PaginationHelper::getOffsetAndLimit($filters); + + $qb = $this + ->getEntityManager() + ->createQueryBuilder() + ->select('book') + ->from(Book::class, 'book') + ->orderBy($filters['order'] ?? 'book.created', $filters['dir'] ?? 'desc') + ->setFirstResult($page['offset']) + ->setMaxResults($page['limit']); + + $qb->getQuery()->useQueryCache(true); + + return new BookCollection($qb, false); + } + } + +* `src/Book/src/Service/BookService.php` + + bookRepository->saveBook($book); + } + + public function getBooks(array $filters = []) + { + return $this->bookRepository->getBooks($filters); + } + } + +* `src/Book/src/Service/BookServiceInterface.php` + + $this->getDependencies(), + MetadataMap::class => $this->getHalConfig(), + ]; + } + + public function getDependencies(): array + { + return [ + 'factories' => [ + BookHandler::class => AnnotatedServiceFactory::class, + BookService::class => AnnotatedServiceFactory::class, + BookRepository::class => AnnotatedRepositoryFactory::class, + ], + 'aliases' => [ + BookServiceInterface::class => BookService::class, + ], + ]; + } + + public function getHalConfig(): array + { + return [ + AppConfigProvider::getCollection(BookCollection::class, 'books.list', 'books'), + AppConfigProvider::getResource(Book::class, 'book.create'), + ]; + } + } + +* `src/Book/src/RoutesDelegator.php` + + get( + '/books', + BookHandler::class, + 'books.list' + ); + + $app->post( + '/book', + BookHandler::class, + 'book.create' + ); + + return $app; + } + } + +* `src/Book/src/InputFilter/BookInputFilter.php` + + add(new NameInput('name')); + $this->add(new AuthorInput('author')); + $this->add(new ReleaseDateInput('releaseDate')); + } + } + +* `src/Book/src/InputFilter/Input/AuthorInput.php` + + setRequired($isRequired); + + $this->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + + $this->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'author'), + ], true); + } + } + +* `src/Book/src/InputFilter/Input/NameInput.php` + + setRequired($isRequired); + + $this->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + + $this->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'name'), + ], true); + } + } + +* `src/Book/src/InputFilter/Input/ReleaseDateInput.php` + + setRequired($isRequired); + + $this->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + + $this->getValidatorChain() + ->attachByName(Date::class, [ + 'message' => sprintf(Message::INVALID_VALUE, 'releaseDate'), + ], true); + } + } + +* `src/Book/src/Handler/BookHandler.php` + + bookService->getBooks($request->getQueryParams()); + + return $this->createResponse($request, $books); + } + + public function post(ServerRequestInterface $request): ResponseInterface + { + $inputFilter = (new BookInputFilter())->setData($request->getParsedBody()); + if (! $inputFilter->isValid()) { + return $this->errorResponse($inputFilter->getMessages()); + } + + $book = $this->bookService->createBook($inputFilter->getValues()); + + return $this->createResponse($request, $book); + } + } + +## Configuring and registering the new module + +Once you set up all the files as in the example above, you will need to do a few additional configurations: + +* Register the namespace by adding this line `"Api\\Book\\": "src/Book/src/",` in `composer.json` under the `autoload.psr-4` key. +* Register the module by adding `Api\Book\ConfigProvider::class,` under `Api\User\ConfigProvider::class,`. +* Register the module's routes by adding `\Api\Book\RoutesDelegator::class,` under `\Api\User\RoutesDelegator::class,` in `src/App/src/ConfigProvider.php`. + +It should look like this: + + public function getDependencies(): array + { + return [ + 'delegators' => [ + Application::class => [ + RoutesDelegator::class, + \Api\Admin\RoutesDelegator::class, + \Api\User\RoutesDelegator::class, + \Api\Book\RoutesDelegator::class, + ], + ], + 'factories' => [ + ... + ] + ... + +* In `src/config/autoload/doctrine.global.php` add this under the `doctrine.driver` key: + + 'BookEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/Book/src/Entity', + ], + +* `Api\\Book\Entity' => 'BookEntities',` add this under the `doctrine.driver.drivers` key + +Example: + + [ + ... + 'driver' => [ + 'orm_default' => [ + 'class' => MappingDriverChain::class, + 'drivers' => [ + 'Api\\App\Entity' => 'AppEntities', + 'Api\\Admin\\Entity' => 'AdminEntities', + 'Api\\User\\Entity' => 'UserEntities', + 'Api\\Book\Entity' => 'BookEntities', + ], + ], + 'AdminEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/Admin/src/Entity', + ], + 'UserEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/User/src/Entity', + ], + 'AppEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/App/src/Entity', + ], + 'BookEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => __DIR__ . '/../../src/Book/src/Entity', + ], + ], + ... + +Next we need to configure access to the newly created endpoints, add `books.list` and `book.create` to the authorization rbac array, under the `UserRole::ROLE_GUEST` key. +> Make sure you read and understand the rbac documentation. + +## Migrations + +We created the `Book` entity, but we didn't create the associated table for it. + +Doctrine can handle the table creation, run the following command: + + vendor/bin/doctrine-migrations diff --filter-expression='/^(?!oauth_)/' + +This will check for differences between your entities and database structure and create migration files if necessary, in `data/doctrine/migrations`. + +To execute the migrations run: + + vendor/bin/doctrine-migrations migrate + +## Checking endpoints + +If we did everything as planned we can call the `http://0.0.0.0:8080/book` endpoint and create a new book: + + curl -X POST http://0.0.0.0:8080/book + -H "Content-Type: application/json" + -d '{"name": "test", "author": "author name", "releaseDate": "2023-03-03"}' + +To list the books use : + + curl http://0.0.0.0:8080/books diff --git a/documentation/README.md b/documentation/README.md index cbb02ed..0f6968c 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -17,7 +17,7 @@ At this point, we assume you already have Postman installed. The following steps You should see a new collection (`DotKernel_API`) added to your collection list, containing the documentation of all DotKernel API endpoints. Also, you should see a new environment (`DotKernel_API`) added to your environments. -This contains a variable, called `APPLICATION_URL` set to `http://localhost:8080`. +This contains a variable, called `APPLICATION_URL` set to `http://0.0.0.0:8080`. If your application runs on a different URL/port, modify this variable accordingly. ## Usage diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..ec0644c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,24 @@ +docs_dir: docs/book +site_dir: docs/html +extra: + project: "DotKernel API" + current_version: v4 + versions: + - v4 +nav: + - Home: index.md + - v4: + - Introduction: + - "Introduction": v4/introduction/introduction.md + - "Getting Started": v4/introduction/getting-started.md + - "Server Requirements": v4/introduction/server-requirements.md + - "File Structure": v4/introduction/file-structure.md + - "Installation": v4/introduction/installation.md + - "Packages": v4/introduction/packages.md + - Tutorials: + - "Creating a book module": v4/tutorials/create-book-module.md +site_name: api +site_description: "DotKernel API" +repo_url: "https://github.com/dotkernel/api" +plugins: + - search