Why protect tables and fields on top of every model? - codeigniter

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

Related

Laravel Model Define Parameters Won't Insert

I am currently moving over from symfony to laravel, it's quite a bit different when it comes to the database. So i have a basic model, i'm just going to use an example:
class Test extends Model
{
use HasFactory;
}
All good, i have a migration and the table created. However, i don't like this:
$test = new Test();
$test->my_field = 'hello';
$test->save();
I don't like it because it's having to use a magic __set() to create the parameter, if i define the parameter in my model like this:
class Test extends Model
{
use HasFactory;
public ?string $my_field;
}
I get database errors when it tries to insert when i define the params like this. Why is that? It's doing the same thing as __set() but i'm actually physically defining them, which in my opinion is a better way to code it as my IDE can typehint and it's just nicer to follow the program knowing what params are there.
What's the reason for it inserting when i don't define them, and not when i do? From my actual table which is bookings , has a field booking_ref:
General error: 1364 Field 'booking_ref' doesn't have a default value (SQL: insert into booking_reviews (updated_at, created_at) values (2021-12-13 14:13:08, 2021-12-13 14:13:08))
This happens when i define the $booking_ref param on the model, but if i take it out and rely on the __set() method it works fine. Doesn't make any sense to me right now.
I think this is a reasonable enough misunderstanding to be useful to future visitors, so I want to try to explain what's going on with some pseudo-code and some references to the current source code.
You are correct that when setting a property on a Laravel model, that is a column in the DB, internally Laravel is using the PHP magic method __set.
What this does is allow you to 1) set properties directly instead of calling some kind of setter function, and 2) interact with your table columns without needing the boilerplate of column definitions in your model.
Where the assumptions go wrong is with what __set is doing. __set does not have to simply set an actual property with the same name. __set is just a method you may implement to do whatever you want. What you assumption implies is that it's doing something like this:
public function __set($key, $value)
{
$this->{$key} = $value;
}
However, you can do whatever you want with the $key and $value passed to the magic method.
What Laravel does is call another method defined in the HasAttributes trait - setAttribute.
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
setAttribute does a few extra things, but most importantly it adds the key/value pair to Model property $this->attributes[].
To hopefully help this difference make sense, here is what the two __set methods would yield with a basic example:
$model->my_column = 'value';
// 1st example
/**
* {
* public $my_column = 'value';
* }
*/
// Laravel way
/**
* {
* protected $attributes= ['my_column => 'value'];
* }
*/
I won't go through both saving and updating since they're very similar, but to show how this is used, we can look at the save method, which calls performInsert and after a few more calls makes it's way back to the attributes property to determine what to actually insert into the query.
Summary
Laravel does not use custom model properties when deciding what column/values to add to queries.
This is why when you create custom mutators, you interact with the attributes property just like Laravel does internally.
Anytime you introduce "magic" into code, you have some tradeoffs. In this case, that tradeoff is slightly less clarity with what database columns are actually available. However, like I mentioned in comments, there are other solutions to make models more IDE friendly like Laravel IDE helper.

Laravel store data thru model

I dont know how else to label the title. Anyone have ideas go ahead and make a suggested edit.
I have a series of Models for the database in my application.
I can currently add a global scope to the model and have the model automatically add a "where" clause on my queries to the database on a key:value pair. This is working great.
class Customers extends Model
{
protected $table = 'customers';
protected static function boot() {
parent::boot();
static::addGlobalScope('companyRecordID', function (Builder $builder) {
$builder->where('companyRecordID', Auth::guard('user')->user()->companyRecordID);
});
}
}
I am having troubles trying to identify if this can be done; Id like to be able to store the "companyRecordID" from the Auth::guard('user')->user()->companyRecordID automatically when a database record is created. Similar to created_at and updated_at are created automatically without requiring code from the controller to define.
Can someone direct me to what I should be looking for. Ive spent a few hours trying to google key word pairs to find an answer with no avail.
If you are using Models to create the records you can listen for the creating event for the Model and then add any additional fields you may need. You can create a listener for the creating event on Customers:
Customers::creating(function ($customer) {
if (auth('user')->user()) {
$customer->companyRecordID = auth('user')->user()->companyRecordID;
}
});
You can throw that in a Service Provider's boot method or your Model's boot method. If you throw it in the Model's boot method you may want to adjust to using static:: instead of Customers::.
I believe you got your answer but,
On your model please use protected $fillable
protected $fillable = ['','',''];
protected $table = 'customers';
also, you can use Relationships to optimize your codes.

Disable eager relations

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();

Cakephp: generic validation rules in AppModel?

I'm wondering what's the "best" approach to validate fields generically. In my application several tables have date values that are always entered using a date picker widget. I don't want to repeat the validation code, so I would like to do something like filling the $validate array in the AppModel. But it gets overwritten in the concrete model class. The best I found so far is the paragraph "Dynamically change validation rules" in the cake book, and apply that logic to the AppModel somehow, but it looks a bit hacky and un-caky. Does anyone have a hint?
(If you have questions, please ask.)
Thanks
Just name them differently - unique so to speak:
public function validateDateTime() {}
etc. This way your custom rules don't verwrite the core rules and vica versa.
I had some validation rules that I wanted to put in 3 models, to not repeat the same code, here what I did
in AppModel.php, define some var with those rules that should be in multiple models.
public $validationRules = arra(
// rules here
);
and add them for necessary models in AppModel's constructor
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
/**
* add validation
*/
if (in_array($this->alias, array('MyModel1', 'MyModel2', 'MyModel3')) ) {
$this->validate = array_merge($this->validate, $this->validationRules);
}
}
if there are some custom validation functions, those can be moved to AppModel.php as well.

Zend Framework Model

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/

Resources