Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] Validated subsets #38366

Merged
merged 14 commits into from
Aug 13, 2021
11 changes: 11 additions & 0 deletions src/Illuminate/Contracts/Support/ValidatedData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Illuminate\Contracts\Support;

use ArrayAccess;
use IteratorAggregate;

interface ValidatedData extends Arrayable, ArrayAccess, IteratorAggregate
{
//
}
11 changes: 11 additions & 0 deletions src/Illuminate/Foundation/Http/FormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\ValidatedInput;
use Illuminate\Validation\ValidatesWhenResolvedTrait;
use Illuminate\Validation\ValidationException;

Expand Down Expand Up @@ -185,6 +186,16 @@ protected function failedAuthorization()
throw new AuthorizationException;
}

/**
* Get a validated input container for the validated input.
*
* @return \Illuminate\Support\ValidatedInput
*/
public function safe()
{
return new ValidatedInput($this->validated());
Copy link
Contributor

@arcanedev-maroc arcanedev-maroc Aug 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this instead:

return $this->validator->safe();

And you can remove the use statement after.

}

/**
* Get the validated data from the request.
*
Expand Down
207 changes: 207 additions & 0 deletions src/Illuminate/Support/ValidatedInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<?php

namespace Illuminate\Support;

use ArrayIterator;
use Illuminate\Contracts\Support\ValidatedData;
use stdClass;

class ValidatedInput implements ValidatedData
Copy link
Member

@JosephSilber JosephSilber Aug 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason this isn't an actual collection?

(The only difference is the only method working on nested data, which we could easily add to the collection's method, or just override it here)

{
/**
* The underlying input.
*
* @var array
*/
protected $input;

/**
* Create a new validated input container.
*
* @param array $input
* @return void
*/
public function __construct(array $input)
{
$this->input = $input;
}

/**
* Get a subset containing the provided keys with values from the input data.
*
* @param array|mixed $keys
* @return array
*/
public function only($keys)
{
$results = [];

$input = $this->input;

$placeholder = new stdClass;

foreach (is_array($keys) ? $keys : func_get_args() as $key) {
$value = data_get($input, $key, $placeholder);

if ($value !== $placeholder) {
Arr::set($results, $key, $value);
}
}

return $results;
Comment on lines +37 to +51
Copy link
Member

@timacdonald timacdonald Aug 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we aren't just hitting the Arr::only(), Arr::* method on all of these?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that Request's InteractsWithInput had it's own implementation that I presume has some slightly different behavior than a plain Arr::only. Therefore, I just kept the implementation matching. I haven't dug into what the subtle differences between the two would be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one difference that I can think of is passing * would return different result for both @timacdonald @taylorotwell

}

/**
* Get all of the input except for a specified array of items.
*
* @param array|mixed $keys
* @return array
*/
public function except($keys)
{
$keys = is_array($keys) ? $keys : func_get_args();

$results = $this->input;

Arr::forget($results, $keys);

return $results;
}

/**
* Get the input as a collection.
*
* @return \Illuminate\Support\Collection
*/
public function collect()
{
return new Collection($this->input);
}
Comment on lines +76 to +79
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryangjchandler will appreciate this one

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is beautiful.


/**
* Get the raw, underlying input array.
*
* @return array
*/
public function all()
{
return $this->input;
}

/**
* Get the instance as an array.
*
* @return array
*/
public function toArray()
{
return $this->all();
}

/**
* Dynamically access input data.
*
* @param string $name
* @return mixed
*/
public function __get($name)
{
return $this->input[$name];
}

/**
* Dynamically set input data.
*
* @param string $name
* @param mixed $value
* @return mixed
*/
public function __set($name, $value)
{
$this->input[$name] = $value;
}

/**
* Determine if an input key is set.
*
* @return bool
*/
public function __isset($name)
{
return isset($this->input[$name]);
}

/**
* Remove an input key.
*
* @param string $name
* @return void
*/
public function __unset($name)
{
unset($this->input[$name]);
}

/**
* Determine if an item exists at an offset.
*
* @param mixed $key
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($key)
{
return isset($this->input[$key]);
}

/**
* Get an item at a given offset.
*
* @param mixed $key
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($key)
{
return $this->input[$key];
}

/**
* Set the item at a given offset.
*
* @param mixed $key
* @param mixed $value
* @return void
*/
#[\ReturnTypeWillChange]
taylorotwell marked this conversation as resolved.
Show resolved Hide resolved
public function offsetSet($key, $value)
Copy link
Contributor

@tpetry tpetry Aug 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't an immutable object make sense? A stict guarantee that ValidatedData is the unmodified result of a validator would sometimes be usefull i guess. What i mean is, changing a validated input may make it not the valid anymore.

{
if (is_null($key)) {
$this->input[] = $value;
} else {
$this->input[$key] = $value;
}
}

/**
* Unset the item at a given offset.
*
* @param string $key
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($key)
{
unset($this->input[$key]);
}

/**
* Get an iterator for the input.
*
* @return \ArrayIterator
*/
taylorotwell marked this conversation as resolved.
Show resolved Hide resolved
public function getIterator()
{
return new ArrayIterator($this->input);
}
}
11 changes: 11 additions & 0 deletions src/Illuminate/Validation/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Illuminate\Support\Fluent;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Str;
use Illuminate\Support\ValidatedInput;
use RuntimeException;
use stdClass;
use Symfony\Component\HttpFoundation\File\UploadedFile;
Expand Down Expand Up @@ -500,6 +501,16 @@ public function validateWithBag(string $errorBag)
}
}

/**
* Get a validated input container for the validated input.
*
* @return \Illuminate\Support\ValidatedInput
*/
public function safe()
{
return new ValidatedInput($this->validated());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have a question ... may i know why not bind the interface to the concrete ValidatedInput instance inside ValidationServiceProvider ... and here call app(ValidatedData::class, ['input' => $thsi->validated()])

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's nice! And also changing the return type to the interface. I mean, we don't need the interface if the implementation is hardcoded to a specific class.

}

/**
* Get the attributes and values that were validated.
*
Expand Down