Yii Make a model instance that cannot be saved - php

Let's say I have a Widget table and the Yii Model class that goes with it.
I want to be able to instantiate it ($tempWidget = new Widget) but somehow make sure it cannot be saved to the database. I want to use the model just for the user to test things, simulate...
Obviously, I could just avoid to call $tempWidget->save() but I'd like some kind of flag that would prevent save from saving, in case some other part of the code tries to do so.

There are a few ways to accomplish what you want. The easiest way is to modify the models beforeSave() method to prevent the model from being able to save by unsetting all the attributes using the CModel unsetAttributes method , example:
public function beforeSave(){
$this->attributes = $this->unsetAttributes();
}
This will work only if you have rules associated with this model that have required fields (at least one required field), otherwise this would create an entry in your table consisting only of the primary key (assuming PK is auto increment).

Related

Laravel Models - Grabbing Object's ID

I know that one can create an object from a model that represents a particular entry in the DB by using ModelName::find($model_id). What I'm curious about is how one goes about grabbing the current ID within the model on a function - so say that I wanted to expand the User model so that it will return all of the songs uploaded by said user.
I want to just be able to call $user = User::find($user_id) to grab the relevant user, and then call the function from within the model like so: $user->list_songs();. How do I, within this function in the model (public function list_songs()), check to see what the current id is? I'm currently passing it as a variable, and this seems counter-intuitive to the idea of a model.
Within the model you can simply use $this->id so long as the model has been saved to the database.

Laravel revisionable models with one-to-many relationship

Is it possible for revisionable to track changes to one-to-many relationships? For example:
Model: User.
Model: Posts. User model uses Venturecraft\Revisionable\RevisionableTrait; and have a hasMany relationship to Posts. If a post is added or updated, can this be tracked by revisionable under the User which the post belongs to?
Thanks in advance
I was able to come up with something. However it's not the most elegant solution, so it'd be great if somebody would help me to clean this up (especially that unset is bothering me).
My solution will create duplicates in the table the Model belongs to. I don't know if this is what you wanted
The first thing you need to do is to add a nullable datetime revision_at column in the appropriate table.
The trait itself is pretty simple. We make use of the models boot() method to register the models updating event. It will fire whenever a model is about to update. That's exactly what we need, since we don't want a revision the first time we are creating the model.
<?php
trait RevisionableTrait {
public static function boot()
{
parent::boot();
static::updating(function( $model ){
// Grab the original Model and unset the id
// from it, so that we don't get duplicate
// entries when we create a new model.
$attributes = $model->getOriginal();
unset( $attributes['id'] );
// Next we need to add the date the revision
// was created
$attributes['revision_at'] = new DateTime();
$revision = new static($attributes);
$revision->save();
});
}
}
The only thing we do here is to grab the original model before the new fields were assigned, unset the id to make sure we don't create a duplicate entry, set the current time for the revision_at field and save the model.
That's it basically.
can this be tracked by revisionable under the User which the post belongs to?
This is automatically done since the new revision model still belongs to the respective user,
If you want to fine-tune it you could create a dedicated table for revisions where a reference to the model is stored. However storing the properties might get a little bit harder (Maybe you could store them serialized).
Another possible improvement would be to modify the getter methods of the model in the trait. For example let all() only return models that are no revisions. Then add a withRevisions() method to grab them too. You can extract the logic from it if you take a look how Laravel handles Soft Deletes. It's exactly the same.
why not use this?
$user = User::first();
$user->posts->map(function($post) {
return $post->revisionHistory;
});

Managing objects with multiple contexts

Question:
Is there a preferred design pattern for handling an object under different contexts? For example: if I need to GET a user from the database then that object needs to have an id and a privilege level. However, if I want to create a NEW user for registration then it does not need an id since this will be added in the database via auto_increment. A GUEST user does not need an id but should have a username of 'Guest'.
Attempted Solutions
Using a constructor seems to neglect context.
Creating new objects for registered_user, new_user, and guest_user seems wasteful.
Creating methods within the user object that can be used to initialize it in unique ways is tempting.
As of now I believe that I should create a separate factory that has functions such as create_guest($x), create_new_user($x, $y), create_registered_user($x, $y, $z)? This would allow the user object to have one purpose: hold a user (while still allowing it to have a constructor to establish a bare minimum requirement), and also have a user factory with the sole purpose of: initiating different types of users.
Please excuse any ignorance, just looking for the best design pattern for handling the same object in unique contexts.
Example:
<?php
class user
{
private id;
private username;
private privilege;
public function __construct()
{
some code...
}
public function is_admin()
{
some code...
}
}
?>
Using the provided class, you can always autoinitialize the value for $this->id to be 0 and use it to determine if this user is a guest. Main idea is that you will never have an id of 0 in your database (if you are using auto-increment for this column).
This can also be used to check if you are to update or create the record. A simple check on the id would reveal either 0 or another int. If it is 0, then it is either guest or it should be created. If it is greater than 0, it has already been created and it needs to be updated.
Generally, I prefer to pack the create and update into the user class itself by adding load(), update(), create() and delete() methods, where load() would accept an array/object which if passed will be used to load the data into the current context or if not supplied, the function will try to read the data from a different source (such as a DB).

CakePHP: Can I create a function in a controller that doesn't have an associated view?

I have an index action in my users_controller that get a list of users. For each user i want to calculate the number of projects they have associated (one user => many projects). I was thinking of using a method like getProjectTotal and calling it for each user. Would I put this method in the users_controller and call it like
$this->getProjectTotal($id)
in the view?
Thanks,
Jonesy
Sure. It sounds like this is just a helper method based on the call. I do that all the time. I'll typically set the method visibility to private or at least protected to keep it from being called accidentally in a rendering scenario.
I'm still relatively new to CakePHP, but I've been using the built-in counterCache in Cake 1.2 to track the number of hasMany records for a parent Model in one of my apps. Create a field in your parent Model to store the hasMany count, and enable counterCache in the $belongsTo property for the child Model, and you're good to go. It automatically updates the counterCache count field in the parent model whenever the # of "hasMany" records increases/decreases. I like this method of tracking as it keeps the controller a little cleaner if all you need is the count without any other conditions.
Docs: http://book.cakephp.org/view/816/counterCache-Cache-your-count
Also, I'm still new to MVC, but I think if you're going to gather the count via a private/protected controller method, you'd want to call it in the controller and then send the data to the view, not perform the actual method from the view, in this scenario.
Also - yes you can make a controller method for work that isn't going to render a view - BUT - in your case you should use counterCache / a Model function since you are either fetching / counting / manipulating actual data related to the Project model and it's relationship with the User model and current logged in User specifically.
When building out my controllers I tend to stick to methods that render a view or return data for an element called from requestAction. If the method is computational or setting up variables but doesn't require a template or isn't called from an element I move it to a component / helper / model / behavior. Combined with a docblock with #requestAction in the flags for introspection and I can get a list of regular actions, and data returning actions without worrying that a controller is full of other methods.

modelling database entities in MVC -- how to create them?

I'm working on a PHP web app and I'm trying to use the MVC model. I have a lot of things that exist as 'database entities' -- a row, or a set of related rows, that are conceptually one human recognizable 'thing'. So I often try to provide an interface to that thing in a model class.
So of course, the run of the mill functionality is to have an existing entity, get some data from it, and sometimes update it. However, where I run into problems is when I need to create a new entity. None of the default data for an existing entity exists yet!
For instance, my __construct() methods will often have arguments (i.e. $id for WHERE id = $id) so that I can select data from the database and populate all the properties of the object from that data. However, when I want to create a new entity, the values for those arguments don't exist yet!
How do you all handle this situation? Do you
try to provide functionality for both creation and subsequent manipulation in the same object
have another class just for generating the entity
avoid __construct and have somthing like create() for new entities and instantiate() for existing ones
something else?
If you need the ability to create "new" objects, then I don't think your constructors should be loading from the database by default. Since PHP doesn't support function overloading (so you can't have overloaded constructors), you can either just remove the "populate from database" code from the constructor into a function for that purpose, or give the $id-type argument a default value that indicates that properties shouldn't be populated from the database:
function __construct($id = 0)
{
if ($id == 0)
// create with blank properties
else
// load from database
}
Then if you want a new object, you just call it with no arguments, and if you want to load from the database, you include the id argument.

Categories