Buzzwords! Specification & Examples

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

So if phpspec is all about helping you design your classes - helping you ask: how do I want this class to look and behave? - how... does it actually do that? The idea is cool: instead of jumping straight into your code and hacking until something works... or you get sleepy... stop... step back... and instead, first, describe how you want your class to behave.

We do that by creating a class called a... specification. That's a fancy... or maybe boring word that means that, before coding, we will first create a class where we simply describe how our future class will work and act.

Generating the Specification

Let's see this in action. Remember the two commands of the phpspec executable? The first is describe - run it with -h:

./vendor/bin/phpspec describe -h

I'm passing -h to see the help details. Basically, each time you want to create a new class, you should first use this command to create a corresponding specification class. Oh, notice that forward slashes are used for the namespaces, that's just to avoid escaping problems.

Anyways, because we're building a dinosaur park, the first class we need is... Dinosaur! So let's run:

./vendor/bin/phpspec describe App/Entity/Dinosaur

I could have chosen any namespace starting with App - that's up to how you want to organize your code. But, if you're used to Doctrine in Symfony, this will feel familiar.

What are Examples?

Ok! One new file: DinosaurSpec.php. Let's go check it out! Ok - so phpspec creates a spec/ directory, which is meant to have the same file structure that our classes will eventually have in src/.

Open the new file. Ok... these spec classes look a little weird at first - and we're going to talk a lot about them. The purpose of this class is for us to describe the behavior of our future Dinosaur class. On a philosophical level, we do this by writing example code: using our Dinosaur class as if it already existed and was finished.

... lines 1 - 8
class DinosaurSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(Dinosaur::class);
}
}

On a more concrete level: we describe the behavior through examples. Every function in this class that starts with it_ or its_ will be read by phpspec as an "example". They are the key to phpspec, and also the most complex part.

There are two very important things to understand about the code inside these example methods. First, and this is truly magic, you're supposed to use the $this variable as if we were inside of the Dinosuar class itself. Literally: you treat $this like a Dinosaur object - showing examples of how you want it to work by calling methods on that class - like $this->getLength() if the Dinosaur class had a getLength() method.

Hello Matchers

In addition to using $this to call methods that exist - or should exist - inside Dinosaur, the second important thing to know is that you can also call a huge number of methods that start with should or shouldNot. These are called "matchers" - and they are the way you assert that things are working correctly in phpspec.

In the one generated example function, because we're pretending to be inside the Dinosaur class, we pretend that $this is a Dinosaur object. When we call ->shouldHaveType(Dinosaur::class), this asserts that the object is an instance of that class... which, by the way, doesn't even exist yet! It's a pretty pointless test - but I usually keep it.

Oh, and the last strange thing about this class is that... it violates coding standards! Did you notice the missing public before the functions? That's totally legal in php - methods are public by default. And the method names are written using snake-case instead of camel case. Both of these things are done on purpose for one important reason: readability. We're writing PHP - but this class is meant to be a human-readable description of our future Dinosaur class. And right now, our specification says nothing more than a Dinosaur object should be... a Dinosaur object.

Generating the Class with run

Ready to execute the other phpspec command? It's called run - let's show the help details on this one too:

./vendor/bin/phpspec run -h

This is the main command in phpspec. Its job is to look at all of our spec files - just one right now - and all of the example methods inside - and verify whether or not the actual class behaves like we've described with that example code.

Now... you might think that's a bit crazy. After all, how can phpspec look to see if our Dinosaur class has the correct "behavior"? The Dinosaur class doesn't even exist yet! Heck, there's nothing in our src/ directory at all! Well... let's see what happens:

./vendor/bin/phpspec run

At first, it does fail because App\Entity\Dinosaur does not exist. That's expected. But check this out: it's asking: do you want me to create it for you? This is what makes phpspec so fun! When it sees that you've described some behavior that's missing, it can create it for you! Let's choose yes, of course!

... lines 1 - 4
class Dinosaur
{
}

Cool! Go look - in src/... there it is! It doesn't do anything, but... actually... our new class now has the behavior described in our spec. To prove it, re-run phpspec:

./vendor/bin/phpspec run

Woh! It works! That... does make sense. Even though we don't understand much about how the "examples" work yet, after generating the code, if you try to create an instance of a Dinosaur class..... you do get a Dinosaur object! Eureka!

Next: let's start creating some meaningful examples of how our class should behave and see how phpspec can help us build that.

Leave a comment!

  • 2019-12-13 Aydın Bulut

    Hi weaverryan

    Yes, I am using Eloquent with Slim in that application, thanks a lot for better googling then me :D I will take care of it, have a nice day :)

  • 2019-12-12 weaverryan

    Hey Aydın Bulut !

    Hmm. Indeed. Doing some quick googling, it looks like it could be from Eloquent - are you using Eloquent with Slim by chance? I don't know the fix exactly, but if you search for "Call to a member function connection() on null slim" or "Call to a member function connection() on null eloquent" you'll see references. What's odd is that everything is mocked in PHPSpec... so it "shouldn't" (with big quotes) matter what ORM or framework you're using. But... it seems something is going on here related (I think) to Eloquent.

    Cheers!

  • 2019-12-11 Aydın Bulut

    Thanks weaverryan for response.

    I don't have that method in my code and I don't have any idea about that. When I started with pure PHP & PHPSpec it works well, but when I tried to use PHPSpec on Slim Framework entities it failed. I thought it might be Slim related.

  • 2019-12-10 weaverryan

    Hey Aydın Bulut!

    Ah! I don't like that error at all! Do you have a connection() method anywhere in your code? I thought at first that this error might be some low-level error coming from phpspec. But, if I search the *entire* vendor/ directory of this project, I don't see any method called "connection()". So I'm wondering if this might be referring to something in your code?

    Cheers!

  • 2019-12-09 Aydın Bulut

    Hi!

    When I run the test, phpspec throws an error instead of suggesting me to create the non-exists method on real class.

    // my test method
    function it_shoult_return_5()
    {
    $this->getLength()->shouldBe(5);
    }

    // error
    App/Models/Student
    16 - it shoult return 5
    exception [err:Error("Call to a member function connection() on null")] has been thrown.

  • 2019-02-25 weaverryan

    I like it! Thanks for sharing :)

  • 2019-02-23 Benjamin Quarta

    Maybe i can offer a solution that is a bit more... well "handy". In my case this is a symfony-project.

    Go to your Project-Root and find the "bin" folder. Make a new file named phpspec and paste this chunk of code right in:

    ----------------------

    #!/usr/bin/env php
    < ? php // <-- please note, that i had to fill in whitespaces here, because disqus doesen't like my post otherwise

    if (!file_exists(dirname(__DIR__).'/vendor/phpspec/phpspec/bin/phpspec')) {
    echo "Unable to find the `phpspec` script in `vendor/phpspec/phpspec/bin/`.\n";
    exit(1);
    }

    require dirname(__DIR__).'/vendor/phpspec/phpspec/bin/phpspec';

    -----------------------

    Now you should be able to call phpspec simply by typing the command "php bin/phpspec run".
    Aaaand it feels more "natural" if you work with "php bin/console" and "php bin/phpunit" too :D

    Maybe this makes things more beautiful =)

  • 2019-01-14 Michał Wilczyński

    Great!

    Your solution also works! I changed slashes to backslahses and command

    .\.vendor\bin\phpspec

    works like a harm :D

  • 2019-01-14 weaverryan

    Hey Michał Wilczyński!

    Yes! Nice find! OR, use try this syntax:


    ./vendor/bin/phpspec

    (or you may need to change the slashes \.vendor\bin\phpspec). The phpspec is a "bat" file for Window, so instead of executing it via php, you're supposed to execute it directly. But, it's just kinda tricky. You'll notice that in the script we always use the ./vendor/bin/phpspec version of the command - it's a bit more portable across operating systems.

    Cheers!

  • 2019-01-14 Michał Wilczyński

    Ok, I have already resolved the problem. Instead of using

    php vendor/bin/phpspec describe -h

    I use:

    php vendor/phpspec/phpspec/bin/phpspec

  • 2019-01-14 Michał Wilczyński

    Hi!

    I have problem when I running command, on my win10

    php vendor/bin/phpspec describe -h

    Output is:

    dir=$(cd "${0%[/\\]*}" > /dev/null; cd "../phpspec/phpspec/bin" && pwd)

    if [ -d /proc/cygdrive ] && [[ $(which php) == $(readlink -n /proc/cygdrive)/* ]]; then
    # We are in Cgywin using Windows php, so the path must be translated
    dir=$(cygpath -m "$dir");
    fi

    "${dir}/phpspec" "$@"

    How can I resolve this problem?
    Thank you!

  • 2019-01-08 Victor Bocharsky

    Hey Nicoweb!

    Why do you think so? :) We're working on phpspec tutorial right now, that's why it's been releasing these days. The previous one: "Symfony 4 Forms: Build, Render & Conquer!" - was completely released recently :)

    P.S. But sure, releasing a few tutorials at the same time is our goal :)

    Cheers!