is it posible to have a true Model Layer in PHP like in Ruby on Rails? With Zend Framework you can create a Model but this is a class. As I know you have to write all the logic by myself.
Solutions?
True, in Zend Framework you need to declare classes for the database tables you'd like to access. But the rest can be done implicitly, if the default behavious is sufficient. The following is a valid and functional model class (compare to http://framework.zend.com/manual/en/zend.db.table.html#zend.db.table.introduction):
class bugs extends Zend_Db_Table_Abstract
{
// table name matches class name
}
This should let you access the table named "bugs", e.g.:
$table = new bugs();
$data = array(
'created_on' => '2007-03-22',
'bug_description' => 'Something wrong',
'bug_status' => 'NEW'
);
$table->insert($data);
Again, the example was taken directly from the documentation mentioned above.
Or since 1.8.x there is a DataMapper pattern used for models (see quickstart in manual)
i wrote a script that might suite your needs.
http://code.google.com/p/zend-db-model-generator/
Related
For my tests (Laravel 6) I am trying to use factories with make(), in order to bypass the DB operations just creating a new instance of the model.
Now, I am having an headache to make things work with my one to many relationship.
A skeleton of my code just to have an overview:
class Fine extends Model {
// Other things here
public function articles()
{
return $this->hasMany('App\Model\Article');
}
}
where Article does not have any other relationships.
Now, I would like to create a Fine factory to create an instance in which I can parse $fine->articles, all without any database interaction.
Here's the basic Fine factory that works (without using articles):
$factory->define(Fine::class, function (Faker $faker) {
return [
"id" => 10,
Fine::FIELDONE => 'xxx',
Fine::FIELDTWO => 'yyy',
];
});
that I use with $fine = factory(Fine::class)->make(); .
Now I need to "prefill" articles. I have tried with hydrate() but it does not work.
I have tried with afterMaking() with different combinations of code:
$factory->afterMaking(Fine::class, function (Fine $fine, Faker $faker) {
$articles = factory(\App\Model\Article::class, 3)->make([\App\Model\Article::FINE_ID => $fine->id]);
// TEST WITH SAVEMANY: $fine->saveMany(factory(\App\Model\Article::class, 10)->make());
// SAME WITH CREATEMANY
// TEST WITH HYDRATE: $multa->articles()->hydrate([$article]);
// $multa->load('articles');
});
Just to make a point I have tried different roads but, I admit, I am a bit lost.
Basically I end up with the classical
Illuminate\Database\QueryException: SQLSTATE[42S02]: Base table or view not found
error, so I am still accessing the database.
Now, I am starting to think that factories are a proper choice only if at the end one is going to store data on the database (I am sure that saveMany() is going to work if I use create() and not make()).
Can you please give me an hint on how to accomplish this task? If factories are not the right way to do it I am more than open to change my choice, too.
Thanks in advance!
Lorenzo
You can set this information on a non existing model instance if you really have to:
$fine->setRelation(
'articles',
factory(\App\Model\Article::class, 3)->make()
)
It depends what you plan on doing with this model and its relationship since there are no IDs set so they are not really related in that way.
Is there a shorthand way to save a new record when using Yii2 ActiveRecord?
Here are some examples from the docs:
$customer = new Customer();
$customer->name = 'Qiang';
$customer->save();
and...
Yii::$app->db->createCommand()->insert('customer', ['name' => 'Sam'])->execute();
It would be great if there was a shorthand method like...
Customer::create(['name' => 'Qiang']);
...but I can't seem to find that in the docs. Wondering if I'm missing something. Or would I need to create my own custom ActiveRecord class?
Some ugly way of doing things
(new Customer(['name' => 'Qiang',]))->save();
If you need to return the model
($customer = new Customer(['name' => 'Qiang',]))->save();
".. Or would I need to create my own custom ActiveRecord class?"
Will be good solution for your case, however every time you generate a model from Gii, you'll need not to forget change parent class
Creating record is actually more complicated in real world, because save() does not guarantee that record was saved in database. For example validation may not pass, the record will not be saved, and save() return false. Depending on situation you may want to throw exception in this case, or not.
I suggest to create factory class and encapsulate all necessary conditions, exceptions handling or defaults there - in long term it will be easier to maintain.
$customer = Yii::$app->modelFactory->createCustomer(['name' => 'Qiang']);
In my project I have many Eloquent models that have eager relations configured in class like this:
protected $with = [ 'countries', 'roles' ];
But sometimes I need just old plain model without any relations. Can I somehow do:
Model::noRelations()->all()
Really don't wanna use query builder nor create another class just for few occasions.
If you have to set the $with property on your model rather than leaving it empty, you can manually override the relationships that need to be eager loaded like this:
Model::setEagerLoads([])->get();
Link to API for setEagerLoads
In addition to Thomas Kim answer.
If you anyway extend Eloquent\Model class and often need to strip off relations from model, this solution might suit you well.
Create scope in your default model class:
public function scopeNoEagerLoads($query){
return $query->setEagerLoads([]);
}
For any ORM, that extends that class you will be able to:
User::noEagerLoads()->all()
Just like the issues say
Model::without(['countries', 'roles' ])->all();
I have seen a CodeIgniter application where the coder always protected table and fields at the tp of every model. Why would you do this? and maybe also why would you protect primary key separate from other fields?
class Students_model extends MY_Model{
protected $table = 'students';
protected $primary_key = 'id';
protected $columns = array(
'student_code' => array('Code', 'trim|required'),
'student_name' => array('Name', 'trim|required'),
'country' => array('Country', 'trim|required'),
);
}
This is a really good question for understanding best practices in MVC (not just Codeigniter). There are actually a few different things going on here.
1) "protected" is prolly done out of habit. This protected is the php class keyword, and has nothing to do with CI or Models. You can read about it here: http://jp2.php.net/protected; if after reading you don't understand why it is used in this case, it's because there really isn't a reason to use it in this case.
2) The key to understanding the rest is "extends MY_Model". MY_Model is a base model object where all your normal CRUD functions and utilities are written; we then inherit from it. However, our MY_Model will have a function like get_all() which will say:
$this->db->from($this->table)->get();
for example. So, in our Students model we set $this->table = "students", then the above code translates to:
$this->db->from('students')->get();
but any other models can pass a different table name. So we are able to make very simple table specific models and share all the more complex logic via MY_Model
You can see that going back to #1, it is unlikely we'll ever inherit from Students_model, so protected isn't harming anything, but isn't terribly necessary
3) $columns in this case are the validation rules; whichever MY_Model being used here is also adding the validation functions to the MY_Model instead of putting on the controllers
I want to write tests for my data models that make sense and work
I've been playing trying to test models like a boss in Laravel (right now I'd say I'm struggling to hold down a position in middle management). I'm using Ardent for my data models. The article referenced above makes it look like Factory Muff is super awesome and would be really handy in facilitating the creation of mock objects.
However, as I dig deeper, I find that Factory Muff is very limited in the data it can provide. It's basically just random words and email addresses. Anything beyond that, it seems I have to write a static method into my data model to generate valid data for the mock object. That doesn't seem very useful, but I'm guessing I'm probably doing it wrong, with a complete misunderstanding of what Factory Muff is all about.
In the data model
Consider this data validation ruleset:
public static $rules = [
'property' => 'required|address',
'name' => 'required|name',
'email' => 'required|email',
'phone' => 'required|phone',
'dob' => 'required|date',
];
Factory Muff seems to be completely ill-suited to generate any of this data beyond the name and email address, unless I want to write a static method that generates data formatted in the correct fashion. This is what I think I have to do with Factory Muff to be able to create a mock object without getting validation errors:
public static $factory = [
'property' => 'call|makeStreetAddress',
'name' => 'string',
'email' => 'email',
'phone' => 'call|makePhone',
'dob' => 'date|makeDate',
];
public static function makeStreetAddress()
{
$faker = \Faker\Factory::create();
return $faker->streetAddress;
}
public static function makePhone()
{
$faker = \Faker\Factory::create();
return $faker->phoneNumber;
}
public static function makeDate()
{
$faker = \Faker\Factory::create();
return $faker->date;
}
This seems pretty verbose, particularly with 10 to 20 fields on a table. I also don't like calling \Faker\Factory::create() in every single static method call (I don't really have a concrete reason, I just don't like it; if you know of a workaround, or if my fear is unfounded, please let me know.)
In the database seeder
I've got database seeding scripts setup that use Faker to generate a bunch of garbage in the database for use in development and testing. It's super intuitive and easy-to-use. For example, this is the seeding script for the above data set.
$faker = \Faker\Factory::create();
$application = Application::create([
'property' => $faker->streetAddress,
'name' => $faker->name,
'email' => $faker->email,
'phone' => $faker->phoneNumber,
'dob' => $faker->dateTimeBetween('-60 years', '-18 years'),
]);
The more I think about what it is I'm doing here, the more I feel like I'm being redundant.
Questions
If I'm seeding the database with garbage data generated by Faker before running my tests, do I even need Factory Muff to create mock objects? Shouldn't I be able to adequately test the codebase using the seed data from the database? Why would I need to mock?
Am I missing the whole point of Faker Muff? Does it have any advantages? It seems to me like little more than a random word generator.
What am I doing wrong? I'm extremely interested in TDD, but it's so daunting. If you noticed any bad practices, or the lack of best practices in my code, please let me know.
I think you are mixing up two different concepts here.
First, the purpose of the data that you create with Faker (in your case, database seeding) is to simulate real life scenarios in your application. For example, if you are developing a blogging platform, you can use it to have some blog posts with tags, user comments and author comments.
When it comes to testing, you can use this in your functional or acceptance tests. For example, if you want to test something like test tag page shows posts tagged with X tag or test user can only delete his own comments, then you could take advantage of Faker data to have some previous posts, tags and comments to work with.
On the other hand, FactoryMuff allows you to quickly create an instance of any given model. Consider the scenario when you are unit testing the validation method for your Post model, so you would have to:
// 1. create new Post
// 2. fill it with data
// 3. try to validate
Having your database seeded is not going to be useful here, since you are going to be creating a new model, and FactoryMuff will do steps 1 and 2 for you. Also, keep in mind that when you are unit testing, you want to do it in isolation, so you shouldn't need to touch your database at all. Instead, you could mock your database object and return fake models and its possible relationships (FactoryMuff to the rescue again).
Finally, I think that maybe you are not seeing the advantages of FactoryMuff functionality because your application may be still small, but you will and won't mind writing a few static methods as your codebase –and your tests– grows.