Symfony2: How do i validate an object/array with its constraints in another object/array? - validation

It's built dynamically, annotations are not an option. I have the constraints in an array.
It seems you need to manually construct the validator but I get stuck fast on the point where you need to construct a MetaDataFactory that needs an loader, but all the loaders have the job of loading meta data from a file.. I dont think I'm doing it right.
Simply said:
I have an array/object with keys/values. I want them validated by another array that contains the constraints.
It's not possible to iterate over the keys and call validate on each and every one of them, since some rely on the context values from other keys.
$array = [
'key1' => 'abc',
'key2' => 'def',
];
$constraints = [
'key1' => new All([
new Constraint..,
new CallbackConstraint.., // <- this one checks key2 for its value
]),
'key2' => new NotBlank
];
I can also build one array containing both.
Againt, the object/array is built dynamically, i cant validate the container itself. It's about the keys inside it that are definitions on its own.
So what i basically would want is:
$validator->validate($array, $constraints);
I can imagine you need a custom MetaDataFactory / Loader / Context class or something, the problem is simply that the callback validator needs to access $this->getRoot() to get to the other values
Nevermind that, you would need something like a CallbackLoader in which you create your own properties => constraints mapping. But the LoaderInterface requires a concrete implementation of ClassMetaData, which on his own has no way of dealing with arrays or ArrayObjects, unless you have methods/properties on that ArrayObject. But--since my container is built dynamically, i can't have that. Method methods are not an option because of the property/method_exists calls.

Related

Laravel create collection with named properties from scratch

My question is similar to this question Adding new property to Eloquent Collection, but I need some further explanation.
In many occasions it would be really useful to be able to construct your own collections and use Laravel collection methods. The docs only explain how to make a simple collection of nameless items (a 1 dimensional data set), but I'm interested in 2 dimensional data sets.
For example when you use Laravel validator, it takes your named array of inputs and spits out a Illuminate\Support\ValidatedInput object, which is a collection. Then you can access your properties as properties in any object and also use collection methods such as only() or except().
$safe = $request->safe(); //returns a Illuminate\Support\ValidatedInput collection
//$safe = $request->validate(); //returns an associative array
echo $safe->my_prop;
Model::create( $safe->only(['prop1', 'prop2']) );
My objective is to transform an associative array such as:
$a = [
'prop1' => 'val1',
'prop2' => 3656,
'prop3' => ['stuff', 'more']
];
Into a collection that can be used like the Illuminate\Support\ValidatedInput object.
PS I'm currently using arrays and array functions (like array_intersect_key() as a substitute to ->only()), but would rather use collections.
Using
$c = collect( $a );
you cannot access the properties of $c like $c->prop, you still need to use $c['prop']. Which is confusing, given that many objects in laravel are both objects (which allow $object->property) AND collections.

Has laravel 7 model a way to get list of all columns?

Has laravel 7 model a way to get list of all columns ?
I found and tried method
with(new ModelName)->columns
but it returns empty string.
I do not mean $fillable var of a model.
Thanks!
If you just want a reliable way to pull the list of attributes from any given instance no matter the state, and assuming the table structure isn't changing often, the path of least resistance might be to set a defaults attributes array to ensure the attributes are always present.
e.g.
class Fish extends Model
{
protected $attributes = [
'uuid' => null,
'fin_count' => null,
'first_name' => null,
];
}
$fishie = app(\Fish::class);
will then result in an instance of Fish with uuid, fin_count, and first_name set. You can then use $fishie->attributes or $fishie->getAttributes() to load the full set.
Assuming the structure doesn't change a lot, setting the attributes on the model like this will save you a database query every time you want to reference the list. The flip side is that instances change from not having the attributes unless explicitly defined to always being present, which may have implications in the project.
Here's the documentation for default attributes:
https://laravel.com/docs/master/eloquent#default-attribute-values

Seeding a test database in Laravel using factories and a deterministic seed

I have a number of factories generating models using faker, as recommended in the documentation:
$factory->define(App\Member::class, function (Faker $faker) {
return [
'name' => $faker->name()
];
});
I would like to use these factories in tests in order to seed the database into a known state. Faker (by its nature) is creating random data when I call factory(\App\Member::class, 10) which makes an assertion like $this->assertEquals('Eve', Member::find(5)->name) fail on future runs.
I note that Faker has a seed method to allow deterministic data generation:
$faker = Faker\Factory::create();
$faker->seed(1234);
However, with the factory(\App\Member::class, 10) interface there appears to be no way to set the seed of the Faker instance used in the factory.
Is there a way to set the Faker seed from the test case?
Or failing this what at the best practices for setting the database state in a test (I would like to use factories but maybe this is not the best approach)?
Faker is used with your factories in order to allow you to quickly create model instances without having to provide all the data yourself within your tests. It's much easier to simply do:
factory(User::class)->create();
...than it is to manually specify all of the fields that the User model requires. Faker provides random, sample data for all of the fields as specified in your factory definition.
When asserting in your tests, you shouldn't rely on knowing what that random data will be ahead of time. You can provide attributes yourself, which are merged into the random data defined in your factory and you can use this data to make your assertions.
A simple, trivial example:
$user = factory(User::class)->create(['name' => 'Joe Bloggs']);
$this->assertEquals('Joe Bloggs', $user->name);
The benefit of this is that you only need to provide the attributes you're interested in for your test and you can let your factory take care of providing the rest of the data.
You could of course allow your factory to provide the attributes and then use that information in the generated model(s) to check the data in your database. Something like this:
$user = factory(User::class)->create(['enabled' => false]);
$user->enableUser();
$this->seeInDatabase((new User)->getTable(), [
'id' => $user->id,
'name' => $user->name,
'enabled' => true
]);
In this example, the factory is used to create a user, providing the enabled attribute to be false. The method you're testing, in this case enableUser is run. Then you can use the ID and the name of the user generated by the factory in your where part of seeInDatabase, along with 'enabled' => true to ensure that the database was updated for the generated user, by setting the enabled field to true but leaving the generated name unchanged.

Apply several constraints in one "validate" pass in Silex

I'm using Silex, and trying to validate some value with validator service. But the problem is that I need apply several constraints to one value, but validator don't let to do this without using Required constraint.
When I just want to validate a choice value (say, 'apple' or 'orange') I have to wrap constraints Choice and NotNull (because Choice allows a null value) into Required and Collection (because Required cannot be used without Collection and it is CollectionValidator who validates series of Required's constraints) like this:
$violations = $app['validator']->validate(array('value'),
new Collection(array(
new Required(array(
new Choice(array('apple', 'orange')),
new NotNull()
))
)));
It looks verbose so I'm looking for more elegant solution (for such explicit use of validator).
You can use the validateValue function that accepts an array of constraints as second parameter.
$violations = $app['validator']->validateValue('value', array(
new Choice(array('apple', 'orange')),
new NotBlank()
));

Set values on Symfony2 validation with no form

I'm coding an API and I'm doing the create method. I'm doing the following without needing a form:
$params = array('title' => 'test', 'parent_id' => 781);
// bind data
$place = new Place();
$place->bind($params);
// validate params
$errors = $this->validator->validate($place);
I need to check that parent_id is a correct value (its object exist - i know how to do this) and after that, I need to set some values dependent on the parent. So at the end the Place object will have the fields: title, parent_id, level, country_id for example.
How would you do this? On the validation? How? If not, how to avoid calling two times the DB to get the parent object?
You should first validate & then set any additional values afterward. Anything that modifies the value does not belong in the validator.
If your using doctrine, it should load the parent object into memory when you first access it, so it won't need to actually query the database again when you access the parent object a second time.

Resources