In the beginning of the year 2022 I started a project called boulderampel. It`s a web application to overview the occupancy in climbing gyms and is based on the PHP Symfony Framework. Unfortunatelly, its not online anymore because of several reasons but nevertheless, I’m going to introduce it.

The Idea

Because of the Corona Virus munich climbing gyms started to publish the current occupancy on their websites. The purpose was to inform their guests and to help them avoiding crowded hours.

My idea was to have one website where the user overviews the current occupancy of all climbing gyms in his town so he can decide quite spontaneously where to go. In the best case this would help to distribute the load more equally.

The application enables climbers to overview the current situation and to decide wether they are going to visit or to go somewhere else. Sometimes training can be hard during crowded hours and lately the risk to become infected with the Corona virus increases in closed and crowded spaces.

Screenshot Boulderampel

The Engine

The heart of the application is a GymFetcher class where all local fetchers get injected. These classes implement the GymFetcherInterface and are autowired by Symfonies tagged iterator system.

Also I implemented a Cache to not overload the websites with requests from my application. New occupancy information gets parsed if the last request is older than 5 minutes.

class GymFetcher
    private array $localFetchers = [];

    public function __construct(iterable $localGymFetchers, private LoggerInterface $logger, private CacheInterface $cache)
        foreach ($localGymFetchers as $f) {
            $this->localFetchers[] = $f;

    public function fetchCapacity(string $gymName, bool $cacheEnabled = true): ?Capacity
        foreach ($this->localFetchers as $fetcher) {
            if ($fetcher->supports($gymName)) {
                try {
                    if ($cacheEnabled) {
                        return $this->cache->get('capacity_'.$gymName, function (ItemInterface $item) use ($fetcher) {

                            return $fetcher->fetchCapacity();

                    return $fetcher->fetchCapacity();
                } catch (\Exception $e) {
                    $this->logger->error(\sprintf('Fetch Error in %s: %s', $gymName, $e->getMessage()));

                    return null;

        throw new \LogicException(\sprintf('Error in class GymFetcher: Could not find fetcher by gymName: %s', $gymName));


Right now the Boulderampel is hosted on the Hetzner Cloud that runs on a Linux Ubuntu 18.04 image. Php-fpm and a Caddy Webserver are installed manually. It is the first time I use Caddy and I am really happy with the decision to not use NGINX. As a web developer my focus right now lies more on web technlogoies and developing so I am not that keen in DevOps and Linux stuff.

With caddy the basic configuration of the webserver felt quite easy and secure. I like the implementation with a Caddyfile and the CLI that makes it easy to know how Caddy is configured and running. I mean how cool is it that you send a get request to the api with

curl localhost:2019/config/ | jq

and you receive a response with information on the current config.

To ensure Continuous Integration I added a Makefile that copies all the neccesary files via rsync, builds the yarn packages, uploads the zipped directory to the server and executes a shell file that installs the composer packages, restarts caddy and clears the cache.