Robert Basic's blog

Archive for the 'Programming' category

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.

Recording screencasts of OSS contributions

by Robert Basic on April 19, 2017.

I enjoy contributing to open source projects, and I learn a lot while doing it. When someone asks me for advice on how to improve as a programmer, I usually tell them to find an open source project that interests them, and start contributing.

Easier said than done.

I’ve been contributing since… early 2009 I think, when I joined the Zend Framework mailing list.

To try and bring closer contributing to beginners, I decided to start recording screencasts of me doing open source contributions. To give a glimpse of how I do it.

So far I have created 4 of them and uploaded on YouTube. The quality is not perfect, but I think it’s good enough. There’s no video editing, I want to show how I really do it, no fixing of mistakes, no retakes. I use zoom to start a “meeting” and then share and record the screen. It’s actually the best screencasting software for Fedora I’ve found, and it’s not even a screencasting software ¯\(ツ)/¯

While doing these screencasts I also realised that I quite enjoy doing this and the whole process has the added bonus of me actual doing rubber ducking, because, well, I talk all the time as I do things.

Also, potential clients and employers can get a peak at how I work.

Happy hackin’!

Tags: about, open source, php, screencast.
Categories: Blablabla, Programming.

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.