loading...

Laravel validation rule — encoded image

mattkingshott profile image Matt Kingshott 👨🏻‍💻 Originally published at itnext.io on ・3 min read

Image courtesy of Unsplash

Laravel validation rule — encoded image

In this new series, we’ll be exploring the concept of custom validation rules in Laravel and how they can assist you. I’m posting an article each day with a new rule you can use in your projects. The rules are also part of a package.

In this post, we’ll be creating a rule which confirms that a user has supplied a base64-encoded image file with a given MIME type. If you’ve ever used a front end JavaScript component to allow users to manipulate an image file, say an avatar, and then send it to the server, it’ll either be in this format or binary, so being able to validate that it is genuine is definitely worth doing!

If you’re new to this topic, check out the first article in the series.

Promotion

I’ve released Pulse — a friendly, affordable (from $5 a month ) server and site monitoring service designed specifically for developers. It includes the usual hardware monitors, custom service monitors, alerting via various notification channels, logs, and API support. Take a look… https://pulse.alphametric.co

Server and Site Monitoring with Pulse

The check and response

Validating an image is a bit more complex than our previous rules, so let’s begin by detailing the steps involved:

  1. Verify the supplied string begins with "data:image/{{mime}};base64,"
  2. Strip the prefix and write the remaining data to a temporary file.
  3. Create an instance of the Illuminate\Http\UploadedFile class and populate its parameters to point to the temporary file.
  4. Use Laravel’s native image validation rule to verify the file is genuine.
  5. Remove the temporary file.

In order for the rule to work, we need only supply a single parameter — the expected / required mime type of the file. Now that we know what should be happening, let’s take a look at the logic of the passes method:

public function passes($attribute, $value)
{
    if (! Str::startsWith($value, "data:image/{$this- 
                               >parameters[0]};base64,")) {
        return false;
    }

$result = validator(
        ['file' => $this->createTemporaryFile($value)], 
        ['file' => 'image']
    )->passes();

fclose($this->file);

return $result;
}

You’ll notice that we have a createTemporaryFile method, which handles the writing of the file to disk. Let’s take a look at the logic of this method:

protected function createTemporaryFile($data)
{
   $this->file = tmpfile();

fwrite($this->file, base64\_decode(Str::after($data, 'base64,')));

return new UploadedFile(
       stream\_get\_meta\_data($this->file)['uri'], 'image',
       'text/plain', null, null, true
   );
}

One important thing to remember is that we have to call fclose AFTER we run the validation, otherwise the file will be automatically deleted and the validation will fail since the image does not exist.

Next, we’ll need to write an error message to respond with when the user has supplied an invalid image or an image with an incorrect MIME:

public function message()
{
    return "The :attribute must be a valid {$this->parameters[0]} image";
}

Testing it works

As before, we’ll write a quick unit test to confirm that the rule works correctly and rejects any invalid images.

Note that I’ve created some helpers to make the actual validation process a little clearer. Check out thepackage file to see how they work.

/\*\* @test \*/
public function a\_encoded\_image\_can\_be\_validated()
{
    $png\_rule = ['image' => [new EncodedImage('png')]];
    $jpeg\_rule = ['image' => [new EncodedImage('jpeg')]];

$this->assertFalse(validator(['image' => $this->getFile('image.jpeg')], $png\_rule)->passes());

$this->assertTrue(validator(['image' => $this->getFile('image.png')], $png\_rule)->passes());

$this->assertTrue(validator(['image' => $this->getFile('image.jpeg')], $jpeg\_rule)->passes());

$this->assertFalse(validator(['image' => $this->getFile('image.png')], $jpeg\_rule)->passes());

$this->assertFalse(validator(['image' => $this->invalidImage('image.jpeg')], $jpeg\_rule)->passes());

$this->assertFalse(validator(['image' => $this->invalidImage('image.png')], $jpeg\_rule)->passes());

$this->assertFalse(validator(['image' => $this->invalidImage()], $jpeg\_rule)->passes());
}

Wrapping Up

You can see the complete class and pull it into your projects by visiting the repo on Github: https://github.com/alphametric/laravel-validation-rules

I have additional validation rules that I intend to share with you in the coming days, so be sure to follow me for those articles. If you’re interested, you can also follow me on Twitter to see everything I’m up to.

Happy coding!


Posted on Apr 8 '19 by:

mattkingshott profile

Matt Kingshott 👨🏻‍💻

@mattkingshott

Founder. Developer. Writer. Lunatic. Created Pulse, IodineJS, Axiom, and more. #PHP #Laravel #Vue #TailwindCSS

Discussion

markdown guide