<![CDATA[Martin Hujer blog]]> 2021-04-05T08:24:37+00:00 https://blog.martinhujer.cz/ Sculpin <![CDATA[How to add a non-nullable column without downtime]]> 2021-04-05T00:00:00+00:00 https://blog.martinhujer.cz/how-to-add-non-nullable-column-without-downtime/ Database migrations are hard for many reasons. In this article I explain how to add a non-nullable column to an application when you are using Zero-Downtime deployments.

If you are using maintenance windows for releasing new versions, please read my article on why Deploying only during maintenance windows is an antipattern.

What is the problem?

Even if your deployment process is automated, there may be a delay between when the database migration runs, and the new version of the application is deployed. It may be a minute or two if you have just one application server but longer if you have a fleet of servers, and you are deploying gradually.

When you add a new non-nullable column to a table, inserts on this table will fail, until the new version of the application is deployed to all nodes, because it will try to insert NULL into a non-nullable column.

You can think "Whatever...my deploy is fast. Nobody will notice.", but please keep in mind that the deployment may fail at any phase - it may fail after the database migration was run but before the new code is deployed. Your production environment will be down until you can either roll back the database change or fix your deployment (npmjs down?).

Consider this example: you have simple todo-list app, and you want to allow users to decide whether the task is simple or complex. So in the new application version you add a type column and start inserting either simple or complex in that column.


What happens? After you run the migration and before the application deployment will finish, no tasks can be added to the application because of the error:

SQL Error (1364): Field 'type' doesn't have a default value

What can be done about it?

It's all about backwards compatibility

The solution is simple - the database changes must be backwards compatible.

The changes performed on the database must be also compatible with the currently deployed application version.

Step 1: add a nullable column

Instead of adding a non-nullable column, you add a nullable column first:


Even after the change, the database is still compatible with the current application version.

Step 2: fill the column in the application

If you are using an ORM or just prefer to keep all the logic in the application (because of testability), now it's time to update the entity to always set type column to value simple. This ensures that all new records will have a proper value set.

I usually do this in the same commit as Step 1 - I add the database field and update the entity to set a new field value. When deploying, the database is updated first, which is OK as the change is backwards compatible, and the code is deployed afterwards.

Note: We are not adding any complex logic yet!

Step 3: make the column non-nullable

Before the column can be made non-nullable, we must fill the values for the existing records. As we have only one task type now, we can do it like this:

UPDATE tasks SET `type` = 'simple' WHERE `type` IS NULL;

At this point, there shouldn't be any NULL values left, so we can change the column to NOT NULL:


Step 4: Start using the column

Finally, you can change the application to allow choosing task type and deploy this version without any additional database changes.

If something goes wrong with the new application changes, you can easily revert to the previous application version without any changes to database structure.

How to remove the non-nullable column?

Removing column is quite similar but backwards:

  1. Make the column nullable in the database.
  2. Stop using the column in the application and deploy it.
  3. Remove the column from the database.

How to rename a column in a database?

Renaming a column with zero downtime is hard (you need to add a new column and remove the old afterwards), so do it only when really necessary.

  1. Add a new nullable column to database.
  2. Change the application, so it fills both columns with the same value (and deploy it).
  3. Copy the values from the old column to the new column using SQL.
  4. Make the new column non-nullable.
  5. Change the application, so it always reads the new value (and deploy it).
  6. Make the old column nullable.
  7. Stop filling in the old column in the application (and deploy it).
  8. Remove the old column from the database.


Adding a new non-nullable column to the database without downtime is more work that just running an ALTER TABLE, but it is worth it because it allows you to deploy changes anytime without waiting for a maintenance window.

<![CDATA[Deploying only during maintenance windows is an antipattern]]> 2021-04-04T00:00:00+00:00 https://blog.martinhujer.cz/maintenance-window-antipattern/ In this article I argue that deploying the new web application versions only during a maintenance window is an antipattern, and you should strive to have Zero-Downtime deployments.

It has long been a common practice to take a website or a web application down regularly and deploy a new version during that downtime. It is easy: You just schedule a 3-hour maintenance window between midnight and 3AM, make the application return "We are performing maintenance" page with an HTTP 503 code and do the upgrade or whatever during that time. And try to get some sleep afterwards.

Apart from the obvious advantages - nobody is messing with the application when you are releasing a new version, there are many reasons why it is not such a good idea:

  1. There is no natural maintenance window
  2. It's better to sleep at night
  3. The computers never sleep
  4. Business won't wait for your maintenance window
  5. Small changes are safer

1. There is no natural maintenance window

There are natural maintenance windows only for a small part of the web applications - e.g., when you are working on a B2B application which is used only by business only during their business hours, and they are all in one time-zone.

When you are making a B2C application - ecommerce website or something like that, there is always going to be someone using your application. There are people working shifts, so they may be bored during a night shift and want to browse your website.

Or you go global and suddenly there is always someone's afternoon somewhere.

If you schedule maintenance window to the time when someone needs to use the application, you will annoy your existing users and lose any potential new customers who will visit the application during downtime.

And there are some questions which needs answering: What happens when someone filled a form a minute before the maintenance window and tries to submit it during the maintenance window? Will it fail? How? What about the data they entered?

Note: At least don't forget to send proper HTTP 503 status code or your temporary page may be indexed by search engines.

2. It's better to sleep at night

I'm a morning person (usually getting up at 7 AM, back at bed at 11 PM) so staying awake beyond midnight is really hard, and I guess I won't be very good at debugging a thorny production issue at 3 AM.

The more important is that messing up your sleep rhythm is bad for your health. If I stayed up until 3 AM, I would be useless the next day even after sleeping in. And my sleep rhythm will be disturbed for a few days at least. So, it is not worth it health- and productivity-wise.

3. The computers never sleep

The computers never sleep, so even if there is a natural maintenance window, there may still be API calls coming to your application, webhooks may be called, etc. And depending on the 3rd party error handling, those may or may not be retried later.

4. Business won't wait for your maintenance window

If you can deploy new features only once every two weeks, the businesspeople in your company won't be happy having to wait up two weeks for a small change to go live. If they worked somewhere else where Zero-Downtime deployments where common, they would see you as incompetent.

Don't forget that if there is a competition moving faster than you, you risk losing customers to them.

5. Small changes are safer

Imagine there are 20 different changes in the release, and something breaks, or the load suddenly increases after the deployment. How will you debug it? It can be any of those 20 changes. If you did deploy only one change it would be both easier to debug the problem or rollback the change completely.

How do you rollback a big release with many database changes? And what if you discover the issue the day after the release? You cannot just revert to the older backup. You are out of luck.

So, what should we do instead?

  • Automate the deployment process
  • Deploy each change separately
  • Be careful about database migrations (I will write a separate post about database migrations without downtime)
  • Even if it may seem that the change warrants a maintenance window, try to think hard how to decompose it into several steps which may be deployed gradually without downtime.


When you consider the downsides discussed above, it does not seem that bad to spend some time on making your deployment process Zero-Downtime, right?

Please note that there are rare occasions when it is dramatically easier to schedule the maintenance window instead of doing it gradually (e.g., migrating the database to a new server). But those should be rare once-a-year special occasions, not part of your regular deployment process.

<![CDATA[Clever way to sort PHP arrays by multiple values]]> 2019-11-27T00:00:00+00:00 https://blog.martinhujer.cz/clever-way-to-sort-php-arrays-by-multiple-values/ In the post I will describe a clever trick which allows you to easily sort PHP array or collection by multiple values.

Let's start from the beginning. Sorting using usort and uasort functions works by providing a callback for comparing two values in the array. From the docs:

The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.

Basic sorting

The simplest example may look like this (when having an array of Product instances):

// order products by: price ASC
usort($products, function (Product $a, Product $b): int {
    return $a->getPrice() <=> $b->getPrice();

You can notice that I'm using a spaceship operator <=> which was added in PHP 7.0. It compares two expressions, $a and $b, and returns -1, 0 or 1 when $a is respectively less than, equal to, or greater than $b.

The example above can be simplified more using the arrow functions from PHP 7.4 (although I wouldn't say that it is more readable):

usort($products, fn (Product $a, Product $b): int => $a->getPrice() <=> $b->getPrice());

To sort the values in the descending order, just swap $a and $b expressions in the callback:

// order products by: price DESC
usort($products, function (Product $a, Product $b): int {
    return $b->getPrice() <=> $a->getPrice();

Sorting by two properties

If you want to sort the products by two fields, price ASC and products in stock first, it gets tricky. First you need to check whether the prices are equal. If they are you compare the inStock flag to have the available products first. Otherwise just compare the prices.

// order products by: price ASC, inStock DESC
usort($products, function (Product $a, Product $b): int {
    if ($a->getPrice() === $b->getPrice()) {
        return $b->isInStock() <=> $a->isInStock();
    return $a->getPrice() <=> $b->getPrice();

Sorting by multiple properties

It will get much more complex when you want to sort the array by three or four properties:

// order products by: price ASC, inStock DESC, isRecommended DESC, name ASC
usort($products, function (Product $a, Product $b): int {
    if ($a->getPrice() === $b->getPrice()) {
        if ($a->isInStock() === $b->isInStock()) {
            if ($a->isRecommended() == $b->isRecommended()) {
                return $a->getName() <=> $b->getName();
            return $b->isRecommended() <=> $a->isRecommended();
        return $b->isInStock() <=> $a->isInStock();
    return $a->getPrice() <=> $b->getPrice();

You have to carefully craft the conditions and don't miss the places where you are comparing $b with $a instead of $a with $b to sort them in descending order.

This example is quite close to what I needed when working on OutdoorVisit.com tickets list in activity detail. I couldn't do it in the database because there was some preprocessing (non-database filtering etc.) required.

I didn't want to have this complex sorting logic written as above, so I came up with the following solution.


// order products by: price ASC, inStock DESC, isRecommended DESC, name ASC
usort($products, function (Product $a, Product $b): int {
        ($a->getPrice() <=> $b->getPrice()) * 1000 + // price ASC
        ($b->isInStock() <=> $a->isInStock()) * 100 + // inStock DESC
        ($b->isRecommended() <=> $a->isRecommended()) * 10 + // isRecommended DESC
        ($a->getName() <=> $b->getName()); // name ASC

I compare all attributes that impact the sorting in the same expression. I also add weight to each comparison to prioritize them.

The trick is that the return value from the callback can be any positive or negative integer, not just -1 or 0 or 1. It allows me to sum the separate comparisons together and return it as a result.

It can be further simplified using arrow function from PHP 7.4:

// order products by: price ASC, inStock DESC, isRecommended DESC, name ASC
usort($products, fn (Product $a, Product $b): int =>
    ($a->getPrice() <=> $b->getPrice()) * 1000 + // price ASC
    ($b->isInStock() <=> $a->isInStock()) * 100 + // inStock DESC
    ($b->isRecommended() <=> $a->isRecommended()) * 10 + // isRecommended DESC
    ($a->getName() <=> $b->getName()) // name ASC

Solution (from František Maša)

František Maša suggested even better solution in the comments. Thanks!

usort($products, fn (Product $a, Product $b): int =>
    [$a->getPrice(), $b->isInStock(), $b->isRecommended(), $a->getName()]
    [$b->getPrice(), $a->isInStock(), $a->isRecommended(), $b->getName()]


Let me know in the comments if you find this trick useful.

Or do you have a better way of doing this?

<![CDATA[How to configure PHPStan for Symfony applications]]> 2019-10-23T00:00:00+00:00 https://blog.martinhujer.cz/how-to-configure-phpstan-for-symfony-applications/ PHPStan is a fantastic tool for a static analysis of PHP code. It reads the code and PHPDoc and will try to detect potential issues such as:

  • undefined variables
  • incorrect types passed throughout the codebase
  • use of non-existent methods and attributes
  • passing of incorrect number of parameters to a method
  • use of possible null-pointers
  • lot more...

Apart from being able to analyse regular PHP code, PHPStan can understand even some framework-specific magic using custom-made extensions.

But let's start from the beginning...

PHPStan installation

You can install PHPStan either directly with all its dependencies by running:

composer require --dev phpstan/phpstan

Or you can install phpstan-shim:

composer require --dev phpstan/phpstan-shim

The advantage of phpstan-shim is that it is a Phar file with all the dependencies packed inside (and prefixed), so they won't conflict with other dependencies you may have in your project. Therefore, I prefer using phpstan-shim.

To have the extensions configured automatically, you need to install phpstan/extension-installer:

composer require --dev phpstan/extension-installer

Using PHPStan

PHPStan can be run this way:

vendor/bin/phpstan analyse -l 0 src tests

It will probably report bunch of errors depending on your project size and age. The best approach from here is to gradually fix the issues and raise the level of strictness (-l 1 etc.).

If there are some issues which cannot be fixed easily, you can exclude them from the report. When doing so, try to be specific and put the filename in the exclude, so you won't exclude the issues from the whole project. And don't forget to properly escape the regular expressions, or you may be excluding way more things than you wanted (hint: | needs to be escaped too). Those exclusions should be included in phpstan.neon configuration file (which is passed as -c phpstan.neon to the analyse command).

You should also have a look at a new Baseline Feature in PHPStan, which allows you ignore all the current issues and let the PHPStan check just the new code.

Adding PHPStan to CI build

To prevent issues from creeping back to the codebase, you should include PHPStan in you CI build to have it fail when a new error appears.

It can be done easily by using Composer Scripts. Your scripts section in composer.json can look like this:

"scripts": {
    "phpstan": "phpstan analyse -c phpstan.neon src tests --level 7 --no-progress", 
    "tests": "phpunit",    
    "ci": [

It will run both the phpstan and tests scripts when you run composer ci.

Note: If you have a Symfony application, you will already have a scripts section in your composer.json, so just add new items there.

You can read more thoroughly about Composer Scripts in my article Have you tried Composer Scripts? You may not need Phing.

Configuring the Symfony extension

You might have noticed that PHPStan reports some issues in Symfony-specific code, that works OK. It is because there is no way for PHPStan to understand Symfony magic just from the code itself. It includes getting services from Container (you should not be doing it anyway!), working with arguments and options in Commands and much more.

To have those errors disappear, you need to install phpstan/phpstan-symfony extension and provide PHPStan with a path to Symfony container compiled to XML. It is usually stored in the var/cache/dev directory. The following configuration should be added to phpstan.neon file:

        container_xml_path: var/cache/dev/srcApp_KernelDevDebugContainer.xml

Also, to have the Commands analysed properly, PHPStan needs a console loader. It is a script that initializes the Symfony Console for the application and passes it to PHPStan. It can use it to determine the arguments or options types etc.

I usually put it in build/phpstan/console-loader.php:

<?php declare(strict_types = 1);

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;

require dirname(__DIR__) . '/../config/bootstrap.php';
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
return new Application($kernel);

The configuration file phpstan.neon should look like this:

        container_xml_path: var/cache/dev/srcApp_KernelDevDebugContainer.xml
        console_application_loader: build/phpstan/console-loader.php

With this configuration, PHPStan can understand the Symfony code. It also checks that you are not fetching non-existent (or private) services from container.

Configuring PHPStan with PHPUnit

In the previous part we have successfully configured PHPStan to check various things in Symfony projects. However, it is still possible to improve the configuration.

We are now using same configuration file for both src and tests, but Symfony uses a separate container when running in either dev or test environments. It means that PHPStan will report errors such as Service "Doctrine\ORM\EntityManagerInterface" is private. even if the tests work fine.

The solution is simple - use a separate configuration file for src and for tests. We can keep the current phpstan.neon, but we have to create specific configuration for tests - phpstan-tests.neon. It will look very similarly with only change being the container_xml_path which now points to the container compiled in var/cache/test:

        container_xml_path: var/cache/test/srcApp_KernelTestDebugContainer.xml
        console_application_loader: build/phpstan/console-loader.php

You need to adjust the scripts setup in composer.json to run PHPStan twice - first for the src directory and then for the tests with a different configuration file. When using this setup, you can still run composer phpstan which in turn runs checks for both src and tests.

"phpstan": [
"phpstan-general": "phpstan analyse -c phpstan.neon src --level 7 --no-progress",
"phpstan-tests": "phpstan analyse -c phpstan-tests.neon tests --level 7 --no-progress",

I know that the PHPStan configuration is duplicated a little bit, but that does not matter much (you are not adding new extensions that often).

One thing that you must keep in mind is that the Symfony container must be compiled before it can be used for analysis. You can do it by running bin/console cache:warmup --env=dev and bin/console cache:warmup --env=test. As it needs to be part of the CI build, you can put it to the Composer scripts as well:

"phpstan": [
    "@php bin/console cache:warmup --env=dev",
    "@php bin/console cache:warmup --env=test",

Or you can put it into a separate script, so it won't be slowing you down when running PHPStan repeatedly without changes in the container (but you must make sure that the container is recompiled for the test environment after change).

Finally, we are getting to configuring the PHPUnit extension itself. We need to install it through Composer:

composer require --dev phpstan/phpstan-phpunit

It will be included automatically thanks to the phpstan/extension-installer we installed in the beginning. So that's it.

PHPStan and Doctrine ORM

Doctrine ORM contains even more magic things which can't be inferred just from the code itself. Repository and Entity Manager use object type in lot of places, so the PHPStan won't know which type is there and you would need to add lots of inline PHPDoc to make it work.

Or you can install phpstan/phpstan-doctrine extension which helps PHPStan to understand Doctrine magic.

composer require --dev phpstan/phpstan-doctrine

Like with Symfony extension, you must help Doctrine extension by creating a loader script that provides an Entity Manager so PHPStan can query it about various things. I usually put it into build/phpstan/doctrine-orm-bootstrap.php and the script should look like this:

<?php declare(strict_types = 1);

use App\Kernel;

require dirname(__DIR__) . '/../config/bootstrap.php';
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
return $kernel->getContainer()->get('doctrine')->getManager();

You should add this to respective sections in both phpstan.neon and phpstan-tests.neon:

        objectManagerLoader: build/phpstan/doctrine-orm-bootstrap.php

With this setup PHPStan will use the EntityManager to also check your DQLs and Query Builders, which is awesome.

Next version of PHPStan-Doctrine extension will also support analysis of the entity annotations to determine whether the property type matches the column type, whether the property types for associations are defined correctly etc.

Bleeding edge features

PHPStan can check even more things when you enable bleeding edge rules from the core of PHPStan. Current PHPStan release is 0.11.x is mostly backwards compatible (not that many new issues are detected between patch versions). However, Ondra practices something along the lines of the trunk-based development, where new features (checks!) are hidden behind feature flags.

You can enable all of them by adding this to your configuration files (applies to phpstan-shim, the path will be different for a regular installation):

    - phar://phpstan.phar/conf/bleedingEdge.neon

Strict rules

There is a phpstan/phpstan-strict-rules package which adds opinionated checks not included in the PHPStan core. You can install it through Composer:

composer require --dev phpstan/phpstan-strict-rules

And suddenly you will get many more potential issues or bad practices reported :-)


If you configure the PHPStan according to this article, it will change your life :-) (at least a little bit).

Nowadays I can't imagine developing modern PHP applications without PHPStan running on max level with lots of checks. It helps to prevent many issues during development and refactoring of the applications.

<![CDATA[Jaké novinky přinese PHP 7.4]]> 2019-10-07T00:00:00+00:00 https://blog.martinhujer.cz/jake-novinky-prinese-php-7-4/ Vydání PHP 7.4 je plánováno na 28. listopadu 2019. Přináší hromadu zajímavých věcí – Typed Properties, Arrow Functions, Preload, FFI a pár dalších vylepšení. Sice přibyly i nějaké deprecations, ale typicky se týkají podivného chování, takže upgrade by měl být snadný.

Článek vyšel na serveru Zdroják.cz

<![CDATA[How to use Data Providers in PHPUnit]]> 2019-09-03T00:00:00+00:00 https://blog.martinhujer.cz/how-to-use-data-providers-in-phpunit/

I think that even if you are already using Data Providers, you will find some of those tips useful.

Data Providers are a handy feature of PHPUnit which allows you to run the same test with different inputs and expected results. This is useful when you are writing some text filtering, transformations, URL generation, price calculations, etc.

Let's say you are implementing your own trim function and you need to test it with lots of tests like the following one:


public function testTrimTrimsLeadingSpace(): void
    $input = ' Hello World';
    $expectedResult = 'Hello World';

    self::assertSame($expectedResult, trim($input));

Instead of duplicating the test method and just changing the inputs, you can use Data Providers:


 * @dataProvider provideTrimData
public function testTrim($expectedResult, $input): void
    self::assertSame($expectedResult, trim($input));

public function provideTrimData()
    return [
            'Hello World',
            ' Hello World',
            'Hello World',
            " Hello World \n",

Data Provider is just a regular public method in the test case class which returns an array of arrays.

  • You link it to the test method with a @dataProvider annotation followed by the method name.
  • Parameters returned from the data provider are added to the method definition. (I prefer to pass the expected value as a first parameter to match the assert* methods)
  • PHPUnit runs the test method with each data set as a separate test.

Here is a screenshot of running the test above in PhpStorm: PHPUnit: basic data providers in PhpStorm

Now you know how to use basic Data Providers. In the rest of the article I will dive into more advanced stuff and tips.

Tip #1: Always name the data providers

By default, each data provider is referenced by its array index. It means that when running it, PHPUnit will tell you, that the test failed with data set #0.

To prevent confusion when having lot of data providers, you should always name them. Because the data provider method returns a regular array, it is as easy as adding keys there:


public function provideTrimData()
    return [
        'leading space is trimmed' => [
            'Hello World',
            ' Hello World',
        'trailing space and newline are trimmed' => [
            'Hello World',
            "Hello World \n",
        'space in the middle is removed' => [
            "Hello World",

It makes test results much easier to understand:

PHPUnit: named data providers in PhpStorm

I recommend that you name the data sets the same way you would use for separate tests.

Tip #2: You can run single data set from data provider

When there is something wrong with one of the data sets, you probably want to run the test only with it. You can do so by using PHPUnit's --filter option in CLI.

Here are examples of what is possible (the documentation shows examples with ', but that does not work on Windows, where you have to use " to wrap the argument):

  • --filter "testTrimTrims#2" runs data set #2 (third data set, as the array keys start at zero)
  • --filter "testTrimTrims#0-2" runs data sets #0, #1 and #2
  • --filter "testTrimTrims@trailing space and newline are trimmed" runs specific named data set
  • --filter "testTrimTrims@.*space.*" runs named data sets that match the regexp

Don't forget to check the other possible patterns in docs.

PhpStorm does not currently allow you to run a single data set from the code (please vote for the issue WI-43933), but you can run all of them and then rerun one from the test results. When you have the JetBrains issue tracker open, please also vote for WI-43811 (possibility to go to the data set from the test results).

When I want to run a single data set from the PhpStorm I usually just comment out all the other data sets.

Tip #3: Add type definitions

I always add type hints to the method definitions when possible. When using data providers, it means adding parameter type hints to the method that accepts data sets and adding a return type (and phpdoc) to data provider method:


 * @dataProvider provideTrimData
public function testTrimTrims(
    string $expectedOutput,    // <-- added typehint here
    string $input              // <-- added typehint here
): void
    self::assertSame($expectedOutput, trim($input));

 * @return string[][]          // <-- added typehint here
public function provideTrimData(): array     // <-- added typehint here
    return [
        'leading space is trimmed' => [
            'Hello World',
            ' Hello World',
        'trailing space and newline are trimmed' => [
            'Hello World',
            "Hello World \n",

If you need to have a nullable type in the test method, I recommend splitting it into two separate methods and data providers. Instead of having testTransformData(?string $expectedResult, string $input) with a nullable parameter I would create those:

testTransformData(string $expectedResult, string $input) testTransformingInvalidDataReturnsNull(string $invalidInput)

Tip #4: Data providers are supported well in PhpStorm

Despite the issues I mentioned above (WI-43933 and WI-43811) I think that Data providers support in PhpStorm is quite good.

When you reference non-existing data provider, you can use quick action to generate it: PhpStorm: create data provider quick action

Auto-completion of data provider name works in the @dataProvider annotation: PhpStorm: rename data provider refactoring

Renaming the data provider using Rename refactoring also works as expected: PhpStorm: rename data provider refactoring

Tip #5: More complex data providers

Instead of having a static array written in the code, the data providers can be more complex and prepare the data set dynamically.

For example, when having the external data, you can easily generate resulting array this way:

 * @return string[][]
public function provideSpams(): array
    $spamStrings = require __DIR__ . '/fixtures/spams.php';

    $result = [];
    foreach ($spamStrings as $spamString) {
        $result[mb_substr($spamString, 0, 40)] = [$spamString];

    return $result;

You should be careful with adding lot of logic to the data provider. Otherwise you would have to write a test that tests the data provider...

You can even return instances from the data provider:


public function provideDateTimesPartOfDay(): array
    return [
            new DateTimeImmutable('2018-10-01 10:00:00'),
            new DateTimeImmutable('2019-09-01 15:00:00'),

Tip #6: You can use Closures in Data providers to delay evaluation

The disadvantage of data providers is that they are evaluated before anything else (to allow PHPUnit calculate the total number of tests). It means that they can't access anything initialized in setUpBeforeClass() or setUp().

You can work around this limitation by returning closures from data providers. Have a look at the code bellow - data provider returns a closure which is called in the test itself.


 * @dataProvider provideDateTransformations
public function testWithClosuresInDataProvider(
    string $expectedResult,
    Closure $setTime
): void
    $dateTime = new DateTime('2019-09-01');


    self::assertSame($expectedResult, $dateTime->format('Y-m-d H:i:s'));

public function provideDateTransformations()
    return [
        'midnight' => [
            '2019-09-01 00:00:00',
            function (DateTime $date): void {
                $date->setTime(0, 0, 0);
        '3 o\'clock in the afternoon' => [
            '2019-09-01 15:00:00',
            function (DateTime $date): void {
                $date->setTime(15, 0, 0);

Tip #7: Use yield to simplify large nested arrays

Instead of having large arrays in the data provider, you can use yield for each data set:

public function provideTrimData()
    yield 'leading space is trimmed' => [
        'Hello World',
        ' Hello World',
    yield 'trailing space and newline are trimmed' => [
        'Hello World',
        "Hello World \n",
    yield 'space in the middle is removed' => [
        'Hello World',

I think it may help with code readability for large arrays. However similarly to arrays, all yields are evaluated before tests start (PHPUnit calculates the total number of tests before running them).

Tip #8: Don't use @testWith annotation

Instead of using a separate method for data provider, PHPUnit supports inlining the data sets as JSON in PHPDoc using the @testWith annotation.

 * @testWith
 *        ["Hello World", " Hello World"]
 *        ["Hello World", "Hello World \n"]
public function testTrim($expectedResult, $input): void
    self::assertSame($expectedResult, trim($input));

Please do not use this, because PHPDocs is not a good place to put your code:

  • syntax highlighting does not work there
  • IDE code validation does not work there
  • automatic code formatting does not work there
  • it cannot be analysed statically by PHPStan


Do you have other tips to use Data Providers even more efficiently? Please share them in the comments!

<![CDATA[Don't put .idea and .vscode directories to project's .gitignore]]> 2019-08-26T00:00:00+00:00 https://blog.martinhujer.cz/dont-put-idea-vscode-directories-to-projects-gitignore/

tl;dr: Don't put .idea and .vscode directories to project's .gitignore. You should configure a global .gitignore for your machine instead.

When you are using git (or any other version control), there are some temporary files in the directory structure, which should not be included in the repository. Usually they are listed in the .gitignore file in the project root directory.

What if I told you that there are other ways to exclude temporary files from the project? There are three.

1. .gitignore file in the project's root

.gitignore in the project is the most important one. In it you should list any files or directories which are created by the application itself. Best examples are cache files, logs, local configs etc.

In the Symfony application your .gitignore may look like this (I included an explanation on each line):

/.env.local   <-- local config
/.env.*.local   <-- local config
/var/   <-- cache and logs
/vendor/   <-- dependencies installed through Composer

# PHPUnit
/phpunit.xml   <-- local PHPUnit config used for overriding the default phpunit.xml.dist
.phpunit.result.cache   <-- PHPUnit cache files

/phpcs.xml   <-- local PHPCS config used for overriding the default phpcs.xml.dist
.php_cs.cache   <-- PHPCS cache file

The important thing is that those files are created by the application itself by either building it, running it or doing some work on it.

2. Global .gitignore for your machine

Some files or directories present in the application directory are not created by the application itself, but by the operating system or other applications. Those shouldn't be excluded using the project's .gitignore, because they may differ from developer to developer.

Common examples are .idea (PhpStorm), .vscode (VS Code), Thumbs.db (Windows thumbnails cache), .DS_Store (some macOS cache).

There is a perfect place for them - the global .gitignore file for the machine. When you add something there, it is ignored in any repository, so you don't have to exclude those files in every project you happen to be working on.

You configure the path to the global .gitignore in the .gitconfig file which usually resides in your home directory:

# add this to ~/.gitconfig:

    excludesfile = ~/.gitignore

And create the .gitignore file in your home directory:

# create ~/.gitignore

# PhpStorm

# VSCode

From now on, those will be ignored in any git repository on your machine.

Quite often I see people adding those anyway. From a quick Github search you can see that there are almost 200k results for commits which mention some commonly ignored directory:

There is no point in adding those for the internally developed applications, as you can nudge each developer to properly configure his workstation. But if you are managing an open-source application or library, you may want to make it easier for newcomers to submit patches - even though you know it is not a clean solution. On the other hand, do you expect to receive high-quality contributions from developers who don't bother to properly configure their workstations?

3. .git\info\exclude

For the sake of completeness, I must mention the third option. You can use .git\info\exclude file to exclude files for a single repository. But those exclusions are not versioned, so the others won't benefit from them.

I can't remember using it, but you may find it useful in some situations.


Imagine that there is a new editor called Extra Textedit with an advanced AI which really helps with coding. As it becomes more popular, there will be a flood of commits and Pull Requests with add .eedit directory to .gitconfig.

Please use the global .gitignore and don't make the people on the internet spend hundreds or thousands of hours on this.

Btw. I recommend reading the gitignore documentation to learn more about the patterns you can use to exclude files.

<![CDATA[Oslo - co vidět? Kam na výlety?]]> 2019-08-15T00:00:00+00:00 https://blog.martinhujer.cz/oslo-kam-na-vylety/ V červenci 2019 jsme byli týden v Oslu a stihli jsem toho spoustu, včetně dvou zajímavých hike za město, tak třeba se vám to bude hodit jako inspirace.

Výhled na Oslo z Ekebergparken

Cesta do Osla

Letěli jsme z Prahy s Norwegian. Tím, že jsme byli docela omezení ve výběru termínu, tak nás nakonec zpáteční letenky (pro oba) stály 8700 Kč. Několik dnů po zabookování nám přišel mail, že zpáteční let ruší a že nás přehodili na jiný den. Pokud je to více než 14 dnů do odletu, tak to mohou klidně udělat. Ale v profilu pak šlo letenky zadarmo přebookovat na jiný termín, takže jsme zpáteční let přehodili na další den, na který ty lety předtím byly drahé. No a jako vždy jsme letěli jen s příručákem. Ani v Praze ani v Oslu velikost zavazadel neměřili.

MHD v Oslu

MHD v Oslu provozuje Ruter As a funguje to super. Na jízdenky se používá aplikace RuterBillet, kde si jízdenky přímo kupujete (existuje alternativa s nějakou plastovou kartičkou, to jsme nezkoumali). Koupili jsme si týdenní jízdenky. Výhoda je, že je můžete koupit dopředu a vybrat si čas začátku platnosti a případně je zapnout ručně. Kupodivu nefungovala platba kartou (MasterCard ani VISA, Airbank ani Fio), takže jsme platili PayPalem.

Většina Osla je v zóně 1, takže týdenní jízdenka nám stačila na tu jednu zónu. A na těch několik cest mimo si jde přes aplikaci přikoupit "Extra" na tu konkrétní zónu. Mají to hezky udělané, že místo toho, abyste museli zkoumat, jakou zónu chcete, jen vyberete, kam chcete jet a ono to ukáže, jakou zónu si máte koupit. Ty tickety na extra zónu jsou jen asi na hodinu až dvě, takže jde zase nastavit začátek platnosti a případně to těsně před nástupem do autobusu/lodi/vlaku odstartovat ručně.

Pro hledání spojů buď můžete používat Google Maps nebo jejich aplikaci RuterReise (ta ukazuje i zóny).

Většinu toho zvládnete linkami metra, které kromě centra jezdí po povrchu (historicky to bylo tak, že postupně zapojili samostatné tramvajové tratě do struktury metra).

Cesta z letiště

Z letiště je nejlepší jet vlakem VY. Pozor, na letišti mají mnohem lépe vidět prodej jízdenek do Airport Express Train (Flytoget), který stojí mnohem víc a není o moc rychlejší. Jízdenky na VY vlak si koupíte v automatu vlevo od toho Flytogetu. Nebo ještě lépe - pokud budete už mít dopředu koupenou jízdenku na MHD v RuterBillet, tak si jen přikoupíte zónu na letiště - je to levnější, než celá samostatná jízdenka.

Oslo - letiště

Peníze a nakupování

V Norsku je docela draho, cca 2-3× než v ČR. Vzhledem k tomu, že kurz NOK je 2.7 CZK, tak se dá docela dobře orientovat podle cen, které mají kde napsané - jsou podobné jako v ČR, jen nesedí měna :-) (streetfood 110-150 NOK atd.)

Zajímavost: V Norsku fungují zálohy na PET láhve a plechovky, podobně jako v ČR na láhve od piva. (video)

Všude se dá platit kartou. I když jsme došli lesem na takovou chatu, kde měli suché záchody, tak stejně měli terminál na karty. Stejně tak kiosek na ostrůvku u pláže. A veřejné záchodky v parku mají u dveří také terminál na pípnutí kartou. Takže jsme za celý týden neměli v ruce žádné norské peníze (ale nosili jsme nějaká eura, kdybychom s kartou náhodou někde neuspěli.)

Bydleli jsme na Airbnb, takže jsme si večeře vařili a k obědu jsme měli vždycky svačinu, protože jsme stejně byli někde na výletě. Suroviny v obchodě zas tak drahé nejsou (doporučuji supermarkety Kiwi nebo Rema 1000). A třeba takový chlazený losos stojí stejně jako v ČR ;-)

Když mluvím o bydlení, tak doporučuji bydlet na metru a max pár stanic od centra, ať ušetříte čas.


V Norsku se prodává víc elektroaut než těch se spalovacím motorem. Je to hlavně kvůli různým úlevám pro EV a naopak ekologické dani z aut se spalovacím motorem (více info). Vzhledem k tomu, že většinu elektřiny (95 %) získávají z vodních elektráren, to zní jako super nápad. Nevýhoda je, že rozvodná síť na to není připravena a budou do ní muset hodně investovat.

A s tím souvisí jedna zajímavost - Ruter (dopravní podnik v Oslu) zkouší na jedné krátké lince provozovat autonomní elektrické minibusy. To jsme samozřejmě museli vyzkoušet :-)

Autonomní miniautobus

Drøbak a Oscarsborg - výlet lodí

Drøbak je vesnice asi 30km od Osla, kam se dá dojet MHD lodí (pozor, je potřeba přikoupit zóny - viz výše). Když budete stát frontu na trajekt, tak vám bude připadat, že se tam nevejdete - ale ta loď je fakt velká (cca pro 200 lidí), tak se nebojte.

Drøbak - cesta lodí

Lodí můžete dojet až do Drøbaku nebo vystoupit o zastávku dříve, na ostrově Oscarsborg, kde je vojenská pevnost. Odtamtud se pak dá do Drøbaku dostat přívozem. Je to dost důležité místo norských dějin, protože v roce 1940 se jim z té pevnosti podařilo potopit německý křižník Blücher a tím zbrzdit invazi Němců do Osla tolik, že norský král a vláda stihl utéci. Doporučuji přečíst článek na Wikipedii: Battle of Drøbak Sound. My jsme tam nebyli, protože jsem to zjistil až potom.

Drøbak - Oscarsborg

V samotném Drøbaku je zajímavá třeba pláž, Husvikbatteriet - dělo, které se přičinilo o potopení křižníku Blücher. A kousek na kopci Veisvingbatteriet - další děla, tentokrát v lepším stavu a se super výhledem na Oscarsborg.

Drøbak - dělo

Zpátky z Drøbaku jsme už jeli autobusem, kterým to je rychlejší, než lodí.

Østmarka hike (16 km)

Oslomarka je označení pro lesnatou a kopcovitou krajinu v okolí Osla, kterou Norové využívají k rekreaci (výlety, kola atd.). My jsme vyrazili do východní části nazvané Østmarka.

Østmarka - Svartputten


Metrem jsme dojeli na konečnou, do Ellingsrudåsen. Tam už kousek od východu z metra (který je vyražený ve skále!) začíná turistická trasa. Nejprve jsme šli kousek po hlavní turistické, která je zároveň cyklostezka, ale pak jsme odbočili směrem na Haukåsen. Většinou se nám podobné pokusy - třeba vylézt na nějaký vysoký kopec v okolí - odmění fajn zážitkem. Cestou jsme si udělali malinkou odbočku k jezeru Svartputten a pokračovali jsme se na Haukåsen. Ten je zajímavý zejména tím, že tam je umístěný radar.

Haukåsen radar

Odtamtud jsme došli na chatu Mariholtet, kde se dá občerstvit. Co nás hodně překvapilo bylo, že si místní nedávají pivo, ale kávu nebo colu. To by se v Čechách nestalo :-)

Dál jsme pokračovali po červené a pak po západní straně jezera Lutvann, odkud byl super výhled. Nakonec jsme došli na metro do Haugeurd a jeli domů.

Østmarka - Lutvann

Mrkněte na celou trasu na Wikiloc.

Výhoda chození na výlety v Norsku v létě je, že vám díky pozdnímu soumraku nehrozí, že byste se do tmy nestihli vrátit.


Holmenkollen je známý skokanský můstek, kde se konají různé důležité závody (nejen) ve skocích na lyžích. První můstek tam byl postaven koncem 19. století a zajímavé je, že ho jednou za čas zbourají a pak postaví nový a větší (naposledy v roce 2010).


Metrem dojedete do stanice Holmenkollen a odtamtud dojdete kousek k můstku. Součástí areálu je muzeum, které popisuje historii samotného můstku, ale i polárních výprav. V ceně vstupenky je i výstup (resp. vyvezení se výtahem) na vršek můstku, odkud je super výhled. Doporučuji jít nejdříve vystát tu frontu na výtah, čím tam dorazíte později, tím je větší. Ale muzeum určitě nevynechejte (můžete si ho projít až po návratu z vyhlídky).

Kromě výhledu nabízí horní plošina možnost sjet si dolů na zipline - ale mě to teda přišlo dost vysoko :-) (na fotce jsou vidět ta ocelová lana)


Hike přes Vettakollen a Ullevålseter k jezeru Sognsvann (15 km)

Z Holmenkollen jsme popojeli pár stanic na zastávku Vettakollen, odkud jsme vyrazili na další hike.

První část cesty je trochu do kopce, ale brzy přijde odměna v podobě super výhledu na Oslo (je to kupodivu dříve, než samotný vrchol Vettakollen - odtamtud není vidět nic).

Vettakollen výhled

Pak pokračujete lesem, cestou občas potkáte nějaké jezero, jezírko nebo mokřad. Nejvzdálenějším bodem je Ullevålseter, což je taková horská chata, u které se potkávají různé turistické a cyklo trasy. I když to je uprostřed ničeho, tak samozřejmě jde platit kartou. A pozor, podle webu mají v pondělí zavřeno, tak ať vás to nepřekvapí. Dali jsme si tak ke svačině kávu, horkou čokoládu a něco sladkého.

Chata Ullevålseter

Jezero Lille Aklungen

Cestou zpět jsme šli kolem dvou velkých jezer, a nakonec jsme došli k jezeru Sognsvann. To je kousek od města, takže tam spoustu místních chodí běhat.

Celá trasa opět na Wikiloc.

Jezero Sognsvann

Grilování, jednorázové grily

Možná to bylo tím, že jsme v Oslu měli dobré počasí, ale mezi místními je hodně populární grilování na jednorázových grilech (takové ten alobalový tácek naplněný uhlím). Grilují všude možně, třeba právě u toho jezera Sognsvann.

Hodně zajímavé je, že tomu museli přizpůsobit infrastrukturu. Na místech, která se hodí na grilování, mají kromě obyčejných košů také speciální kovový koš na vyhazování těch jednorázových grilů!

Koš na grily


Na kávu jsme nikam moc nechodili, takže mám jen jeden tip na kavárnu - Espresso House.

Na Airbnb byl Nespresso kávovar, takže jsme si kávu vařili doma. S tím souvisí zajímavost - jak jsem psal výše, tak všechno je v Norsku 2-3× dražší, ale Nespresso kapsle stojí v přepočtu stejně jako v ČR (NOK 3,89 vs CZK 9,90). Pro kapsle jsme se stavili v Nespresso obchodě na jejich "Pařížské".

Oslo - ostrovy Langøyene a Gressholmen

Kromě delší cesty lodí do Drøbaku, o které jsem psal výše, můžete v Oslu vyrazit MHD lodí na ostrovy, které jsou kousek za městem (a zase platí, že ta loď vypadá malá, ale vejde se tam spoustu lidí, takže nezůstanete na břehu ani když tam bude velká fronta).

My jsme nejdříve vyrazili na Langøyene, kde je fajn pláž. Kromě toho to je jediný z ostrovů, na kterém se může stanovat. Kousek od pláže je kiosek, který samozřejmě přijímá platební karty.

Odtamtud jsme přejeli na ostrov Gressholmen. Ten je mnohem více zarostlý, ale taky je na něm několik pláží. A zajímavostí je, že na něm kdysi bylo první norské letiště (pro hydroplány).


A teď zajímavé věci v samotném Oslu.


Ekebergparken je kopec s parkem na kraji Osla. Nejlepší je dojet tramvají přímo do zastávky Ekebergparken a odtamtud se projít po parku, prohlédnout si sochy a dojít na vyhlídku, odkud je vidět kontrast starých a nových budov v Oslu. Odtamtud se pak dá po cestičce sejít dolů do centra.

Sochy v Ekebergparken


Operahuset je zajímavé zejména tím, že je možné vyjít až na střechu, odkud je fajn výhled.

Oslo - Operahuset

Pevnost Akershus

Určitě doporučuji nevynechat pevnost Akershus - kromě historických budov je odtamtud hezký výhled na přístav.


Akershus - výhled na přístav

Norsk Folkemuseum

Do Norsk Folkemuseum postupně přemístili různé budovy, aby mohli ukázat, jak v minulosti žili lidé (nejen) na norském venkově. Areál si buď můžete projít sami nebo se připojit k některé z komentovaných prohlídek (doporučuji!). Nejvýznamnější památkou v muzeu je kostel přibližně z roku 1200, který koncem 19. století nechal král přestěhovat tam, kde je teď.

Norsk Folkemuseum - kostel

A jedna zajímavost, kterou jsme se tam dozvěděli. Určitě znáte takové ty domky se střechami porostlými trávou (a teď nemyslím moderní kancelářské budovy). Vždycky jsem myslel, že ta tráva tam má nějaký účel, ale ono ne. Jen holt tenkrát dávali na střechu vrstvu hlíny jako izolaci a shodou okolností z ní pak rostla tráva.

Norsk Folkemuseum - tráva na střeše

Playa de Huk

Když už kvůli Folkemuseum (nebo jinému muzeu) budete na poloostrově Bygdøy, tak si vezměte plavky a běžte se vykoupat na Playa de Huk. My jsme měli štěstí na počasí - bylo 28°C, takže pláž byla narvaná :-)

Královský palác a zahrada

Když už budete v Oslu, tak se určitě zajděte podívat na královský palác a do jeho zahrad.

Královský palác

Královský palác - zahrada


Velký park, jedno z více turistických míst. Zejména kvůli zajímavé fontáně obklopené turisty a sloupovému sousoší.

Frognerparken - fontána

Frognerparken - sousoší


V Oslu a jeho okolí toho je spoustu k vidění. Určitě doporučuji zařadit nějaké výlety za město než jen chodit mezi budovami.

<![CDATA[Budapešť - co vidět? Kam na výlety?]]> 2019-06-10T00:00:00+00:00 https://blog.martinhujer.cz/budapest-kam-na-vylety/ V květnu 2019 jsme byli v Budapešti a za těch pár dnů jsme toho stihli spoustu (včetně zajímavého hike), tak třeba se vám něco bude hodit jako inspirace.

Budapešť - výhled od Citadelly

První zajímavostí Budapešti je název. Původně to totiž byla dvě samostatná města - Buda a Pešť, každé na jednom břehu Dunaje :-)

Cesta do Budapešti

Původně jsme chtěli jet vlakem, ale Regiojet tam nejezdí (přímo) a s ČD nám to stačilo před dvěma roky do Varšavy. Takže jsme nakonec jeli Regiojet autobusem. A abychom zbytečně nestrávili celý den dovolené v autobuse, tak jsme jeli přes noc - ve 23 h z Florence. A ráno jsme kolem 6 h byli v Budapešti.


MHD v Budapešti funguje super, mají tam metro, tramvaje a autobusy. Na vyhledávání spojení doporučuji buď Google Maps nebo přímo jejich mobilní appku BKK Futár (v obojím jsou realtime data o spojích - včetně případného zpoždění).

Nejvýhodnější je koupit si v automatu sadu 10 jízdenek, vychází to pak na cca 24 Kč za jednu. Jízdenky jsou nepřestupní (ale jde přestupovat mezi linkami metra). A nezapomeňte si je vždycky označit, prý tam revizoři (podobně jako v Praze), rádi číhají na turisty, kteří si koupí v automatu jízdenku a nenapadne je, si ji o pár metrů dál ještě označit. Automat, kde jsme jízdenky zkoušeli koupit z nějakého důvodu nebral Revolut kartu.

Určitě doporučuji MHD používat, ušetří vám to spoustu času při přesunech.

Peníze, HUF

V Budapešti je podobně draho jako v Praze. Na většině míst berou platební karty, ale ne všude. Na výběry z bankomatů si pořiďte Revolut kartu (vybírám po €20 nebo €40). A pozor, v Budapešti je na každém rohu Euronet bankomat, kde velmi pravděpodobně zaplatíte zbytečné poplatky navíc. Doporučuji raději vybrat z bankomatu, který patří přímo pod nějakou banku - tam vás nebudou zkoušet odrbat.


Bydleli jsme v Airbnb (konkrétně v tomhle) blízko Deák Ferenc tér, což je lokalitou podobné jako Můstek. Kousek od metra, tramvají i autobusů a cokoliv v centru v docházkové vzdálenosti.

Lodní výlet

Původně jsme chtěli jít na nějaký lodní výlet, ale nemohli jsme vybrat na který (každý měl nějaké dost negativní recenze). Nakonec jsme zjistili, že stojíme kousek od jedné ze zastávek lodního MHD. Na rozdíl od Prahy to není jen přívoz napříč řekou, ale naopak jede po celé délce města a staví na různých zastávkách na obou březích. Zásadní výhoda je, že lístek stojí 750 HUF (cca 60 Kč). Takže jsme se rozhodli si udělat výlet až na konečnou a zpět. Plavba centrem Budapešti je super a myslím, že ani na standardní boat tour toho o moc víc neuvidíte. Ale ještě zajímavější je, že pak vyjedete z centra a pluje se mezi zarostlými břehy, kde skoro nic není :-) Jako odpolední relax to bylo super.

Budapešť - lodní výlet - parlament

Dohány Street Synagogue

Velká synagoga (opravdu se ji tak říká) je největší synagoga v Evropě (a asi třetí na světě).

Pro mě jedna z nej věcí, které stojí za to navštívit. Při vstupu do synagogy dostanete papírovou jarmulku, kterou musíte mít na hlavě. Mezi lavicemi jsou rozmístěny vlajky a můžete si vybrat jazyk a zúčastnit se komentované prohlídky. Super je, že ti průvodci jsou přímo členové místní židovské obce, takže ten výklad je hodně autentický (a tipuji, že se mezi průvodci liší).

Budapešť - synagoga

Památník obětem 2. světové války

Památník není až tak zajímavý vzhledem, ale spíš politickými souvislostmi. V Maďarsku byly antisemitistické nálady už před příchodem nacistů a zároveň vláda Němcům ochotně pomáhala s deportací židů. Takže se proti památníku hodně protestovalo, až ho nakonec dokončili potají v noci. Detailněji viz článek na Reuters. Hned vedle toho "oficiálního" památníku je protestní památník, kde je mezi sloupky natažený ostnatý drát se zavěšenými fotkami obětí a vysvětlujícím textem v různých jazycích. A pod tím jsou umístěné kufry a svíčky.

Budapešť - Památník obětem 2. světové války

Památník na břehu Dunaje

Na břehu Dunaje najdete památník připomínající těch, kteří byli zastřeleni maďarskou nacistickou stranou během 2. světové války přímo na břehu řeky. Viz článek na Wikipedii.

Budapešť - Památník na břehu Dunaje - boty

St. Stephen's Basilica (Szent István Bazilika)

St. Stephen's Basilica je zajímavá zejména tím, že se dá dostat na věž se super výhledem.

Budapešť - Szent István Bazilika

Na náměstí před bazilikou mají zmrzlinu, kterou naskládají do tvaru květu.

Budapešť - Szent István Bazilika - zmrzlina


Citadella je pevnost se sochou na kopci. Něco jako Vítkov v Praze. Je odtamtud hezký výhled.

Budapešť - výhled od Citadelly

Hrad Buda

K Hradu Buda buď můžete vyjít pěšky nebo lanovkou jako je na Petřín. Na rozdíl od Petřína je to tady výrazně menší kopec. A pokud tam nebudete hned ráno, tak tam bude velká fronta.

Budapešť - Funicular

Před prezidentským palácem můžete vidět střídání stráží. Případně, pokud tam budete tak brzy jako my, tak instalaci stráží :-)

Budapešť - stráže

Na hrad se stojí za to dojít podívat i v noci, u kostela svatého Matyáše je na hradbách ochoz, kam se dá v noci zadarmo (přes den tam vybírají vstupné). Je odtamtud hezký výhled na noční Budapešť. Pokud se vám to v noci nechce nahoru šlapat pěšky, tak můžete dojet autobusem skoro až k tomu kostelu.

Budapešť - Kostel sv. Matyáše - noční

Budapešť - noční

Margaret Island

Margaret Island je takový relaxační ostrov s lesoparkem, lavičkami, restauracemi, zoo a běžeckou dráhou s měkkým povrchem.


Lázně Széchenyi také určitě stojí za návštěvu, ale ve výsledku to byla hromada přeplněných bazénů s horkou a různě smrdutou vodou.

Park se jezírkem

Lázně se nacházejí v parku, kde je také docela velké jezírko s půjčovnou šlapadel.

Budapešť - šlapadla

Výlet z Dömösu do Visegrádu

Za mě asi nejlepší věc z Budapešti, i když nebyla v Budapešti. Autobusem (spol Volánbusz) jsme vyrazili do vesničky Dömös a odtamtud jsme přes kopce a lesy došli do Visegrádu.

Budapešť - Dömös - Visegrád hike - mapa (záznam trasy na Wikiloc)

Byla to krásná cesta lesem s hezkými výhledy.

Budapešť - Visegrád hike - les

Budapešť - Visegrád hike - výhledy

Cestou jdete kolem rozhledy Prédikálószék, odkud je výhled na Danube Bend, což je ohyb na Dunaji. Prostě jako vyhlídka Máj.

Budapešť - Visegrád hike - rozhledna Prédikálószék

Budapešť - Visegrád hike - Danube bend

Následně dojdete do vesnice Pilisszentlászló, kde doporučuji restauraci Kis Rigó Vendéglő. Mají krůtu v těstíčku s ananasem a posypané skořicí (pineapple turkey)

Budapešť - Visegrád hike - pineapple turkey

Odtamtud pak pokračujete scénickou lesní cestou podél potoka až do Visegrádu. Tam už jsme si po 20 km stihli dát jen jednoho Bernarda (všude mají česká piva!) a jeli jsme autobusem zpět.

Jídlo a pití

Burger Market

Burger Market jsme měli kousek od bytu a zajímavý je tím, že v jednom prostoru mají kromě burgerárny také taco a pasta bary.

Food market

Food market jsou různé foodtrucky, podobné Manifesto Marketu u Masaryčky v Praze. Budapešť - Food market


Vnitřní trh, kde se dá koupit spoustu zajímavých věcí. Určitě doporučuji koupit mletou papriku (koření). A u stánku v patře ochutnat nějaký netypický langoš. Budapešť - trh


  • Forest Café příjemná pidikavárna
  • Espresso Embassy - výborná kavárna, byli jsme tam dvakrát nebo třikrát. Nezapomeňte vyzkoušet dort s pekanovými ořechy.
  • Kalap Bistro - parádní snídaně


Budapešť určitě stojí za návštěvu, z ČR je to kousek. A i za pár dnů se toho dá stihnout spoustu. Určitě doporučuji si nechat čas na ten hike.

<![CDATA[Fuerteventura - Things to Do, Best Places to Visit]]> 2019-05-05T00:00:00+00:00 https://blog.martinhujer.cz/fuerteventura-things-to-do/

This article is a translation of my travel guide for Fuerteventura (in Czech).

In October 2018 we spent almost three weeks in Fuerteventura, so I have lots of tips for interesting places and about Fuerteventura in general.

Fuerteventura - Puerto del Rosario

Traveling there - airline tickets, bus

You can fly to Fuerteventura directly from Prague, but one-way ticket costs around 7k CZK (280€) so in the end we flew with Ryanair from Berlin. One ticket from Schönefeld costs around 30€ and you can get there easily by Flixbus or Regiojet. You can also use Czech Rails (ČD) train - morning tickets at 6:30AM from Prague are quite cheap.

I already mentioned this tip in other post, but it is worth repeating here. At the airport, after passing through security, you can usually buy cheap bottles of water in the vending machine (0.5l for 1€ in Fuerteventura). On the other hand, in Schönefeld, you can bring an empty bottle through and refill it with the tap water after the security.

Fuerteventura Buses

Buses on Fuerteventura are operated by Tiadhe company. You can find timetables on their website - unfortunately, they provide only the departure times from the starting stop, not when the bus passes through each stop. You can buy the tickets at the bus (cash only).

Fuerteventura Ferries

After spending two weeks in Fuerteventura we went to Lanzarote for next two weeks and then back again. We took a ferry from Corralejo city to Arrecife. You can choose from three companies that service this route - Armas, Fred Olsen and Lineas Romero. Fred Olsen is the fastest but also the most expensive. We used Lineas Romero ferry both times (it costs 15€ per person). Their ferry is more of a tourist boat than a ferry (and they don't transport cars).

Fuerteventura Car Rental

We weren't on Fuerteventura only for holidays, but we also had to do some work at least part of each day, so we rented car for whole two weeks. Thanks to that we could work in the mornings and take trips in the afternoon (which would be much harder if we had to take a bus). It turned out that the most expensive option is renting for a single day. If you rent for a longer period, the price lowers considerably. Rental for two weeks cost us 220 € (+gas).

We rented the car at the airport from the Cicar company (we also used them in Lanzarote). They offer good insurance and they don't require you to have a credit card (debit card is OK). You can book the car in advance through the website to be sure they have it available for you. It will also speed up the rental process, because you can pre-fill all the info online and they will just check it. You can also add a second driver for free. We returned the car in Corralejo (you don't have to return it to the same office). The fuel level should be approximately same as when you were handed the car.

Careful: You should not drive the rental cars on the dirt roads - it is usually not covered by insurance (check the terms of your rental agreement).

Faro del Tostón - Opel Astra


The weather was good for swimming all the time - temperatures around 24°C and hot when the sun shined. Temperatures at night were just below 20°C.

Fuerteventura food

Fuerteventura is renowned for its goat cheese - queso majorero (see below).

Tap water is desalinated, therefore it is not suitable for drinking and you must buy water in the supermarket. Surprisingly, the 8l barrels usually have better handle than the 5l ones, so despite being heavier, they are easier to carry.

Bigger supermarkets offer fresh fish and seafood. We usually buy precooked shrimp (langostino cocido, 9€/kg) or tuna steak (rodaja de atún, 12€/kg). This year we started buying octopus as well (pulpo cocido). You can buy a precooked whole tentacle or pieces. Just put it in the pan for a while with olive oil and add grounded sweet pepper (pulpo a la gallega).

Pulpo a la Gallega


Some Spanish banks charge an extra fee for a cash withdrawal (in addition to your bank fee). But the Cajamar Caja Rural bank does not charge the extra fee yet (I tested it with Revolut and Airbank card.)

Cities in Fuerteventura

Fuerteventura is a large island (the second biggest of the Canary Islands) and only 100 thousand people live there, so it is quite empty. We didn't want to spend that much time in car, so first week we stayed in the north and explored the places there and the second week, we stayed in the south and visited the places there. The cities in Fuerteventura are different from each other - ranging from locals-only to built-for-tourists.

El Cotillo

El Cotillo is a small town in the north where we spent the first week. As it is not overflowing with tourists, we would also consider it for a longer stay (month+). It has a great surfing beach on one side and the beach lagoons on the other side.

  • Lagoons is a part of the beach which is protected by natural breakwater and shallows caused by low tide make it great for children.
  • Castillo del Tostón - small fort with good views. Entrance fee is 3€, can be paid only by card (no cash).
  • El Goloso de Cotillo - café where we went at least five times (during one week!)
  • Kalima - good tapas bar

El Cotillo - lagoons


Corralejo is resort popular among British tourists. We didn't like the beach much. And it is more expensive in comparison with El Cotillo.

Puerto del Rosario

Puerto del Rosario is the capital of Fuerteventura with very few tourists. We spent last five days there. Many cruise ships harbour in the city port. You can use MarineTraffic.com (FlightRadar for ships) to see more information about the ships. The city has a nice beach with white sand.

  • Oficina de Turismo de Puerto del Rosario - tourist information centre. I recommend visiting it just after arrival to get some paper map and lot of tips (I guess they know more things than I have in this article)
  • Café Atlántico - cafeteria with a sea view. They offer good and cheap sandwiches.
  • Cafetería Del 15 - random cafeteria, where we had lunch after arrival because we were starving. I guess we were the only tourists there in a while :-)
  • free parking close to the city centre. Check that you have the correct one - there is another free parking, but much farther from the city centre. Try to guess where we parked ;-)
  • Panadería Pastelería Pulido Alonso - pleasant café, they have several shops around the island.

More cities

  • Calleta de Fuste - place built for tourists

  • Gran Tarajal - town interesting mainly for its black sand beach and for the abundant narrow one-way streets.

  • Costa Calma - resort with enormous beach where we spent our second week on Fuerteventura. This one is popular among German tourists. We stayed in a nice apartment a bit farther from the city centre. In the local Spar shop you can even buy Dallmayr coffee.

  • Morro Jable - resort in the south of the island with a great beach.

Fuerteventura Trips

We made a lot of trips in Fuerteventura. I already mentioned some cities and places above. We visited all of them by car, so I'm not sure which of them are easily accessible by bus.

El Faro del Tostón

El Faro del Tostón is a lighthouse close to El Cotillo. You can visit the museum of the traditional fishing (we arrived late, so it was already closed).

El Faro del Tostón


Lajares is recommended in the Lonely Planet because of its cafés. Yes, the cafés are there, but there is nothing else of interest.

La Oliva

La Oliva is a small village with an old church and gallery. You should also visit wind-mills Molinos De Villaverde nearby. Careful, that the road does not go all the way to them, but it changes to a bumpy dirt road. Therefore, it is better to leave the car next to the main street and walk to the windmills.

Molinos De Villaverde

Museo del Queso Majorero

Even though the Fuerteventura may appear dry and barren, they breed the majorero goats there and make cheese from their milk (under different brands). You can learn more about how the cheese was produced in the past and how it is produced nowadays in Museo del Queso Majorero. I really liked the museum as the exposition was well prepared and entertaining. The area includes also a windmill and cactus garden, so leave yourself enough time to visit all those places. And speaking about the goats - be careful as you can encounter them all over the island next to the road or even crossing the road.

Museo del Queso Majorero

Antigua and Pulido Alonso

I already mentioned Pulido Alonso café, but the one in Antigua is special for us. When we were heading to Corralejo for a ferry after two weeks in Fuerteventura, we realized we have too much time. So, we decided to stop for coffee. Antigua happened to be nearby, and we chose the Pulido Alonso by the rating. We had great coffee and cakes there.


Betancuria used to be capital of Fuerteventura in the past. Apart from other things, you can visit the church from 15th century. The city is like Charles Castle near Prague in one aspect - everything is closed after 5PM. Close to Betancuria you can visit statues of ancient kings and admire the views.

Statues near Betancuria

Views near the statues near Betancuria

Parque Natural Dune De Corralejo

Sand dunes near Corralejo. You can park easily on this parking place. If you never have been to sand dunes, I recommend this one hundred percent, it's a great experience (you can visit another in Maspalomas in Gran Canaria). Don't forget to bring your sun cream! The sun is really strong in the dunes. Between the sand dunes and the Corralejo, you can find some nice beaches. But we didn't swim there in the end, because when we arrived there was a yellow warning flag and before we took our things from the car, lifeguard swapped it for the red one.

Corrajelo Dunes

But it wouldn't be us to spend whole day walking around on dunes. Instead we hiked to the nearby hill Montaña Roja. You can check the recorded map in Wikiloc. If you watch the map carefully, you can see that we parked unnecessarily far. But from the car, all the places appeared to have to big curb to park there and I didn't want to break the car. But when we walked there, we realized that at least two places are suitable for parking, so I marked them in the map. And the view from the Montaña Roja was spectacular.

Montaña Roja

Salinas del Carmen

You can either get salt from salt mines or you can let the sea water dry up. That's how they did it in the past and how they do it now in the museum of salt production Salinas del Carmen. They have nice indoor exposition, but the main part is outside, where you walk through the whole process of salt production.

Salinas del Carmen

Pájara and a viewpoint on FV-605

When we drove somewhere and we had enough time, instead of using main road (FV-1, FV-2), we used some small scenic road over the hills and villages (FV-605). This way we visited village Pájara. You can find several interesting things there - church from 17th century, dried-up river and a roundabout with a statue of man milking a goat.

Pájara - dried-up river

Pájara - statue of man milking a goat

If you follow the FV-605 south from Pájara, you can stop at perfect viewpoint

Viewpoint at FV-605

Pozo Negro

Fisherman village in the end of the road. There is a black-sand and pebble beach and two cafés.

Faro de la Entallada

Faro de la Entallada is a lighthouse on the cliff with a great sea view. Last part of the road is really narrow and with few guardrails, so be careful.

Faro de la Entallada

Mirador del Salmo

View point Mirador del Salmo is worth visiting when you are driving nearby. It is just next to the road.

Pico de la Zarza

Pico de la Zarza (sometimes also called Pico de Jandía) is the highest mountain of Fuerteventura. Although it has only 807 meters above sea level, you are starting at 0 m. The hike is about 15 km and you can check our walk on Wikiloc. We started the ascent at 9AM and luckily it was partly cloudy on that day. Don't forget to bring enough water and something to cover you head, because there is no place to hide from sun. We parked unnecessarily far; we could have parked at the place where the dirt road started. From the top of the mountain you can admire great views also to the north side of the island.

Pico de La Zarza


Cofete is a nice beach on the northern side of the island. Swimming is forbidden there because of the strong sea currents. You can get there either by car (by the dirt road) or by a special 4x4 bus from Morro Jable. It goes from Estación de Guaguas (=main bus station). It is a line L111 and it takes only 18 people, so be there early.

Cofete bus

You can get back by the same bus or you can hike back over the hill (12 km). Our walk on Wikiloc.

Cofete - path over the hill

Islote de Lobos

Islote de Lobos is a small island not far from Corralejo. You can take a tourist boat there. This is the only thing from the whole article we have kept unvisited for the next time. But I include it here for the sake of completeness.


Although we didn't spend that much time on Fuerteventura, we managed to do lot of things and visit lot of places. Having a rental car for the whole time really helped, because we could do just a short trip in the afternoon which won't be possible if we went by bus.

If you go to Fuerteventura and have enough time, I recommend spending at least a week on Lanzarote, where are also many things to see.