Laravel Factories - Create or Make dynamic - database

I have simple question.
How could I write a Factory that let me defines relationships that use make() or create() depending on original call make() or create()?
It is my use case:
I have a simple factory
/** #var $factory Illuminate\Database\Eloquent\Factory */
$factory->define(App\User::class, function (Faker $faker) {
return [
'role_id' => factory(Role::class),
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => 'secret',
];
});
My problem is with that role_id. When using factory(Role::class), it will create a Role always! Writing in Database...
In my tests, when I write factory(User::class)->create(); It will create an User and Role, thats ok!
But if I write factory(User::class)->make(); It will still create a Role... I wont write in Database a Role when using make(), in that case, I just want a simple role_id => 0.
How could I achieve that?
Thanks!
Edit (Workaround)
Well, it was harder than expected, there is no way to know when using make() or create() when your are defining nested relationships... The only way I found is using debug_stacktrace() method, and look for that make method called from Factory.
For simplification and reusability, I created a helper method for Factories called associateTo($class), this method will define relationships in Factory. It will return an existing ID from related model when using create() or 1 when using make().
By this way, Factories can be:
/** #var $factory Illuminate\Database\Eloquent\Factory */
$factory->define(App\User::class, function (Faker $faker) {
return [
'role_id' => associateTo(Role::class), // Defining relationship
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => 'secret',
'remember_token' => Str::random(10),
];
});
The helper function is:
/**
* Helper that returns an ID of a specified Model (by class name).
* If there are any Model, it will grab one randomly, if not, it will create a new one using it's Factory
* When using make() it wont write in Database, instead, it will return a 1
*
* #param string $modelClass
* #param array $attributes
* #param bool $forceNew
* #return int
*/
function associateTo(string $modelClass, array $attributes = [])
{
$isMaking = collect(debug_backtrace())
// A function called 'make()'
->filter(fn ($item) => isset($item['function']) && $item['function'] === 'make')
// file where I have global functions
->filter(fn ($item) => isset($item['file']) && Str::endsWith($item['file'], 'functions.php'))
->count();
if ($isMaking) return 1;
/** #var Model $model */
$model = resolve($modelClass);
return optional($model::inRandomOrder()->first())->id
?? create($modelClass, $attributes)->id; // call to Factory
}
With this, I can write Unit test easily (using Factories) without the need of using Database connections, so Unit Tests becomes superfast!
I hope this help someone else!

You can overwrite attribute role_id:
factory(User::class)->make(['role_id' => 0]);
Another solution - helper method for any attribute with _id ending:
function make($class)
{
$attributes = \Schema::getColumnListing((new $class)->getTable());
$exclude_attribures = [];
foreach ($attributes as $attribute)
{
if(ends_with($attribute, '_id'))
{
$exclude_attribures[] = [$attribute => 0];
}
}
return factory($class)->make($exclude_attribures);
}
Call example:
make(User:class);

You can take advantage of states
/** #var $factory Illuminate\Database\Eloquent\Factory */
$factory->define(App\User::class, function (Faker $faker) {
return [
'role_id' => factory(Role::class),
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => 'secret',
];
});
$factory->state(App\User::class, 'withoutRelationship', [
'role_id' => null,
'another_id' => null,
]);
Then
factory(User::class)->state('withoutRelationship')->make();

Related

Error Trying To Get Property Of Non-Object In Laravel 5.6

so I am trying to loop through an array of objects to update certain values in my database with the values from the object
When I run this loop in my controller
foreach($statuses as $status){
$workflow->statuses()->where('id', $status->id)->update([
'status' => $status->status
]);
};
It gives me error trying to get property of non-object
When I do a `return response($request->statuses) this is the data structure I see in the console
here is the complete controller
public function workflowStatuses(Request $request, Workflow $workflow)
{
// validate form data
$data = $request->validate([
'workflow' => 'required|string',
]);
// validate form data
$oldStatuses = $request->validate([
'statuses' => 'required|array'
]);
// validate form data
$newStatuses = $request->validate([
'newStatuses' => 'required|array',
]);
$workflow->update($data);
$statuses = $oldStatuses;
foreach($statuses as $status){
$workflow->statuses()->where('id', $status['id'])->update([
'status' => $status->status
]);
};
$workflow->statuses()->saveMany($newStatuses);
return response($workflow, 200);
}
You can think of the return value of $request->validate() as the array of all request input filtered to only include the data that's being validated. If that validated data contains arrays, you'll have a multi-dimensional array.
Here, $oldStatuses is going to be an array that contains a key named statuses that contains the actual array you're looking for.
$oldStatuses = $request->validate([
'statuses' => 'required|array'
]);
// $statuses should get 'statuses' out of this validated array
$statuses = $oldStatuses['statuses'];
Instead, you may want to clean this up and not call validate three times. It's usually better to run all the validation rules in one validate() call, unless you have good reason to logically separate them.
$validated = $request->validate([
'workflow' => 'required|string',
'statuses' => 'required|array',
'newStatuses' => 'required|array',
]);
$statuses = $validated['statuses'];
Try:
foreach($statuses as $status){
$workflow->statuses()->where('id', $status['id'])->update([
'status' => $status['status'] //$status['status'] should also be accessed by key
]);
};

Symfony 2 override entity field property

I want to override the entity field property. I need to get data from another database table (mapped by id). It should be a combination of "artikelnummer" and a field called "name" from another database table.
$builder->add('schlauch', 'entity', array(
'class' => 'SchlauchBundle:Artikelspezifikation',
'property' => 'artikelnummer',
'attr' => array(
'class' => 'extended-select'
),
'data_class' => null
));
The field "artikelnummer" outputs something like "12345" but I need to add the name (from another database table called "schlauch"), so it should look like "12345 Articlename". I tried a query in the entity file, but I dont't want to manipulate the output everywhere.
Is it possible to use a query for property and override it?
You can simple solve that by adding new getter to you entity:
class Artikelspezifikation
{
//…
/**
* #var Schlauch
*
* #ORM\ManyToOne(targetEntity="Schlauch", inversedBy="artikelspezifikations")
*/
private $schlauch;
//…
/**
* Get display name
*
* #return string
*/
public function getDisplayName()
{
return $this->artikelnummer . ' ' . $this->schlauch->getArtikelName();
}
//…
/**
* Set schlauch
*
* #param \SchlauchBundle\Entity\Schlauch $schlauch
*
* #return Artikelspezifikation
*/
public function setCategory(\SchlauchBundle\Entity\Schlauch $schlauch = null)
{
$this->schlauch = $schlauch;
return $this;
}
/**
* Get schlauch
*
* #return \SchlauchBundle\Entity\Schlauch
*/
public function getCategory()
{
return $this->schlauch;
}
}
And in your form class just change property:
$builder->add('schlauch', 'entity', array(
'class' => 'SchlauchBundle:Artikelspezifikation',
'property' => 'displayName',
'attr' => array(
'class' => 'extended-select'
),
'data_class' => null
));

CakePHP 3.x: saving multiple records

I've read Converting Multiple Records. Now I'm trying to save multiple photos at once from a form.
With:
debug($this->request->data);
I've this:
[
(int) 1 => [
'filename' => '25483_106728809362869_5795827_n.jpg',
'description' => '',
'album_id' => '2'
],
(int) 3 => [
'filename' => '44569_193398817463220_816845208_n.jpg',
'description' => '',
'album_id' => '1'
]
]
It seems ok.
Bake has created for me this action method:
public function add() {
$photo = $this->Photos->newEntity();
if($this->request->is('post')) {
$photo = $this->Photos->patchEntity($photo, $this->request->data);
if($this->Photos->save($photo)) {
return $this->redirect(['action' => 'index']);
}
}
$this->set(compact('photo'));
}
But the CakeBook doesn't explain well how to proceed. I sense I have to use newEntities() and patchEntities(), but I don't quite understand how to do.
For example: why the newEntity() method can accept NULL, while the method newEntities() necessarily wants an argument??
The save() method accepts only one entity at a time? So, I have to cycle saving for each entity?
Can I have a small example? Thanks.
Assuming your data is in the correct format, it should be as simple as this:
$photos = $this->Photos->newEntities($this->request->data());
foreach ($photos as $photo) {
$this->Photos->save($photo);
}
newEntity() can accept a null because calling newEntity with no data creates a blank entity that you can add data to, in case you don't want to pass in request data. For example:
$photo = $this->Photos->newEntity();
$photo->description = 'Cool!';
$photo->filename = 'example.jpg';
$this->Photos->save($photo);
newEntities(), however, expects multiple data or at least an array of data if you want to make many entities.
Using saveMany:
In some occasions it would be even better using saveMany which don't need foreach loop anymore.
$entities = $this->Photos->newEntities($this->request->data());
if($this->Photos->saveMany($entities)) {
// saved
} else {
// error
}

Zend Framework 2 Db Validator Zend\Validate\Db\NoRecordExists - I just can't make things work for me

I have been searching all over, found a few "solutions" to this, some even made me re-write most of my InputFilter and add a lot of stuff to my Module.php and/or module.config.php... With no luck whatsoever... Just couldn't make it work for me, still got all sort of errors.
I decided to undo everything and start from scratch (the way my code initially looked, before getting to validate form entries against the db) and ask my question here.
I am doing a registration process.
Of course, I need to validate the email address against existing records in my users table (no 2 identical email addresses should be allowed).
Sure, in my database I have that column set to only accept unique values... but I also have to validate it and give the user the appropriate message on form submit, before I actually do anything with the database.
How do I use Db\NoRecordExists (or any other Db validator for that matter)?
What should I further write in my code (add/edit)?
I've pasted all my code below.
The form element I need to add the Db\NoRecordExists validator is 'user_identifier'.
This is my /config/autoload/global.php :
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=my_database_name;host=localhost',
'username' => 'my_user',
'password' => 'my_password',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
);
This is my Registration form (/module/User/src/User/Form/RegisterForm.php) :
namespace User\Form;
use Zend\Form\Form;
class RegisterForm extends Form {
public function __construct() {
parent::__construct('register');
$this->setHydrator(new \Zend\Stdlib\Hydrator\Reflection());
$this->setObject(new \User\Entity\Users());
$this->setAttributes(array(
// not important for my issue
));
$this->setInputFilter(new \User\Form\RegisterFilter);
// User Identifier
$identifier = new \Zend\Form\Element\Email();
$identifier->setName('user_identifier');
$identifier->setAttributes(array(
'id' => 'user-email',
'placeholder' => 'Email',
'class' => 'form-control'
));
$identifier->setLabel('Your email:');
/*
* Many other fields were here, but to make the sample code
* shorter here, I've only left one of the fields I need to
* validate against the database
*/
$this->add($identifier); // User's email - used for login
// Submit
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Register',
'class' => 'btn btn-primary',
),
));
}
}
And here is my RegisterFilter (/module/User/src/User/Form/RegisterFilter.php) :
namespace User\Form;
use Zend\Form\Form;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
use Zend\Db\Adapter\Adapter;
class RegisterFilter extends InputFilter {
public function __construct() {
// User Identifier (Email)
$this->add(array(
'name' => 'user_identifier',
'required' => true,
'filters' => array(
array('name' => 'StringTrim'),
),
'validators' => array(
/*
* Some validators here (NotEmpty, EmailAddress)
*/
array(
'name' => 'Db\NoRecordExists',
'options' => array(
'table' => 'users',
'field' => 'user_identifier',
/*
* 'adapter' => had many examples for what to put here, like:
* \Zend\Db\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter()
* and for that I also had to put:
'Zend\Db\Adapter\Adapter' => function ($sm) {
$adapterFactory = new Zend\Db\Adapter\AdapterServiceFactory();
$adapter = $adapterFactory->createService($sm);
\Zend\Db\TableGateway\Feature\GlobalAdapterFeature::setStaticAdapter($adapter);
return $adapter;
}
* in my /config/autoload/global.php (and can't remember anything else) BUT, while
* following the example to the letter, I still got errors (like "no static adapter blah-blah - can't remember) and didn't work
* and so on... followed quite a few different examples/methods, rewrote/added many lines in my code
* (in the Model and/or Controller and/or Module.php) but still couldn't make things work for me.
*/
),
),
),
));
/*
* Filters and validators for the rest of the form elements here
* Removed them so I would keep the code focused on my question
*/
}
}
Here's my User module's Module.php (/module/User/Module.php) :
namespace User;
use User\Entity\Users;
use User\Entity\UsersTable;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
class Module {
public function getAutoloaderConfig() {
// ...
}
public function getConfig() {
// ...
}
public function getViewHelperConfig() {
// ...
}
public function getServiceConfig() {
return array(
'factories' => array(
'User\Entity\UsersTable' => function($sm) {
$tableGateway = $sm->get('UsersTableGateway');
$table = new UsersTable($tableGateway);
return $table;
},
'UsersTableGateway' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Users());
return new TableGateway('users', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
}
Here is my model (/module/User/src/User/Entity/Users.php):
namespace User\Entity;
class Users {
public $user_id;
public $other_id;
public $user_identifier;
public $user_credential;
public $user_type;
public $user_alias;
public $active;
public $enabled;
public $token;
public $created;
public function exchangeArray($data) {
$this->user_id = (isset($data['user_id'])) ? $data['user_id'] : null;
$this->other_id = (isset($data['other_id'])) ? $data['other_id'] : 0;
$this->user_identifier = (isset($data['user_identifier'])) ? $data['user_identifier'] : 'what?';
$this->user_credential = (isset($data['user_credential'])) ? md5($data['user_credential']) : 'not-possible';
$this->user_type = (isset($data['user_type'])) ? $data['user_type'] : 'client';
$this->user_alias = (isset($data['user_alias'])) ? $data['user_alias'] : 'Anonymus';
$this->active = (isset($data['active'])) ? $data['active'] : 0;
$this->enabled = (isset($data['enabled'])) ? $data['enabled'] : 1;
$this->token = (isset($data['token'])) ? $data['token'] : 'no-token';
$this->created = (isset($data['created'])) ? $data['created'] : date('Y-m-d h:m:s', time());
}
public function getArrayCopy() {
return get_object_vars($this);
}
}
and the TableGateway (/module/User/src/User/Entity/UsersTable.php) :
namespace User\Entity;
use Zend\Db\TableGateway\TableGateway;
class UsersTable {
protected $tableGateway;
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
}
/*
* fetchAll(), getUserById(), deleteUser() etc.
* Different methods here...
*/
public function saveUser(Users $user) {
$data = array(
'user_id' => $user->user_id,
'other_id' => $user->other_id,
'user_identifier' => $user->user_identifier,
'user_credential' => $user->user_credential,
'user_type' => $user->user_type,
'user_alias' => $user->user_alias,
'active' => $user->active,
'enabled' => $user->enabled,
'token' => $user->token,
'created' => $user->created
);
$user_id = (int)$user->user_id;
if ($user_id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getUser($user_id)) {
$this->tableGateway->update($data, array('user_id' => $user_id));
} else {
throw new \Exception("User with id {$user_id} does not exist");
}
}
}
}
And last, but not least, here's my controller (/module/User/src/User/Controller/IndexController.php) :
namespace User\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use User\Entity\Users;
class IndexController extends AbstractActionController {
public function registerAction() {
$registerForm = new \User\Form\RegisterForm;
if ($this->getRequest()->isPost()) {
// Form processing
$formData = $this->getRequest()->getPost();
$registerForm->setData($formData);
if ($registerForm->isValid()) {
// Insert into DB here
$user = new Users();
$user->exchangeArray($formData->user);
$this->getUsersTable()->saveUser($user);
}
return new ViewModel(array(
'form' => $registerForm,
));
} else {
return new ViewModel(array(
'form' => $registerForm,
));
}
}
/*
* Other methods go here
* login, logout, editAccount, emailConfirmation
* etc.
*/
public function getUsersTable() {
if (!$this->usersTable) {
$sm = $this->getServiceLocator();
$this->usersTable = $sm->get('User\Entity\UsersTable');
}
return $this->usersTable;
}
}
First of all I feel the need to shout here:
If in my /config/autoload/global.php I have set up my database connection, why on earth should I ever, in any other place in the application, speciffy anything (else) regarding the database, except maybe just the table I want to use?
Why do I have to mess around with setters and getters and factories and service manager and so on at the module level (sometimes even in the controller) and fatten the code so much? And as far as I can see from all sorts of examples (from which I got to write the code in getServiceConfig() in my Module.php), I need to do that for every entity. wtf? This really sucks! Big time!
Then, if I work with #n tables in a controller, I have to have #n functions like "public function getUsersTable() {}"? And #n factories like 'User\Entity\UsersTable' in my getServiceConfig() in Module.php? That's crap! Is this the best way to go? Or am I just unlucky and keep finding the worst examples possible while learning zf2? Why should there be a getTableNameTable() function in my controller? Isn't the Model that should worry about what table I'm talking about? Since every model is designed for one specific table? Like in zf1, where I would just have "protected $_name = 'users';" in my model and that was all I needed.
Why aren't those connection settings "magically" (simply) available ANYWHERE in my application, like in zf1? Why else am I putting that in the config for? I really don't understand why do I need all that in the getServiceConfig() in my Module.php and how could I avoid that?
Things seamed more compact in zf1. In zf2, most of the time I have no ideea whatsoever of what I'm doing, I copy snipets and prey on form submit or F5 that they work right out of the box and I get no errors. I should also probably mention that I don't have a strong understanding of OOP, I'm just a newb trying to learn zf2 (after using zf1 for 2 projects that had a lot to do with database, content administration, ajax, work with facebook api, google maps). With zf1, even if I bearly had an ideea about OOP, I still could do whatever I needed to do. In zf2, it seams there are 1000 ways right out of the box to do each one thing. Almost every time I'm looking for a solution to some problem I ran into, I find many examples... but most of the time, no example has the base code similar to mine to build upon, so I have to rewrite a lot (because I can't adapt, I'm a newb and if I adapt what I find, I immediately get errors, which I sometimes get even if I rewrite accordingly to the exampled I find).
So, what I am asking is:
In my code, pasted above, what should I add/modiffy in order to make that validation against the database? Because, right now, I get "No database adapter present" <em>sure I do</em>
This probably goes beyond the initial scope of this post, BUT how can I avoid so much code and configuration spreading all over the place? I don't want to have to speciffy anything regarding the database connection all over my application, it really should be done in one place and all other modules and entities and controllers should know all there is to know about the database (without telling them where to look in every controller/model, they should simply know that from the config), anything else they would need to know at Model(Entity)/Controller level should just be the table I want to work with, I shouldn't need to repeat myself and keep saying all over the place that "this is my adapter, there - get it from >here<" - that's crap) stop messing with getters and setters and factories and service manager and "make this from here available to that over there" etc. I shouldn't speciffy an adapter in my InputFilters or anywhere except the global/local.php. All that I should tell the validator in the input filter is the table and column to validate against, isn't this more natural?
I did this by passing the adapter to my interface:
Controller code:
if ($request->isPost()) {
$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
$admins = new Admins($dbAdapter);
.
.
.
}
My interface code:
class Admins implements InputFilterAwareInterface
{
public $id;
public $first_name;
public $last_name;
/* etc */
private $gatewayAdapter;
public function __construct($dbAdapter = null) {
$this->gatewayAdapter = $dbAdapter;
}
/* etc*/
public function getInputFilter() {
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
)));
/* etc */
$inputFilter->add($factory->createInput(array(
'name' => 'email',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array('name' => 'NotEmpty',),
array(
'name' => 'Db\NoRecordExists',
'options' => array(
'table' => 'admins',
'field' => 'email',
'adapter' => $this->gatewayAdapter
),
),
),
)));
/* etc */
}
/* etc */
}

Symfony 2 : Access database inside FormBuilder

I'm building a form which contains a category field. I need a choice list to do that, but I don't find out how to fill this choice list with the several categories stored in the database.
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('item', 'text', array('label' => 'Item'));
$builder->add('category', 'choice', array(
'choices' => ???,
'label' => 'Category'
));
}
How can I get the categories from the database?
(I can't seem to access $this->getDoctrine() inside this class.)
Use type entity instead of choice
$builder
->add('entity_property', 'entity', array(
'class' => 'Namespace\\To\\Entity',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('q')
->where('q.a_field = yourvalue');
}
));
Edit:
Two ways for using custom parameters in your query. In both situations, the parameters are injected from outside, so your FormType don't need any references to the session or request objects or whatever.
1- Pass required parameters to your constructor
class TaskType extends AbstractType
{
private $custom_value;
public function __construct($custom_value) {
$this->custom_value = $custom_value;
}
// ...
}
in your buildForm() you must copy the value to local variable and make it available for the query_builder callback:
public function buildForm(/*...*/) {
$my_custom_value = $this->custom_value;
// ...
'query_builder' => function(EntityRepository $repository) use ($my_custom_value) {
return $repository->createQueryBuilder('q')
->where('q.a_field = :my_custom_value')
->setParameter('my_custom_value', $my_custom_value);
}
// ...
}
2- use the $options parameter of the buildForm method.
First you have to define a default value by overriding getDefaultOptions:
public function getDefaultOptions(array $options)
{
return array(
'my_custom_value' => 'defaultvalue'
);
}
Then you can pass it from your controller in the third argument of the createForm method.
$this->createForm(new YourFormType(), $entity, array('my_custom_value' => 'custom_value'));
Now the value should be available through the $options parameter of youru buildForm method. Pass it to the callback as described above.
In Symfony 2.1
You now have to use the OptionsResolverInterface within the setDefaultOptions method. Here is the code you would have to use if you wanted to retrieve the options (using the same example as the accepted answer)
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function buildForm(FormBuilderInterface $builder, array $options){
parent::buildForm($builder, $options);
$my_custom_value = $options[custom_value];
// ...
'query_builder' => function(EntityRepository $repository) use ($my_custom_value) {
return $repository->createQueryBuilder('q')
->where('q.a_field = :my_custom_value')
->setParameter('my_custom_value', $my_custom_value);
}
// ...
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'my_custom_value' => 'defaultvalue'
));
}
You still pass the options in the same way:
$this->createForm(new YourFormType(), $entity, array('my_custom_value' => 'custom_value'));

Resources