Robert Basic's blog

Archive for the 'Development' category

Enable UDP for NFS on Fedora

by Robert Basic on July 03, 2017.

Recently bringing up Vagrant boxes started acting up when mounting the NFS shared folders. This is the error message I get:

==> default: Mounting NFS shared folders...
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

mount -o vers=3,udp 192.168.33.1:/home/robert/projects/project/application /var/www

Stdout from the command:



Stderr from the command:

mount.nfs: requested NFS version or transport protocol is not supported

For some reason NFS doesn’t like UDP on my machine, but as far as I know, UDP is the default in Vagrant.

This can be changed by telling Vagrant to not use UDP for synced folders, by adding nfs_udp: false:

  config.vm.synced_folder "./application", "/var/www", type: "nfs", nfs_udp: false

But as this is something only I have experienced in my team so far, “fixing” it on a project level seems like a bad choice. And when the next project comes, I’ll probably have the same problem all over again.

Digging a bit deeper, I’ve came across this ServerFault answer, which says that since nfs-utils version 2.1.1 UDP support for NFS is disabled by default.

The solution is to edit /etc/sysconfig/nfs and add --udp to RPCNFSDARGS:

RPCNFSDARGS="--udp"

Restarting the NFS server and Vagrant mounts the shared folders without problems again!

Happy hackin’!

Tags: fedora, nfs, udp.
Categories: Development, Software.

Complex argument matching in Mockery

by Robert Basic on May 08, 2017.

This past weekend I did some issue maintenance and bug triage on Mockery. One thing I noticed going through all these issues, is that people were surprised when learning about the \Mockery::on() argument matcher. I know Mockery’s documentation isn’t the best documentation out there, but this still is a documented feature.

First of all, Mockery supports validating arguments we pass when calling methods on a mock object. This helps us expect a method call with one (set of) argument, but not with an other. For example:

<?php
$mock = \Mockery::mock('AClass');

$mock->shouldReceive('doSomething')
    ->with('A string')
    ->once();

$mock->shouldReceive('doSomething')
    ->with(42)
    ->never();

This will tell Mockery that the doSomething method should receive a call with A string as an argument, once, but never with the number 42 as an argument.

Nice and simple.

But things are not always so simple. Sometimes they are more complicated and complex.

When we need to do a more complex argument matching for an expected method call, the \Mockery::on() matcher comes in really handy. It accepts a closure as an argument and that closure in turn receives the argument passed in to the method, when called. If the closure returns true, Mockery will consider that the argument has passed the expectation. If the closure returns false, or a “falsey” value, the expectation will not pass.

I have used the \Mockery::on() matcher in various scenarios — validating an array argument based on multiple keys and values, complex string matching… and every time it was invaluable. Though, now that I think back, the older the codebase, the higher the usage frequency was. Oh, well.

Say, for example, we have the following code. It doesn’t do much; publishes a post by setting the published flag in the database to 1 and sets the published_at to the current date and time:

<?php
namespace Service;
class Post
{
    public function __construct($model)
    {
        $this->model = $model;
    }

    public function publishPost($id)
    {
        $saveData = [
            'post_id' => $id,
            'published' => 1,
            'published_at' => gmdate('Y-m-d H:i:s'),
        ];
        $this->model->save($saveData);
    }
}

In a test we would mock the model and set some expectations on the call of the save() method:

<?php
$postId = 42;

$modelMock = \Mockery::mock('Model');
$modelMock->shouldReceive('save')
    ->once()
    ->with(\Mockery::on(function ($argument) use ($postId) {
        $postIdIsSet = isset($argument['post_id']) && $argument['post_id'] === $postId;
        $publishedFlagIsSet = isset($argument['published']) && $argument['published'] === 1;
        $publishedAtIsSet = isset($argument['published_at']);

        return $postIdIsSet && $publishedFlagIsSet && $publishedAtIsSet;
    }));

$service = new \Service\Post($modelMock);
$service->publishPost($postId);

\Mockery::close();

The important part of the example is inside the closure we pass to the \Mockery::on() matcher. The $argument is actually the $saveData argument the save() method gets when it is called. We check for a couple of things in this argument:

  • the post ID is set, and is same as the post ID we passed in to the publishPost() method,
  • the published flag is set, and is 1, and
  • the published_at key is present.

If any of these requirements is not satisfied, the closure will return false, the method call expectation will not be met, and Mockery will throw a NoMatchingExpectationException.

Happy hackin’!

Tags: arguments, matching, mockery, mocking, php, testing.
Categories: Development, Programming, Software.

Open source taught me how to work with legacy code

by Robert Basic on April 28, 2017.

Contributing to open source projects has many benefits — you learn and you teach, you can make friends or find business partners, you might get a chance to travel. Even have a keynote at a conference, like Gary did.

Contributing to open source projects was the best decision I made in my professional career. Just because I contributed to, and blogged about Zend Framework, I ended up working and consulting for a company for four and a half years. I learned a lot during that time.

What I realized just recently is that open source also taught me how to work with legacy code. It taught me how to find my way around an unknown codebase faster, where to look and what to look for when investigating an issue. Most importantly, it taught me how to react to legacy code.

Usually when people hear “legacy code”, they think code that was written by a bunch of code monkeys who know nothing about writing good software. The past was stupid, the present is smart and wise, and will make everything better for the future. A long time ago, I was the same.

Today, my thinking and my approach is completely different.

I have the utmost respect for the programmer and their code that is before me. Rarely do I have the privilege knowing the circumstances under which a piece of legacy code was written.

In many cases the original author of the code is not on the team any more, or they just don’t remember why was some decision made and a piece of code written in a certain way. It might be a hack workaround for a code that was written by someone even before their time on the project. Maybe they didn’t know better at the time, or maybe they indeed made an error and now it’s my bug to fix.

Whatever the reason is, the code is written, used, and it delivers business value. It requires maintenance, fixes, and improvements and I welcome the challenges it brings.

Happy hackin’!

Tags: code, legacy, maintenance, open source.
Categories: Development, Programming, Software.

Read-only Symfony form field

by Robert Basic on April 10, 2017.

The future me will be grateful for this post. I always get it wrong the first time.

To set a Symfony form field as a read-only, we can’t set the readonly attribute as an option on that field:

src/AppBundle/Form/FooType.php

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class FooType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('title', TextType::class, [
                'readonly' => true,
            ]);
    }
}

This won’t work, and will give the The option "readonly" does not exist. Defined options are:... exception.

We need to set it as an attribute of that field:

src/AppBundle/Form/FooType.php

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class FooType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('title', TextType::class, [
                'attr' => ['readonly' => true],
            ]);
    }
}

Happy hackin’!

Tags: form, php, symfony.
Categories: Development, Programming.

Waste an hour on a stupid mistake

by Robert Basic on April 07, 2017.

I made such a stupid mistake today and lost an hour of my time trying to figure out what the hell is wrong, that I just have to blog it.

I was working on embedding a Symfony form into another form, following the documentation to the letter… Yet there I was staring at this stupid error message:

The options "0", "1" do not exist. Defined options are: "action", "allow_add", "allow_delete",
"allow_extra_fields", "attr", "auto_initialize", "block_name", "by_reference", "compound",
"constraints", "csrf_field_name", "csrf_message", "csrf_protection", "csrf_token_id",
"csrf_token_manager", "data", "data_class", "delete_empty", "disabled", "empty_data",
"entry_options", "entry_type", "error_bubbling", "error_mapping", "extra_fields_message",
"help", "inherit_data", "invalid_message", "invalid_message_parameters", "label", "label_attr",
"label_format", "mapped", "method", "post_max_size_message", "property_path", "prototype",
"prototype_data", "prototype_name", "required", "translation_domain", "trim",
"upload_max_size_message", "validation_groups".

What the hell…

Excerpt of the code that was throwing the error:

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use AppBundle\Form\SampleType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class MultiSampleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('samples', CollectionType::class, [
                'entry_type', SampleType::class,
                'allow_add' => true,
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => null,
        ));
    }
}

I read the documentation over and over again, searched for any one else coming up with the same error, went through the issues on the Symfony repositories… Nothing.

Now when I look at this code sample the error is poking my eyes out, but this morning when I was looking for it… Crickets.

Let’s “zoom” in:

<?php
->add('samples', CollectionType::class, [
    'entry_type', SampleType::class,
    'allow_add' => true,
]);

Do you see it?

The error is that the entry_type should be a key, instead of a value, and SampleType::class is the value for the entry_type key.

<?php
->add('samples', CollectionType::class, [
    'entry_type' => SampleType::class,
    'allow_add' => true,
]);

So stupid.

Oh well, I guess this also part of programming.

Happy hackin’!

Tags: php, stupid.
Categories: Blablabla, Development, Programming.