This file will guide you through the process of getting to project up and running, in case you want to provide coding contributions.
You will also see how to ensure the code fulfills the expected code checks, and how to create a pull request.
The project provides all its dependencies as docker containers through a docker compose
configuration.
Because of this, the only actual dependencies are docker and docker compose.
The first thing you need to do is fork the repository, and clone it in your local machine.
Then you will have to follow these steps:
-
Copy the
config/params/shlink_dev_env.php.dist
in the same directory, but removing the.dist
extension:cp config/params/shlink_dev_env.php.dist config/params/shlink_dev_env.php
The
shlink_dev_env.php
file is gitignored, so you can customize it as you want. For example, by adding your own GeoLite license key. -
Start-up the project by running
docker compose up
.The first time this command is run, it will create several containers that are used during development, so it may take some time.
It will also create some empty databases and install the project dependencies with composer.
-
Run
./indocker bin/cli db:create
to create the initial database. -
Run
./indocker bin/cli db:migrate
to get database migrations up to date. -
Run
./indocker bin/cli api-key:generate
to get your first API key generated.
Once you finish this, you will have the project exposed in ports 8800
through RoadRunner and 8000
through nginx+php-fpm.
Note: The
indocker
shell script is a helper tool used to run commands inside the main docker container.
This project is structured as a modular application, using laminas/laminas-config-aggregator to merge the configuration provided by every module.
All modules are inside the module
folder, and each one has its own src
, test
and config
folders, with the source code, tests and configuration. They also have their own ConfigProvider
class, which is consumed by the config aggregator.
This is a simplified version of the project structure:
shlink
├── bin
│ ├── cli
│ └── [...]
├── config
│ ├── autoload
│ ├── params
│ ├── config.php
│ ├── container.php
│ └── [...]
├── data
│ ├── cache
│ ├── locks
│ ├── log
│ └── proxies
├── docs
│ ├── adr
│ ├── async-api
│ └── swagger
├── module
│ ├── CLI
│ ├── Core
│ └── Rest
├── public
│ └── [...]
├── composer.json
└── README.md
The purposes of every folder are:
bin
: It contains the CLI tools. Thecli
one is the main entry point to run Shlink from the command line.config
: Contains application-wide configurations, which are later merged with the ones provided by every module.data
: Common git-ignored assets, like logs, caches, lock files, GeoLite DB files, etc. It's the only location where Shlink may need to write at runtime.docs
: Any project documentation is stored here, like API spec definitions or architectural decision records.module
: Contains a sub-folder for every module in the project. Modules contain the source code, tests and configurations for every context in the project.public
: Few assets (likefavicon.ico
orrobots.txt
) and the web entry point are stored here. This web entry point is not used when serving the app with RoadRunner.
In order to ensure stability and no regressions are introduced while developing new features, this project has different types of tests.
-
Unit tests: These are the simplest to run, and usually test individual pieces of code, replacing any external dependency by mocks.
The code coverage of unit tests is pretty high, and only components which work closer to the database, like entity repositories, are excluded because of their nature.
-
Database tests: These are integration tests that run against a real database, and only cover components which work closer to the database.
Its purpose is to verify all the database queries behave as expected and return what's expected.
The project provides some tooling to run them against any of the supported database engines.
-
API tests: These are E2E tests that spin up an instance of the app with RoadRunner, and test it from the outside by interacting with the REST API.
These are the best tests to catch regressions, and to verify everything behaves as expected.
They use Postgres as the database engine, and include some fixtures that ensure the same data exists at the beginning of the execution.
Since the app instance is run on a process different from the one running the tests, when a test fails it might not be obvious why. To help debugging that, the app will dump all its logs inside
data/log/api-tests
, where you will find theshlink.log
andaccess.log
files. -
CLI tests: These are E2E tests too, but they test console commands instead of REST endpoints.
They use Maria DB as the database engine, and include the same fixtures as the API tests, that ensure the same data exists at the beginning of the execution.
Depending on the kind of contribution, maybe not all kinds of tests are needed, but the more you provide, the better.
-
Run
./indocker composer cs
to check coding styles are fulfilled. -
Run
./indocker composer cs:fix
to fix coding styles (some may not be fixable from the CLI) -
Run
./indocker composer stan
to statically analyze the code with phpstan. This tool is the closest to "compile" PHP and verify everything would work as expected. -
Run
./indocker composer test:unit
to run the unit tests. -
Run
./indocker composer test:db
to run the database integration tests.This command runs the same test suite against all supported database engines in parallel. If you just want to run one of them, you can add one of
:sqlite
,:mysql
,:maria
,:postgres
,:mssql
at the end of the command.For example,
test:db:postgres
. -
Run
./indocker composer test:api
to run API E2E tests. For these, the Postgres database engine is used. -
Run
./indocker composer test:cli
to run CLI E2E tests. For these, the Maria DB database engine is used. -
Run
./indocker composer ci
to run all previous commands together, parallelizing non-conflicting tasks as much as possible.
The project provides a Swagger UI container for dev envs, which can be accessed in http://localhost:8005.
It will automatically load the contents of docs/swagger
, so you can make any updates and they will get reflected.
Important!: Before starting to work on a pull request, make sure you always open an issue first.
This is important because any contribution needs to be discussed first. Maybe there's someone else already working on something similar, or there are other considerations to have in mind.
Once everything is clear, to provide a pull request to this project, you should always start by creating a new branch, where you will make all desired changes.
The base branch should always be develop
, and the target branch for the pull request should also be develop
.
Before your branch can be merged, all the checks described in Running code checks have to be passing. You can verify that manually by running ./indocker composer ci
, or wait for the build to be run automatically after the pull request is created.
The project includes logs for some architectural decisions, using the adr proposal.
If you are curious or want to understand why something has been built in some specific way, take a look at them.