Skip to content
This repository has been archived by the owner on Jul 16, 2021. It is now read-only.

Boolean validation does not accept "true" and "false", but accepts "1", "0" #514

Closed
jfadich opened this issue Apr 7, 2017 · 24 comments
Closed

Comments

@jfadich
Copy link

jfadich commented Apr 7, 2017

I ran into a problem where boolean validation for GET parameters (?attribute=true) does not work. I went to write a pull request to allow string versions of "true" and "false" to be accepted but looking at the validator test it appears that this is expected behavior.

Why are string versions of 1 and 0 accepted as valid and not string version of true and false? This makes boolean validation pointless for get parameters since they always come in as strings. Since this appears to be intended behavior I'm wondering what the rational is behind the decision and if it can be changed.

@tomschlick
Copy link

Thats odd.. I would just submit your PR anyway. I can't think of a reason why those should not evaluate to their boolean values as PHP even makes the same switch internally.

@jfadich
Copy link
Author

jfadich commented Apr 7, 2017

I did some testing and I think the reasoning behind this may have to do with the way PHP does casting. (bool) "false" evaluates to true because it is a non empty string, even though the value looks like false

@laurencei
Copy link

laurencei commented Apr 7, 2017

I did the PR work around the boolean validation a while ago that added some stuff. So I looked into this at the time.

The problem is you cannot accept "false" as false - because as @jfadich said (bool) "false" === true

This means if you did $model->record = $request->attribute - your database will store 1 if your text is "false" even though you meant to store 0.

The best option is to change your GET method to use 1/0 instead of "true"/"false".

Another option is do some casting on the Request prior to validation.

@tomschlick
Copy link

We could use something like this

$bool = filter_var($value, FILTER_VALIDATE_BOOLEAN);

described here: https://stackoverflow.com/questions/4775294/parsing-a-string-into-a-boolean-value-in-php

@laurencei
Copy link

laurencei commented Apr 7, 2017

If casting is implemented into validation - then yes.

But at the moment Laravel does not cast during validation - so we cannot do this.

I think there was some discussion around it on internals somewhere?

@jfadich
Copy link
Author

jfadich commented May 3, 2017

I got around this by utilizing the base TransformsRequest middleware created for the TrimStrings middleware.

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\TransformsRequest;

class ConvertStringBooleans extends TransformsRequest
{
    protected function transform($key, $value)
    {
        if($value === 'true' || $value === 'TRUE')
            return true;

        if($value === 'false' || $value === 'FALSE')
            return false;

        return $value;
    }
}

@franzliedke
Copy link

You might want to consider on and off as boolean values as well, some browsers use these as default values for checkboxes IIRC.

@Garbee
Copy link

Garbee commented May 4, 2017

Only on with browsers. An unchecked checkbox is never submitted.

@apokryfos
Copy link

I think (for consistency) we should consider whether the boolean validation rule should match the built in boolean validation filter.

In my code I had:

$validator->extend("trueboolean", function ($attribute, $value, $parameters) {
     return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null;
});

I think it's just a matter of being consistent with the build in validation rules.

As @jfadich suggests I can see how the filter_var can be moved to a TransformsRequest middleware

@vpratfr
Copy link

vpratfr commented Mar 8, 2018

The accepted rule kind of does the same thing except that it restricts valid values to true (on, yes, true, 1).

I would also like a rule which allows to validate a checkbox.

For now, the workaround is to set the checkbox value to 1 and use the boolean rule.

@basvandorst
Copy link

You can also use the in validator to check whether a parameter is true or false:

$request->validate([
    'attribute' => 'in:true,false'
]);

@Xerotherm1c
Copy link

You can also use the in validator to check whether a parameter is true or false:

$request->validate([
    'attribute' => 'in:true,false'
]);

Will the "false" === true evaluation prove troublesome when using this method of boolean validation?

@mfn
Copy link

mfn commented Dec 25, 2018

Yes, it won't work. Validation does not change/coerce the input types.

So, depending on the "laxness" of the validation, you end up with true or "true" (or using the other examples) 0 or "0" and you would basically need a helper to correctly figure which of the both states now is it.

@mallardduck
Copy link

I think that the docs should be updated to make the 'boolean' validation rule more clear. The way it's phrased now, it isn't very obvious that boolean strings aren't acceptable to use.

While technically the validation rule does work with true/false the Middleware workaround posted by @jfadich is required for the rule to work as you would expect based on the docs.

@robjbrain
Copy link

I got around this by utilizing the base TransformsRequest middleware created for the TrimStrings middleware.

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\TransformsRequest;

class ConvertStringBooleans extends TransformsRequest
{
    protected function transform($key, $value)
    {
        if($value === 'true' || $value === 'TRUE')
            return true;

        if($value === 'false' || $value === 'FALSE')
            return false;

        return $value;
    }
}

Anyone reading this should be very careful using this.

Imagine an instance where someone adds a comment simply saying "true" (a common response in a chat room or forum perhaps?)

This middleware would convert the comment string to a boolean.

@cjaoude
Copy link

cjaoude commented May 9, 2019

Seems best to make a custom rule using filter_var. Then just...

eg. [‘required’, new Bool]

makes it clear what’s happening vs messing with middleware example above which might unintentionally cast a literal ‘true’ string.

@robjbrain
Copy link

Is it possible to overwrite the existing bool method so that ['required', 'bool'] will still work?

@jfadich
Copy link
Author

jfadich commented May 29, 2019

@robjbrain Keep in mind I was posting that as a suggestion, not production ready code. The transforms request middleware provides a means to pass attributes through so you can specify which fields should be converted

Route::post('comments', 'CommentsController@store`)->middleware('convert_string_bool:field1,field2')

Then in the middleware:

    protected function transform($key, $value)
    {
        if(!in_array($key, $this->attributes) {
             return $value;
        }

       ...
    }

@iraklisg
Copy link

iraklisg commented Jan 16, 2020

@jfadich $this->attributes seems to be undefined (on Laravel 5.6.x)

@jfadich
Copy link
Author

jfadich commented Jan 16, 2020

@iraklisg Looks like it was removed. laravel/framework@a4936b9. You can add it back in yourself in your custom middleware.

@NewEXE
Copy link

NewEXE commented Apr 16, 2020

For correct parsing you can write helper like this one

if (! function_exists('to_bool')) {
    /**
     * Returns TRUE for TRUE, "1", "true", "t", "on" and "yes". Returns FALSE otherwise.
     *
     * @param mixed $value
     * @return bool
     */
    function to_bool($value): bool
    {
        if (is_bool($value)) {
            return $value;
        }

        if ($value instanceof PotentiallyMissing && $value->isMissing()) {
            return false;
        }

        if (is_string($value) && Str::lower($value) === 't') {
            return true;
        }

        return filter_var($value, FILTER_VALIDATE_BOOLEAN);
    }
}

@oliverpool
Copy link

Since Laravel 6.x:

$availableForHire = Request::boolean('available_for_hire'); // true or false

Source: https://www.amitmerchant.com/convert-request-variable-parameters-laravel/

@lalithhakari
Copy link

I ran into a problem where boolean validation for GET parameters (?attribute=true) does not work. I went to write a pull request to allow string versions of "true" and "false" to be accepted but looking at the validator test it appears that this is expected behavior.

Why are string versions of 1 and 0 accepted as valid and not string version of true and false? This makes boolean validation pointless for get parameters since they always come in as strings. Since this appears to be intended behavior I'm wondering what the rational is behind the decision and if it can be changed.

because i think, php intrprets "1" or "0" as number..where "true" or "false" is not interpreted as a number. am i right?

@Livijn
Copy link

Livijn commented Apr 6, 2021

Here is a test for those implementing the middleware @jfadich provided.

<?php
namespace Tests\Feature;

use Route;
use Tests\TestCase;

class ConverStringBooleansTest extends TestCase
{
    /** @test It converts string booleans */
    public function it_converts_string_booleans()
    {
        Route::post('/_tests/booleans', function () {
            return request()->all();
        });

        $this->post('/_tests/booleans', ['val1' => 'true', 'val2' => 'TRUE', 'val3' => 'false', 'val4' => 'FALSE'])
            ->assertExactJson(['val1' => true, 'val2' => true, 'val3' => false, 'val4' => false]);
    }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests