55 | in your web browser.
56 |
57 | **Note:** The PHP internal webserver is designed for
58 | application development, testing or application demonstrations.
59 | It is not intended to be a full-featured web server.
60 | It should not be used on a public network.
61 |
--------------------------------------------------------------------------------
/docs/cache.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Cache
4 | parent: Advanced
5 | ---
6 |
7 | # Cache
8 |
9 | ## Introduction
10 |
11 | Some data retrieval or processing tasks performed by your
12 | application could be CPU intensive or take several seconds to complete.
13 | When this is the case, it is common to cache the retrieved data for a
14 | time, so it can be retrieved quickly on subsequent requests for the same data.
15 |
16 | ## HTTP Caching
17 |
18 | Slim uses the optional standalone [slimphp/Slim-HttpCache](https://github.com/slimphp/Slim-HttpCache) PHP component
19 | for HTTP caching. You can use this component to create and return responses that
20 | contain `Cache`, `Expires`, `ETag`, and `Last-Modified` headers that control
21 | when and how long application output is retained by client-side caches. You may have to set your php.ini setting "session.cache_limiter" to an empty string in order to get this working without interferences.
22 |
23 | * [HTTP Caching](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
24 |
25 | ## Storage Caching
26 |
27 | The cached data is usually stored in a very fast data store such as Memcached or Redis.
28 | Thankfully, the [laminas/laminas-cache](https://docs.laminas.dev/laminas-cache/) and
29 | [symfony/cache](https://symfony.com/doc/current/components/cache.html)
30 | components provides a [PSR-6](https://www.php-fig.org/psr/psr-6/) and
31 | [PSR-16](https://www.php-fig.org/psr/psr-16/) compliant API for various cache backends, allowing you to take advantage
32 | of their blazing fast data retrieval and speed up your web application.
33 |
34 | * [Symfony Cache](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
35 | * [Redis](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
36 | * [Memcached](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
37 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Path-based git attributes
2 | # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
3 |
4 | public/* linguist-vendored
5 | docs/* linguist-documentation
6 |
7 | # Set the default behavior, in case people don't have core.autocrlf set.
8 | # Git will always convert line endings to LF on checkout. You should use
9 | # this for files that must keep LF endings, even on Windows.
10 | * text eol=lf
11 |
12 | # ------------------------------------------------------------------------------
13 | # All the files and directories that can be excluded from dist,
14 | # we could have a more clean vendor/
15 | #
16 | # So when someone will install that package through with --prefer-dist option,
17 | # all the files and directories listed in .gitattributes file will be excluded.
18 | # This could have a big impact on big deployments and/or testing.
19 | # ------------------------------------------------------------------------------
20 |
21 | # /.github export-ignore
22 | # /tests export-ignore
23 | # /build export-ignore
24 | # /docs export-ignore
25 | # /.cs.php export-ignore
26 | # /.editorconfig export-ignore
27 | # /.gitattributes export-ignore
28 | # /.gitignore export-ignore
29 | # /.scrutinizer.* export-ignore
30 | # /phpcs.xml export-ignore
31 | # /phpstan.neon export-ignore
32 | # /phpunit.xml export-ignore
33 |
34 | # Define binary file attributes.
35 | # - Do not treat them as text.
36 | # - Include binary diff in patches instead of "binary files differ."
37 | *.pdf binary
38 | *.mo binary
39 | *.gif binary
40 | *.ico binary
41 | *.jpg binary
42 | *.jpeg binary
43 | *.png binary
44 | *.zip binary
45 | *.gif binary
46 | *.ico binary
47 | *.phar binary
48 | *.gz binary
49 | *.otf binary
50 | *.eot binary
51 | *.svg binary
52 | *.ttf binary
53 | *.woff binary
54 | *.woff2 binary
55 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Configuration
4 | parent: Getting Started
5 | nav_order: 2
6 | ---
7 |
8 | # Configuration
9 |
10 | ## Configuration Directory
11 |
12 | The directory for the configuration files is: `config/`
13 |
14 | ## Configuration Environments
15 |
16 | A typical application begins with three environments: dev, prod and test.
17 | Each environment represents a way to execute the same codebase with
18 | different configuration. Each environment
19 | loads its own individual configuration files.
20 |
21 | These different files are organized by environment:
22 |
23 | * for the `dev` environment: `config/local.dev.php`
24 | * for the `prod` environment: `config/local.prod.php`
25 | * for the (phpunit) `test` environment: `config/local.test.php`
26 |
27 | The file `config/settings.php` is the main configuration file and combines
28 | the default settings with environment specific settings.
29 |
30 | The configuration files are loaded in this order:
31 |
32 | * Load default settings from: `config/defaults.php`
33 |
34 | * If the environment variable `APP_ENV` is defined,
35 | load the environment specific file, e.g. `config/local.{env}.php`
36 |
37 | * Load secret credentials (if file exists) from:
38 | * `config/env.php`
39 | * `config/../../env.php`
40 |
41 | To switch the environment you can change the `APP_ENV` environment variable.
42 |
43 | ```php
44 | $_ENV['APP_ENV'] = 'prod';
45 | ```
46 |
47 | ## Secret Credentials
48 |
49 | For security reasons, all secret values
50 | are stored in a file called: **`env.php`**.
51 |
52 | Create a copy of the file `config/env.example.php` and rename it to `config/env.php`
53 |
54 | The `env.php` file is generally kept out of version control
55 | since it can contain sensitive API keys and passwords.
56 |
57 | ## Read more
58 |
59 | * [Environments and Configuration](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
60 |
--------------------------------------------------------------------------------
/docs/database.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Database
4 | nav_order: 6
5 | ---
6 |
7 | # Database
8 |
9 | You have the freedom to choose any database package. Some popular options are:
10 |
11 | * [CakePHP Query Builder](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
12 | * [Laminas Query Builder](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
13 | * [Doctrine DBAL](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
14 | * [Cycle Query Builder](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
15 | * [PDO](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
16 | * [Yii Database](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
17 |
18 | ## Database configuration
19 |
20 | You can configure the database settings for each server environment.
21 |
22 | The default settings are stored in `config/defaults.php`, `$settings['db']`
23 |
24 | ## Read more
25 |
26 | * [Amazon S3](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
27 | * [Amazon DynamoDB](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
28 | * [Apache Cassandra](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
29 | * [Apache Kafka](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
30 | * [Couchbase](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
31 | * [Elasticsearch](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
32 | * [Doctrine CouchDB](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
33 | * [Firebase Realtime Database](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
34 | * [IBM DB2](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
35 | * [Oracle Database](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
36 | * [PostgreSQL](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
37 | * [Microsoft SQL Server](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
38 | * [Multiple database connections](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
39 |
40 |
--------------------------------------------------------------------------------
/docs/deployment.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Deployment
4 | parent: Advanced
5 | ---
6 |
7 | # Deployment
8 |
9 | ## Introduction
10 |
11 | When you're ready to deploy your Slim application to production,
12 | there are some important things you can do to make sure your application
13 | is running as efficiently as possible.
14 |
15 | In this document, we'll cover some great starting points for
16 | making sure your Slim application is deployed properly.
17 |
18 | ## Optimization
19 |
20 | ### Autoloader Optimization
21 |
22 | When deploying to production, make sure that you are optimizing Composer's
23 | class autoloader map so Composer can quickly find the proper
24 | file to load for a given class:
25 |
26 | ```
27 | composer install --optimize-autoloader --no-dev
28 | ```
29 |
30 | In addition to optimizing the autoloader,
31 | you should always be sure to include a `composer.lock` file in
32 | your project's source control repository.
33 | Your project's dependencies can be installed much faster
34 | when a `composer.lock` file is present.
35 |
36 | ### Optimizing Configuration Loading
37 |
38 | When deploying your application to production, you should make sure that you
39 | enable caching to improve the performance.
40 |
41 | This process includes the caching of the routes and the html templates.
42 |
43 | ### Deploying With GitHub Actions
44 |
45 | [GitHub Actions](https://github.com/features/actions) offers a great
46 | way to build and deploy artifacts to your production servers
47 | on various infrastructure providers such as DigitalOcean,
48 | Linode, AWS, and more.
49 |
50 | If you prefer to build and deploy your applications on your
51 | own machine or infrastructure, you may also
52 | try [Apache Ant](https://ant.apache.org/), Phing or [Deployer](https://deployer.org/).
53 |
54 | ## Read more
55 |
56 | * [Apache Ant](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
57 | * [Phing](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Slim 4 Skeleton
6 |
7 |
8 |
9 | [](https://packagist.org/packages/odan/slim4-skeleton)
10 | [](LICENSE)
11 | [](https://github.com/odan/slim4-skeleton/actions)
12 | [](https://coveralls.io/github/odan/slim4-skeleton)
13 | [](https://packagist.org/packages/odan/slim4-skeleton/stats)
14 |
15 | This is a skeleton to quickly set up a new [Slim 4](https://www.slimframework.com/) application.
16 |
17 |
18 |
19 | ## Requirements
20 |
21 | * PHP 8.2 - 8.5
22 |
23 | ## Installation
24 |
25 | Read the **[documentation](https://odan.github.io/slim4-skeleton/installation.html)**
26 |
27 | ## Features
28 |
29 | This project is based on best practices and industry standards:
30 |
31 | * [Standard PHP package skeleton](https://github.com/php-pds/skeleton)
32 | * HTTP router (Slim)
33 | * HTTP message interfaces (PSR-7)
34 | * HTTP Server Request Handlers, Middleware (PSR-15)
35 | * Dependency injection container (PSR-11)
36 | * Autoloader (PSR-4)
37 | * Logger (PSR-3)
38 | * Code styles (PSR-12)
39 | * Single action controllers
40 | * Unit- and integration tests
41 | * Tested with [Github Actions](https://github.com/odan/slim4-skeleton/actions)
42 | * [PHPStan](https://github.com/phpstan/phpstan)
43 |
44 | ## Support
45 |
46 | * [Issues](https://github.com/odan/slim4-skeleton/issues)
47 | * [Blog](https://odan.github.io/)
48 | * [Donate](https://odan.github.io/donate.html) for this project.
49 | * [Slim 4 eBooks](https://odan.github.io/donate.html)
50 |
51 | ## License
52 |
53 | The MIT License (MIT). Please see [License File](LICENSE) for more information.
54 |
--------------------------------------------------------------------------------
/docs/middleware.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Middleware
4 | parent: The Basics
5 | ---
6 |
7 | # Middleware
8 |
9 | The Middleware concept provides a convenient mechanism for inspecting and filtering
10 | HTTP requests entering your application.
11 |
12 | For example, this Slim Skeleton project
13 | includes a middleware that verifies the user of your application is authenticated.
14 | If the user is not authenticated, the middleware will return a `401 Unauthorized`
15 | response. However, if the user is authenticated, the middleware will allow the
16 | request to proceed further into the application.
17 |
18 | ## Registering Middleware
19 |
20 | ### Global middleware
21 |
22 | If you want a middleware to run during every HTTP request to your application,
23 | list the middleware class in the file:
24 | [config/middleware.php](https://github.com/odan/slim4-skeleton/blob/master/config/middleware.php)
25 |
26 | ### Assigning Middleware To Routes
27 |
28 | If you would like to assign middleware to specific routes,
29 | you should first assign the middleware a key in `config/container.php`.
30 |
31 | You can add middleware to all routes,
32 | to a specific route or to a group of routes.
33 | This makes it easier to differentiate between public and protected areas,
34 | as well as API resources etc.
35 |
36 | Once the middleware has been defined in the DI container,
37 | you may use the `add` method to assign the middleware to a route
38 | using the fully qualified class name:
39 |
40 | ```php
41 | $app->get('/my-path', \App\Action\MyAction::class)->add(MyMiddleware::class);
42 | ```
43 |
44 | Assigning middleware to a group of routes:
45 |
46 | ```php
47 | use Slim\Routing\RouteCollectorProxy;
48 |
49 | // ...
50 |
51 | $app->group(
52 | '/my-route-group',
53 | function (RouteCollectorProxy $app) {
54 | $app->get('/sub-resource', \App\Action\MyAction::class);
55 | // ...
56 | }
57 | )->add(MyMiddleware::class);
58 | ```
59 |
60 | ## Read more
61 |
62 | * [Slim 4 - Middleware](https://www.slimframework.com/docs/v4/concepts/middleware.html) (Documentation)
63 | * [Slim 4 - Routing](https://www.slimframework.com/docs/v4/objects/routing.html) (Documentation)
64 |
--------------------------------------------------------------------------------
/docs/directory-structure.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Directory Structure
4 | parent: Getting Started
5 | nav_order: 3
6 | ---
7 |
8 | # Directory structure
9 |
10 | The directory structure is based on the [Standard PHP package skeleton](https://github.com/php-pds/skeleton).
11 |
12 | The `public` directory in your project contains
13 | the front-controller `index.php` and other web accessible files
14 | such as images, CC and JavaScript files.
15 |
16 | The `src` directory contains the core code for your application.
17 |
18 | The `config` directory contains the application settings such as
19 | the routes, service container, database connection and so on.
20 |
21 | The `templates` directory contains the view templates
22 | for your application. You can use the Slim Framework's
23 | template engine, or you can use a third-party
24 | template engine such as Twig or Latte.
25 |
26 | {% raw %}
27 | ```
28 | .
29 | ├── build # Compiled files (artifacts)
30 | ├── config # Configuration files
31 | ├── docs # Documentation files
32 | ├── logs # Log files
33 | ├── public # Web server files
34 | ├── resources # Other resource files
35 | │ ├── migrations # Database migration files
36 | │ ├── seeds # Data seeds
37 | │ └── translations # The .po message files for PoEdit
38 | ├── src # PHP source code (The App namespace)
39 | │ ├── Action # Controller actions (HTTP layer)
40 | │ ├── Console # Console commands
41 | │ ├── Domain # The core application
42 | │ ├── Renderer # Render and Url helper (HTTP layer)
43 | │ ├── Middleware # Middleware (HTTP layer)
44 | │ └── Support # Helper classes and functions
45 | ├── templates # HTML templates
46 | ├── tests # Automated tests
47 | ├── tmp # Temporary files
48 | ├── vendor # Reserved for composer
49 | ├── build.xml # Ant build tasks
50 | ├── composer.json # Project dependencies
51 | ├── LICENSE # The license
52 | └── README.md # This file
53 | ```
54 | {% endraw %}
55 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "odan/slim4-skeleton",
3 | "description": "A Slim 4 skeleton",
4 | "license": "MIT",
5 | "type": "project",
6 | "keywords": [
7 | "slim-framework",
8 | "skeleton",
9 | "slim",
10 | "slim4"
11 | ],
12 | "require": {
13 | "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
14 | "ext-json": "*",
15 | "fig/http-message-util": "^1.1",
16 | "monolog/monolog": "^3",
17 | "nyholm/psr7": "^1.8.1",
18 | "nyholm/psr7-server": "^1.1",
19 | "php-di/php-di": "^7",
20 | "selective/basepath": "^2",
21 | "slim/slim": "^4"
22 | },
23 | "require-dev": {
24 | "friendsofphp/php-cs-fixer": "^3",
25 | "phpstan/phpstan": "^2",
26 | "phpunit/phpunit": "^11",
27 | "selective/test-traits": "^4",
28 | "squizlabs/php_codesniffer": "^4"
29 | },
30 | "autoload": {
31 | "psr-4": {
32 | "App\\": "src/"
33 | }
34 | },
35 | "autoload-dev": {
36 | "psr-4": {
37 | "App\\Test\\": "tests/"
38 | }
39 | },
40 | "config": {
41 | "process-timeout": 0,
42 | "sort-packages": true
43 | },
44 | "scripts": {
45 | "cs:check": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php --ansi --allow-unsupported-php-version=yes",
46 | "cs:fix": "php-cs-fixer fix --config=.cs.php --ansi --verbose --allow-unsupported-php-version=yes",
47 | "sniffer:check": "phpcs --standard=phpcs.xml",
48 | "sniffer:fix": "phpcbf --standard=phpcs.xml",
49 | "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi",
50 | "start": "php -S localhost:8080 -t public/",
51 | "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always --display-warnings --display-deprecations --no-coverage",
52 | "test:all": [
53 | "@cs:check",
54 | "@sniffer:check",
55 | "@stan",
56 | "@test"
57 | ],
58 | "test:coverage": [
59 | "@putenv XDEBUG_MODE=coverage",
60 | "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always --display-warnings --display-deprecations --coverage-clover build/coverage/clover.xml --coverage-html build/coverage --coverage-text"
61 | ]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | pull_request:
6 | paths-ignore:
7 | - 'docs/**'
8 |
9 | jobs:
10 | run:
11 | runs-on: ${{ matrix.operating-system }}
12 | strategy:
13 | matrix:
14 | operating-system: [ ubuntu-latest ]
15 | php-versions: [ '8.2', '8.3', '8.4', '8.5' ]
16 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
17 | env:
18 | APP_ENV: github
19 |
20 | services:
21 | mysql:
22 | image: mysql:8.0.23
23 | env:
24 | MYSQL_ROOT_PASSWORD: root
25 | MYSQL_DATABASE: test
26 | MYSQL_ALLOW_EMPTY_PASSWORD: true
27 | ports:
28 | - 33306:3306
29 |
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v5
33 |
34 | - name: Setup PHP
35 | uses: shivammathur/setup-php@v2
36 | with:
37 | php-version: ${{ matrix.php-versions }}
38 | extensions: mbstring, pdo, pdo_mysql, intl, zip
39 |
40 | - name: Check PHP version
41 | run: php -v
42 |
43 | - name: Check Composer version
44 | run: composer -V
45 |
46 | - name: Check PHP extensions
47 | run: php -m
48 |
49 | - name: Check MySQL version
50 | run: mysql -V
51 |
52 | - name: Start MySQL
53 | run: sudo systemctl start mysql
54 |
55 | - name: Check MySQL variables
56 | run: mysql -uroot -proot -e "SHOW VARIABLES LIKE 'version%';"
57 |
58 | - name: Create database
59 | run: mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS slim_skeleton_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'
60 |
61 | - name: Validate composer.json and composer.lock
62 | run: composer validate
63 |
64 | - name: Install dependencies
65 | run: composer update --prefer-dist --no-progress --no-suggest
66 |
67 | - name: Run PHP Coding Standards Fixer
68 | run: composer cs:check
69 |
70 | - name: Run PHP CodeSniffer
71 | run: composer sniffer:check
72 |
73 | - name: Run PHPStan
74 | run: composer stan
75 |
76 | - name: Run tests
77 | if: ${{ matrix.php-versions != '8.4' }}
78 | run: composer test
79 |
80 | - name: Run tests with coverage
81 | if: ${{ matrix.php-versions == '8.4' }}
82 | run: composer test:coverage
83 |
84 | - name: Upload coverage
85 | if: ${{ matrix.php-versions == '8.4' }}
86 | uses: coverallsapp/github-action@v2
87 | with:
88 | github-token: ${{ secrets.GITHUB_TOKEN }}
89 | file: build/coverage/clover.xml
90 |
--------------------------------------------------------------------------------
/docs/action.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Action
4 | parent: The Basics
5 | ---
6 |
7 | # Single Action Controller
8 |
9 | The *Action* does only these things:
10 |
11 | * It collects input from the HTTP request (if needed).
12 | * It invokes the **Domain** with those inputs (if required) and retains the result.
13 | * It builds an HTTP response (typically with the Domain results).
14 |
15 | All other logic, including all forms of input validation, error handling, and so on,
16 | are therefore pushed out of the Action and into the [Domain](domain.md)
17 | (for domain logic concerns), or the response [Renderer](renderers.md)
18 | (for presentation logic concerns).
19 |
20 | ### Request and Response
21 |
22 | Here is a brief overview of the typical application process that involves different participants:
23 |
24 | * The **Slim router and dispatcher** receives an HTTP request and dispatches it to an **Action**.
25 |
26 | * The **Action** invokes the **[Domain](domain.md)**, collecting any required inputs to the
27 | Domain from the HTTP request.
28 |
29 | * The **Action** then invokes the **[Renderer](renderers.md)** with the data
30 | it needs to build an HTTP Response.
31 |
32 | * The **Renderer** builds an HTTP response using the data fed to it by the **Action**.
33 |
34 | * The **Action** returns the HTTP response to the **Slim response emitter** and sends
35 | the HTTP Response.
36 |
37 | 
38 |
39 | ## Example
40 |
41 | ```php
42 | myService = $myService;
60 | $this->renderer = $renderer;
61 | }
62 |
63 | public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
64 | {
65 | // 1. collect input from the HTTP request (if needed)
66 | $data = (array)$request->getParsedBody();
67 |
68 | // 2. Invokes the Domain (Application-Service)
69 | // with those inputs (if required) and retains the result
70 | $domainResult = $this->myService->doSomething($data);
71 |
72 | // 3. Build and return an HTTP response
73 | return $this->renderer->json($response, $domainResult);
74 | }
75 | }
76 | ```
77 |
--------------------------------------------------------------------------------
/tests/Traits/HttpJsonTestTrait.php:
--------------------------------------------------------------------------------
1 | createRequest($method, $uri);
31 |
32 | if ($data !== null) {
33 | $request->getBody()->write((string)json_encode($data));
34 | }
35 |
36 | return $request->withHeader('Content-Type', 'application/json');
37 | }
38 |
39 | /**
40 | * Verify that the specified array is an exact match for the returned JSON.
41 | *
42 | * @param array $expected The expected array
43 | * @param ResponseInterface $response The response
44 | *
45 | * @return void
46 | */
47 | protected function assertJsonData(array $expected, ResponseInterface $response): void
48 | {
49 | $data = $this->getJsonData($response);
50 |
51 | $this->assertSame($expected, $data);
52 | }
53 |
54 | /**
55 | * Get JSON response as array.
56 | *
57 | * @param ResponseInterface $response The response
58 | *
59 | * @return array The data
60 | */
61 | protected function getJsonData(ResponseInterface $response): array
62 | {
63 | $actual = (string)$response->getBody();
64 | $this->assertJson($actual);
65 |
66 | return (array)json_decode($actual, true);
67 | }
68 |
69 | /**
70 | * Verify JSON response.
71 | *
72 | * @param ResponseInterface $response The response
73 | *
74 | * @return void
75 | */
76 | protected function assertJsonContentType(ResponseInterface $response): void
77 | {
78 | $this->assertStringContainsString('application/json', $response->getHeaderLine('Content-Type'));
79 | }
80 |
81 | /**
82 | * Verify that the specified array is an exact match for the returned JSON.
83 | *
84 | * @param mixed $expected The expected value
85 | * @param string $path The array path
86 | * @param ResponseInterface $response The response
87 | *
88 | * @return void
89 | */
90 | protected function assertJsonValue(mixed $expected, string $path, ResponseInterface $response): void
91 | {
92 | $this->assertSame($expected, $this->getArrayValue($this->getJsonData($response), $path));
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/docs/testing.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Testing
4 | nav_order: 8
5 | ---
6 |
7 | # Testing
8 |
9 | ## Usage
10 |
11 | The test directory is: `tests/`
12 |
13 | The fixture directory is: `tests/Fixture/`
14 |
15 | To start all tests, run:
16 |
17 | ```
18 | composer test
19 | ```
20 |
21 | To start all tests with code coverage, run:
22 |
23 | ```
24 | composer test:coverage
25 | ```
26 |
27 | The code coverage output directory is: `build/coverage/`
28 |
29 | ## Unit Tests
30 |
31 | Testing units in isolation of its dependencies.
32 |
33 | Unit tests should test the behavior and not the implementation details of your classes.
34 | Make sure that unit tests are running in-memory only, because they have to be very fast.
35 |
36 | ## HTTP Tests
37 |
38 | The `AppTestTrait` provides methods for making HTTP requests to your
39 | Slim application and examining the output.
40 |
41 | ### Creating a request
42 |
43 | Creating a `GET` request:
44 |
45 | ```php
46 | $request = $this->createRequest('GET', '/users');
47 | ```
48 |
49 | Creating a `POST` request:
50 |
51 | ```php
52 | $request = $this->createRequest('POST', '/users');
53 | ```
54 |
55 | Creating a JSON `application/json` request with payload:
56 |
57 | ```php
58 | $request = $this->createJsonRequest('POST', '/users', ['name' => 'Sally']);
59 | ```
60 |
61 | Creating a form `application/x-www-form-urlencoded` request with payload:
62 |
63 | ```php
64 | $request = $this->createFormRequest('POST', '/users', ['name' => 'Sally']);
65 | ```
66 |
67 | ### Creating a query string
68 |
69 | The `withQueryParams` method can generate
70 | URL-encoded query strings. Example:
71 |
72 | ```php
73 | $params = [
74 | 'limit' => 10,
75 | ];
76 |
77 | $request = $this->createRequest('GET', '/users');
78 |
79 | // /users?limit=10
80 | $request = $request->withQueryParams($params);
81 | ```
82 |
83 | ### Add BasicAuth to the request
84 |
85 | ```php
86 | $credentials = base64_encode('username:password');
87 | $request = $request->withHeader('Authorization', sprintf('Basic %s', $credentials));
88 | ```
89 |
90 | ### Invoking a request
91 |
92 | The Slim App `handle()` method traverses the application
93 | middleware stack + actions handler and returns the Response object.
94 |
95 | ```php
96 | $response = $this->app->handle($request);
97 | ```
98 |
99 | Asserting the HTTP status code:
100 |
101 | ```php
102 | $this->assertSame(200, $response->getStatusCode());
103 | ```
104 |
105 | Asserting a JSON response:
106 |
107 | ```php
108 | $this->assertJsonContentType($response);
109 | ```
110 |
111 | Asserting JSON response data:
112 |
113 | ```php
114 | $expected = [
115 | 'user_id' => 1,
116 | 'username' => 'admin',
117 | 'first_name' => 'John',
118 | 'last_name' => 'Doe',
119 | 'email' => 'john.doe@example.com',
120 | ];
121 |
122 | $this->assertJsonData($expected, $response);
123 | ```
124 |
125 | You can find more examples in: `tests/TestCase/Action/`
126 |
127 | ## Read more
128 |
129 | * [Testing with PHPUnit](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
130 |
--------------------------------------------------------------------------------
/docs/console.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Console
4 | parent: Advanced
5 | ---
6 |
7 | # Console
8 |
9 | ## Installation
10 |
11 | You'll need to install the Symfony Console component to add command-line capabilities to your project.
12 |
13 | Use Composer to do this:
14 |
15 | ```
16 | composer require symfony/console
17 | ```
18 |
19 | ## Creating a console command
20 |
21 | Create a new command class, e.g. `src/Console/ExampleCommand.php` and copy/paste this content:
22 |
23 | ```php
24 | setName('example');
39 | $this->setDescription('A sample command');
40 | }
41 |
42 | protected function execute(InputInterface $input, OutputInterface $output): int
43 | {
44 | $output->writeln(sprintf('Hello, World!'));
45 |
46 | // The error code, 0 on success
47 | return 0;
48 | }
49 | }
50 | ```
51 |
52 | ## Register the Console Application
53 |
54 | To integrate the Console application with your application,
55 | you'll need to register it. Create a file, e.g., `bin/console.php`, and add the following code
56 |
57 | ```php
58 | getParameterOption(['--env', '-e'], 'dev');
69 |
70 | if ($env) {
71 | $_ENV['APP_ENV'] = $env;
72 | }
73 |
74 | /** @var ContainerInterface $container */
75 | $container = (new ContainerBuilder())
76 | ->addDefinitions(__DIR__ . '/../config/container.php')
77 | ->build();
78 |
79 | try {
80 | /** @var Application $application */
81 | $application = $container->get(Application::class);
82 |
83 | // Register your console commands here
84 | $application->add($container->get(ExampleCommand::class));
85 |
86 | exit($application->run());
87 | } catch (Throwable $exception) {
88 | echo $exception->getMessage();
89 | exit(1);
90 | }
91 |
92 | ```
93 |
94 | Set permissions:
95 |
96 | ```php
97 | chmod +x bin/console.php
98 | ```
99 |
100 | To start to example command, run:
101 |
102 | ``` bash
103 | php bin/console.php example
104 | ```
105 |
106 | The output:
107 |
108 | ```
109 | Hello, World!
110 | ```
111 |
112 | ## Console Commands
113 |
114 | To list all available commands, run:
115 |
116 | ``` bash
117 | php bin/console.php
118 | ```
119 |
120 | ## Read more
121 |
122 | * [Symfony Console Commands](https://symfony.com/doc/current/console.html)
123 | * [Console](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
124 |
--------------------------------------------------------------------------------
/tests/Traits/HttpTestTrait.php:
--------------------------------------------------------------------------------
1 | container instanceof ContainerInterface) {
35 | throw new RuntimeException('DI container not found');
36 | }
37 |
38 | $factory = $this->container->get(ServerRequestFactoryInterface::class);
39 |
40 | return $factory->createServerRequest($method, $uri, $serverParams);
41 | }
42 |
43 | /**
44 | * Create a form request.
45 | *
46 | * @param string $method The HTTP method
47 | * @param string|UriInterface $uri The URI
48 | * @param array|null $data The form data
49 | *
50 | * @return ServerRequestInterface The request
51 | */
52 | protected function createFormRequest(string $method, string|UriInterface $uri, ?array $data = null): ServerRequestInterface
53 | {
54 | $request = $this->createRequest($method, $uri);
55 |
56 | if ($data !== null) {
57 | $request = $request->withParsedBody($data);
58 | }
59 |
60 | return $request->withHeader('Content-Type', 'application/x-www-form-urlencoded');
61 | }
62 |
63 | /**
64 | * Create a new response.
65 | *
66 | * @param int $code HTTP status code; defaults to 200
67 | * @param string $reasonPhrase Reason phrase to associate with status code
68 | *
69 | * @throws RuntimeException
70 | *
71 | * @return ResponseInterface The response
72 | */
73 | protected function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
74 | {
75 | if (!$this->container instanceof ContainerInterface) {
76 | throw new RuntimeException('DI container not found');
77 | }
78 |
79 | $factory = $this->container->get(ResponseFactoryInterface::class);
80 |
81 | return $factory->createResponse($code, $reasonPhrase);
82 | }
83 |
84 | /**
85 | * Assert that the response body contains a string.
86 | *
87 | * @param string $expected The expected string
88 | * @param ResponseInterface $response The response
89 | *
90 | * @return void
91 | */
92 | protected function assertResponseContains(string $expected, ResponseInterface $response): void
93 | {
94 | $body = (string)$response->getBody();
95 |
96 | $this->assertStringContainsString($expected, $body);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/config/container.php:
--------------------------------------------------------------------------------
1 | fn() => require __DIR__ . '/settings.php',
24 |
25 | App::class => function (ContainerInterface $container) {
26 | $app = AppFactory::createFromContainer($container);
27 |
28 | // Register routes
29 | (require __DIR__ . '/routes.php')($app);
30 |
31 | // Register middleware
32 | (require __DIR__ . '/middleware.php')($app);
33 |
34 | return $app;
35 | },
36 |
37 | // HTTP factories
38 | ResponseFactoryInterface::class => function (ContainerInterface $container) {
39 | return $container->get(Psr17Factory::class);
40 | },
41 |
42 | ServerRequestFactoryInterface::class => function (ContainerInterface $container) {
43 | return $container->get(Psr17Factory::class);
44 | },
45 |
46 | StreamFactoryInterface::class => function (ContainerInterface $container) {
47 | return $container->get(Psr17Factory::class);
48 | },
49 |
50 | UploadedFileFactoryInterface::class => function (ContainerInterface $container) {
51 | return $container->get(Psr17Factory::class);
52 | },
53 |
54 | UriFactoryInterface::class => function (ContainerInterface $container) {
55 | return $container->get(Psr17Factory::class);
56 | },
57 |
58 | // The Slim RouterParser
59 | RouteParserInterface::class => function (ContainerInterface $container) {
60 | return $container->get(App::class)->getRouteCollector()->getRouteParser();
61 | },
62 |
63 | BasePathMiddleware::class => function (ContainerInterface $container) {
64 | return new BasePathMiddleware($container->get(App::class));
65 | },
66 |
67 | LoggerInterface::class => function (ContainerInterface $container) {
68 | $settings = $container->get('settings')['logger'];
69 | $logger = new Logger('app');
70 |
71 | $filename = sprintf('%s/app.log', $settings['path']);
72 | $level = $settings['level'];
73 | $rotatingFileHandler = new RotatingFileHandler($filename, 0, $level, true, 0777);
74 | $rotatingFileHandler->setFormatter(new LineFormatter(null, null, false, true));
75 | $logger->pushHandler($rotatingFileHandler);
76 |
77 | return $logger;
78 | },
79 |
80 | ExceptionMiddleware::class => function (ContainerInterface $container) {
81 | $settings = $container->get('settings')['error'];
82 |
83 | return new ExceptionMiddleware(
84 | $container->get(ResponseFactoryInterface::class),
85 | $container->get(JsonRenderer::class),
86 | $container->get(LoggerInterface::class),
87 | (bool)$settings['display_error_details'],
88 | );
89 | },
90 | ];
91 |
--------------------------------------------------------------------------------
/.cs.php:
--------------------------------------------------------------------------------
1 | setUsingCache(false)
7 | ->setRiskyAllowed(true)
8 | ->setRules(
9 | [
10 | '@PSR1' => true,
11 | '@PSR2' => true,
12 | // custom rules
13 | 'psr_autoloading' => true,
14 | 'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], // psr-5
15 | 'phpdoc_to_comment' => false,
16 | 'no_superfluous_phpdoc_tags' => false,
17 | 'array_indentation' => true,
18 | 'array_syntax' => ['syntax' => 'short'],
19 | 'cast_spaces' => ['space' => 'none'],
20 | 'concat_space' => ['spacing' => 'one'],
21 | 'compact_nullable_type_declaration' => true,
22 | 'declare_equal_normalize' => ['space' => 'single'],
23 | 'general_phpdoc_annotation_remove' => [
24 | 'annotations' => [
25 | 'author',
26 | 'package',
27 | ],
28 | ],
29 | 'increment_style' => ['style' => 'post'],
30 | 'list_syntax' => ['syntax' => 'short'],
31 | 'echo_tag_syntax' => ['format' => 'long'],
32 | 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
33 | 'phpdoc_align' => false,
34 | 'phpdoc_no_empty_return' => false,
35 | 'phpdoc_order' => true, // psr-5
36 | 'phpdoc_no_useless_inheritdoc' => false,
37 | 'protected_to_private' => false,
38 | 'yoda_style' => [
39 | 'equal' => false,
40 | 'identical' => false,
41 | 'less_and_greater' => false
42 | ],
43 | 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
44 | 'ordered_imports' => [
45 | 'sort_algorithm' => 'alpha',
46 | 'imports_order' => ['class', 'const', 'function'],
47 | ],
48 | 'single_line_throw' => false,
49 | 'declare_strict_types' => false,
50 | 'blank_line_between_import_groups' => true,
51 | 'fully_qualified_strict_types' => true,
52 | 'no_null_property_initialization' => false,
53 | 'nullable_type_declaration_for_default_null_value' => false,
54 | 'operator_linebreak' => [
55 | 'only_booleans' => true,
56 | 'position' => 'beginning',
57 | ],
58 | 'global_namespace_import' => [
59 | 'import_classes' => true,
60 | 'import_constants' => null,
61 | 'import_functions' => null
62 | ],
63 | 'class_definition' => [
64 | 'space_before_parenthesis' => true,
65 | ],
66 | 'trailing_comma_in_multiline' => [
67 | 'after_heredoc' => true,
68 | 'elements' => ['array_destructuring', 'arrays', 'match']
69 | ],
70 | 'function_declaration' => [
71 | 'closure_fn_spacing' => 'none',
72 | ]
73 | ]
74 | )
75 | ->setFinder(
76 | PhpCsFixer\Finder::create()
77 | ->in(__DIR__ . '/src')
78 | ->in(__DIR__ . '/tests')
79 | ->in(__DIR__ . '/config')
80 | ->in(__DIR__ . '/public')
81 | ->name('*.php')
82 | ->ignoreDotFiles(true)
83 | ->ignoreVCS(true)
84 | );
85 |
--------------------------------------------------------------------------------
/docs/domain.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Domain
4 | parent: The Basics
5 | ---
6 |
7 | # Domain
8 |
9 | The Domain layer is the core of the application.
10 |
11 | ## Services
12 |
13 | Here is the right place for complex **business logic** e.g. calculation, validation, transaction handling, file creation etc.
14 | Business logic is a step-up on complexity over CRUD (Create, Read, Update and Delete) operations.
15 | A service can be called directly from the action handler, a service, the console and from a test.
16 |
17 | ## Domain vs. Infrastructure
18 |
19 | The infrastructure (layer) does not belong to the core application
20 | because it acts like an external consumer to talk to your system,
21 | for example the database, sending emails etc.
22 |
23 | An Infrastructure service can be:
24 |
25 | * Implementations for boundary objects, e.g. the repository classes (communication with the database)
26 | * Web controllers (actions), console, etc.
27 | * Framework-specific code
28 |
29 | By separating domain from infrastructure code you automatically **increase testability**
30 | because you can replace the implementation by changing the adapter without affecting
31 | the interface users.
32 |
33 | Within the Domain layer you have multiple other types of classes, for example:
34 |
35 | * Services with the business logic, aka. Use cases
36 | * Value Objects, DTOs, Entities, aka. Model
37 | * The repository (interfaces), for boundary objects to the infrastructure.
38 |
39 | ## Keep it clean
40 |
41 | Most people may think that this pattern is not suitable because it results in too many files.
42 | That this will result in more files is true, however these files are very small and focus on
43 | exactly one specific task. You get very specific classes with only one clearly defined responsibility
44 | (see SRP of SOLID). So you should not worry too much about too many files, instead you should worry
45 | about too few and big files (fat controllers) with too many responsibilities.
46 |
47 | ## Read more
48 |
49 | This architecture was inspired by the following resources and books:
50 |
51 | * [The Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
52 | * [The Onion Architecture](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/)
53 | * [Action Domain Responder](https://github.com/pmjones/adr)
54 | * [Domain-Driven Design](https://amzn.to/3cNq2jV) (The blue book)
55 | * [Implementing Domain-Driven Design](https://amzn.to/2zrGrMm) (The red book)
56 | * [Hexagonal Architecture](https://fideloper.com/hexagonal-architecture)
57 | * [Alistair in the Hexagone](https://www.youtube.com/watch?v=th4AgBcrEHA)
58 | * [Hexagonal Architecture demystified](https://madewithlove.be/hexagonal-architecture-demystified/)
59 | * [Functional architecture](https://www.youtube.com/watch?v=US8QG9I1XW0&t=33m14s) (Video)
60 | * [Object Design Style Guide](https://www.manning.com/books/object-design-style-guide?a_aid=object-design&a_bid=4e089b42)
61 | * [Advanced Web Application Architecture](https://leanpub.com/web-application-architecture/) (Book)
62 | * [Advanced Web Application Architecture](https://www.slideshare.net/matthiasnoback/advanced-web-application-architecture-full-stack-europe-2019) (Slides)
63 | * [The Beauty of Single Action Controllers](https://driesvints.com/blog/the-beauty-of-single-action-controllers)
64 | * [On structuring PHP projects](https://www.nikolaposa.in.rs/blog/2017/01/16/on-structuring-php-projects/)
65 | * [Standard PHP package skeleton](https://github.com/php-pds/skeleton)
66 | * [Services vs Objects](https://dontpaniclabs.com/blog/post/2017/10/12/services-vs-objects)
67 | * [Stop returning arrays, use objects instead](https://www.brandonsavage.net/stop-returning-arrays-use-objects-instead/)
68 | * [Data Transfer Objects - What Are DTOs](https://www.youtube.com/watch?v=35QmeoPLPOQ)
69 | * [SOLID](https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design)
70 |
--------------------------------------------------------------------------------
/src/Middleware/ExceptionMiddleware.php:
--------------------------------------------------------------------------------
1 | responseFactory = $responseFactory;
32 | $this->renderer = $jsonRenderer;
33 | $this->displayErrorDetails = $displayErrorDetails;
34 | $this->logger = $logger;
35 | }
36 |
37 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
38 | {
39 | try {
40 | return $handler->handle($request);
41 | } catch (Throwable $exception) {
42 | return $this->render($exception, $request);
43 | }
44 | }
45 |
46 | private function render(
47 | Throwable $exception,
48 | ServerRequestInterface $request,
49 | ): ResponseInterface {
50 | $httpStatusCode = $this->getHttpStatusCode($exception);
51 | $response = $this->responseFactory->createResponse($httpStatusCode);
52 |
53 | // Log error
54 | if (isset($this->logger)) {
55 | $this->logger->error(
56 | sprintf(
57 | '%s;Code %s;File: %s;Line: %s',
58 | $exception->getMessage(),
59 | $exception->getCode(),
60 | $exception->getFile(),
61 | $exception->getLine()
62 | ),
63 | $exception->getTrace()
64 | );
65 | }
66 |
67 | // Content negotiation
68 | if (str_contains($request->getHeaderLine('Accept'), 'application/json')) {
69 | $response = $response->withAddedHeader('Content-Type', 'application/json');
70 |
71 | // JSON
72 | return $this->renderJson($exception, $response);
73 | }
74 |
75 | // HTML
76 | return $this->renderHtml($response, $exception);
77 | }
78 |
79 | public function renderJson(Throwable $exception, ResponseInterface $response): ResponseInterface
80 | {
81 | $data = [
82 | 'error' => [
83 | 'message' => $exception->getMessage(),
84 | ],
85 | ];
86 |
87 | return $this->renderer->json($response, $data);
88 | }
89 |
90 | public function renderHtml(ResponseInterface $response, Throwable $exception): ResponseInterface
91 | {
92 | $response = $response->withHeader('Content-Type', 'text/html');
93 |
94 | $message = sprintf(
95 | "\n
Error %s (%s)\n
Message: %s\n
",
96 | $this->html((string)$response->getStatusCode()),
97 | $this->html($response->getReasonPhrase()),
98 | $this->html($exception->getMessage()),
99 | );
100 |
101 | if ($this->displayErrorDetails) {
102 | $message .= sprintf(
103 | 'File: %s, Line: %s',
104 | $this->html($exception->getFile()),
105 | $this->html((string)$exception->getLine())
106 | );
107 | }
108 |
109 | $response->getBody()->write($message);
110 |
111 | return $response;
112 | }
113 |
114 | private function getHttpStatusCode(Throwable $exception): int
115 | {
116 | $statusCode = StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR;
117 |
118 | if ($exception instanceof HttpException) {
119 | $statusCode = $exception->getCode();
120 | }
121 |
122 | if ($exception instanceof DomainException || $exception instanceof InvalidArgumentException) {
123 | $statusCode = StatusCodeInterface::STATUS_BAD_REQUEST;
124 | }
125 |
126 | return $statusCode;
127 | }
128 |
129 | private function html(string $text): string
130 | {
131 | return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/docs/security.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Security
4 | nav_order: 5
5 | ---
6 |
7 | # Security
8 |
9 | ## Basic Authentication
10 |
11 | [BasicAuth](https://en.wikipedia.org/wiki/Basic_access_authentication)
12 | is an authentication scheme built into the HTTP protocol.
13 | As long as the client transmits its data over **HTTPS**,
14 | it's a secure **authentication** mechanism.
15 |
16 | ```
17 | Authorization: Basic YXBpLXVzZXI6c2VjcmV0
18 | ```
19 |
20 | The [tuupola/slim-basic-auth](https://github.com/tuupola/slim-basic-auth) package implements HTTP Basic Authentication.
21 |
22 | * [Basic Authentication](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
23 |
24 | ## OAuth 2.0
25 |
26 | For **authorization**, you could consider to use [OAuth 2.0](https://oauth.net/2/) in combination with a signed [JSON Web Token](https://oauth.net/2/jwt/).
27 |
28 | The JWTs can be used as OAuth 2.0 [Bearer-Tokens](https://oauth.net/2/bearer-tokens/) to encode all relevant parts of an access token into the access token itself instead of having to store them in a database.
29 |
30 | Please note: [OAuth 2.0 is not an authentication protocol](https://oauth.net/articles/authentication/).
31 |
32 | Clients may use the **HTTP Basic authentication** scheme, as defined in [RFC2617](https://tools.ietf.org/html/rfc2617),
33 | to authenticate with the server.
34 |
35 | After successful authentication, the client sends its token within the `Authorization` request header:
36 |
37 | ```
38 | Authorization: Bearer RsT5OjbzRn430zqMLgV3Ia
39 | ```
40 |
41 | The [lcobucci/jwt](https://github.com/lcobucci/jwt) and
42 | [firebase/php-jwt](https://github.com/firebase/php-jwt) packages
43 | are a very good tools to work with JSON Web Tokens.
44 |
45 | * [Firebase JWT](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
46 | * [Mezzio OAuth2 Server](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
47 | * [JSON Web Token (JWT)](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
48 | * [OAuth Libraries for PHP](https://oauth.net/code/php/)
49 | * [Auth0 PHP SDK](https://auth0.com/docs/libraries/auth0-php)
50 | * [Stop using JWT for sessions](http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/)
51 | * [Swagger - OAuth 2.0](https://swagger.io/docs/specification/authentication/oauth2/)
52 |
53 | ## Cross-site Request Forgery (CSRF) Protection
54 |
55 | Cross-site request forgery (CSRF) is a web security vulnerability
56 | that tricks a victim's browser into performing unwanted
57 | actions on a web application where the user is authenticated,
58 | without their knowledge or consent.
59 |
60 | * [CSRF](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
61 | * [Slim Framework CSRF Protection](https://github.com/slimphp/Slim-Csrf)
62 |
63 | **SameSite Cookies** can be used for security purposes
64 | to prevent CSRF attacks,
65 | by controlling whether cookies are sent along with cross-site requests,
66 | thereby limiting the risk of third-party interference with
67 | the intended functioning of web applications.
68 |
69 | * [SameSite Cookies](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
70 | * [selective/samesite-cookie](https://github.com/selective-php/samesite-cookie)
71 |
72 | ## Cross-Origin Resource Sharing (CORS)
73 |
74 | Cross-Origin Resource Sharing (CORS) is a security feature
75 | implemented by web browsers that controls how web pages
76 | in one domain can request resources from another domain,
77 | aiming to safely enable interactions between different origins.
78 |
79 | * [Setting up CORS](https://www.slimframework.com/docs/v4/cookbook/enable-cors.html)
80 | * [CORS](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
81 | * [middlewares/cors](https://github.com/middlewares/cors)
82 |
83 | ## Cross Site Scripting (XSS) Prevention
84 |
85 | Cross-site Scripting (XSS) is a client-side code injection attack.
86 | The attacker aims to execute malicious scripts in a web browser of the
87 | victim by including malicious code in a legitimate web page or web application.
88 |
89 | To prevent XSS you can use an Auto-Escaping Template System such as Twig
90 | or by using libraries that are specifically designed to sanitize HTML input:
91 |
92 | * [laminas/laminas-escaper](https://github.com/laminas/laminas-escaper)
93 | * [Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
94 | * [Cross-site Scripting (XSS)](https://www.acunetix.com/websitesecurity/cross-site-scripting/)
95 | * [XSS - Cross-site Scripting Protection](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
96 |
97 | ## More Resources
98 |
99 | * [Mezzio OAuth2 Server](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
100 | * [PHP Middleware](https://github.com/php-middleware)
101 | * [middlewares/firewall](https://github.com/middlewares/firewall)
102 | * [PSR-15 HTTP Middlewares](https://github.com/middlewares)
103 | * [Shieldon - Web Application Firewall](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
104 | * [Spam Protection](https://ko-fi.com/s/5f182b4b22) (Slim 4 - eBook Vol. 1)
105 | * [Symfony Rate Limiter](https://ko-fi.com/s/e592c10b5f) (Slim 4 - eBook Vol. 2)
106 | * [XSS - Cross-site Scripting Protection](https://ko-fi.com/s/3698cf30f3) (Slim 4 - eBook Vol. 3)
107 |
--------------------------------------------------------------------------------