I'm using phpactiverecord. I have an Order object that has an id and code. On database, the id is the primary key int and auto increment. code is a varchar and unique and is composed by the id and other chars.
The problem is that I need to set the code value before to save and for this, I need to get the id.
Currently I did:
class Order extends ActiveRecord\Model{
function save() {
if (parent::save()): // I save all data
$this->code = "{$this->id}w"; // Once it was saved I get the id
parent::save(); // I save the code
return true;
endif;
return false;
}
}
Is there a more elegant way to do it?
You're technically not supposed to override save() in that way, as even in this case you're changing the method signature, (it's public function save($validate=true)). And there are a bunch of possible callbacks for your case. The better way for your case is:
public static $after_create = array('after_create');
public function after_create()
{
$this->code = $this->id.'w';
$this->save();
}
Also it's soo awkward to use templating if else inside of class code :P.
This code may possibly fail if you don't have the latest version from github, as there was a bug earlier on where after_create didn't know that the object was already saved.
Related
Given following code:
class Picture {
public function getAbsolutePathAttribute() {
return "absolute_path"
}
}
$picture = new Picture();
echo $picture->absolute_path; // prints "absolute_path"
$picture->absolute_path = "I will override you no matter what";
echo $picture->absolute_path; // prints "absolute_path"
Is there way of overriding an eloquent mutator attribute?
I have tried a magic method:
setAbsolutePathAttribute($value) {
$this["attributes"] = $value;
}
However it did not work.
So I don't recommend trying to solve this by overriding the mutator. You might be able to, but the less you touch the core of Laravel the better. Why? Because you never know what future Laravel code could look like.
A core change could possibly break your internal override the next time you do a minor or major Laravel version upgrade.
So, 2 ways to solve this, given your simple example:
1. Create a default value at the database level
Laravel migrations feature a default column modifier, it looks something like this: ->default('something')
That way you don't need the mutator at all for getting that default value you're looking for.
You can read more about them here: https://laravel.com/docs/5.6/migrations#column-modifiers
2. Make your accessor smarter
Your example is pretty simple, so I'll just work with it:
class Picture {
public function getAbsolutePathAttribute() {
if(is_null($this->absolute_path)) {
return "absolute_path";
}
return $this->absolute_path;
}
}
That way it only does something if there is no value present.
I need to pass some data down to my view. I have two models a user and a tips model.
The User model has a method that returns the user hasMany(Tip::Class) and the Tip model has a method that returns that the tip belongsTo(User::class).
I'm doing a profile page for a user and using route model binding to return a user model when accessing the profile.
public function tipsterProfileShow(User $tipster)
{
if (!$tipster->isTipster())
{
return redirect()->route('home');
}
return view('profile.index')->with([
'tipster' => $tipster,
'tips' => $tipster->tips(),
]);
}
I want to display some data such as the amount of tips that are correct which are indicated by the status column in the tips table.
At the moment in the blade view I'm using
{{$tips->where('status','Won')->count()}}
I feel this isn't best practice but I may be wrong.
Would it be better doing something like the below?
public function tipsterProfileShow(User $tipster)
{
if (!$tipster->isTipster())
{
return redirect()->route('home');
}
return view('profile.index')->with([
'tipster' => $tipster,
'tips' => $tipster->tips(),
'wins' => $tipster->tips()->where('status', 'Won')->count()
]);
}
That way I would be keeping the queries out of the view. I'm really new to laravel and 'best practice' so trying to get some advice.
You're asking about a best practice, which is generally frowned upon, but this really is something essential that every beginner should learn, so I still think it merits an answer.
In brief: YES! What you're doing is a great first step towards keeping your code separated by logic. Your views should be responsible for displaying data, and your controllers for handling data over to the views. Whether your controllers should actually be responsible for calculating the data is another topic, and one which is constantly debated.
That said, you could get this down to just a single line in the controller if you apply a little bit of other logic:
public function tipsterProfileShow(User $tipster)
{
return view('profile.index', compact('tipster'));
}
The first step is to add a method to your User model, something like this:
public function winCount()
{
return $this->tips()->where('status', 'Won')->count();
}
Now you can access $tipster->winCount() from your view. You can also access $tipster->tips() straight away in your view - most would agree that's perfectly fine.
The second step is to extract the redirect call for non-tipsters into a middleware, which you can read about here: https://laravel.com/docs/5.3/middleware
There are further steps you might take from there, but that's a good starting point. Good luck! :)
I will advice you creating a POPO (Plain Old PHP Object) that will contain a full description of a user more synonymous to a User profile.
A model in laravel represents a row in the table and it will lazy-load any relationship attached to it.
So if you have a POPO, you will be able to define everything that is related to the user and pass it on to the view without having to query in the view.
Take below as an example:
Class UserPorfile{
private $id;
private $username;
private $tips;
public function setId($id){
$this->id = $id;
}
public function getId(){
return $this->id;
}
public function setUsername($username){
$this->username = $username;
}
public function getUsername(){
return $this->username;
}
public function setTips(array $tips){
$this->tips = $tips;
}
public function getTips(){
return $this->tips;
}
}
passing an object of this class to your view seems much more better
Yes, you are correct. Given you are trying to follow the MVC software development pattern, you should avoid putting business logic in your view.
This line of code:
{{$tips->where('status','Won')->count()}}
is actually doing 2 things:
Query the model for all objects with a certain criteria
Count the result
By following the MVC principles it should be your controller that sends the commands to the model, not the view.
Good luck!
I'm creating an audit trail module that i will put in a larger system; and i've created a table to store the trail entries , as an "auditor" what i want to see the currently logged on user, the page where he/she is in, what action he/she did, and what were the changes and when...
these are basically what i want to see; my audit trail table looks like:
User| Timestamp| Module Name| Action| Old Value| New Value| Description
i basically had no problem getting the user, by
Yii::app()->session['username'];
the page/module and action by getting the controller's :
$this->module->getName();
$this->action->id;
My problem lies with the changes old value to new value, the edits done by the user.
i could sort of "sniff" out what edits/ changes he/she did by literally copying the variables and passing it through my function where i create the log.. How do i do this dynamically?
i sort of want to detect if a certain model's properties or attributes has been changed and see what changes were made so that i could get a detail log...Thanks ! sorry, i'm really trying hard to explain this.
In each model that you want to observe you can write a afterFind() method, where you store the current DB attributes into some private variable, e.b. _dbValues. Then in beforeSave() you verify the current attributes with the ones in _dbValues and create an audit record if there was a change.
After you have this working, you can take it a step further and create a behavior from it. You'd put the private variable, the afterFind() and the beforeSave() method there. Then you can attach that behavior to many records.
Quick example:
class Book extends CActiveRecord
{
private $oldAttrs = array();
public static function model($className = __CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return 'book';
}
protected function afterSave()
{
// store history
if (!$this->isNewRecord) {
$newAttrs = $this->getAttributes();
$oldAttrs = $this->getOldAttributes();
// your code
}
return parent::afterSave();
}
protected function afterFind()
{
// Save old values
$this->setOldAttributes($this->getAttributes());
return parent::afterFind();
}
public function getOldAttributes()
{
return $this->oldAttrs;
}
public function setOldAttributes($attrs)
{
$this->oldAttrs = $attrs;
}
}
Your solution is good, but what if there're 2 threads that call ->save() at the same time?
Assume that:
the 1st thread find record, save the A status.
the 2nd thread find record, save the A status.
then 1st thread change record to B, call ->save(). System will log A->B
then 2nd thread change record to C, call ->save(). System will log A->C
summary, there're 2 log: A->B, A->C. If this is not problem for you, just ignore it and do the above solution.
I need to override my form like this:
protected function doSave($con = null)
{
$filename= Helper::generateFilename($this->getValue('name')).'jpg';
imagejpeg(Helper::createImage($this->getValues()), sfConfig::get('sf_upload_dir').'/poster/'.$filename );
/* Here I need to set value of *fielname* field of the form equal to
$filename */
parent::doSave($con);
}
Helper.class.php is located in /lib/so it acts like a helper class.
generateFilename actually gives a randomized string. since there is no function like setValue in a form, I'm stuck.
I tried macgyver's approach but didn't work for me. Not sure if I did anything wrong.
Syfmony version is 1.4 with Doctrine.
I had to override the updateObject() function inside the form class.
In my case its inside TimesheetForm.class.php. I had to change the status of the timesheet based on user action.
Following is the piece of code that that I tried. It did update the object before saving. Which was seen effected in the db.
public function updateObject($values = null)
{
$object = parent::updateObject($values);
$object->setStatus("Submitted");
return $object;
}
I didn't impelement/override the doSave() function. I'm using symfony 1.4.
Hope the above helps.
You have to use, say you have used filename as field in your schema to store the image filename, $this->object->setFilename($filename); before parent::doSave($con);
In CakePHP function edit I use read() function as:
$this->data = $this->Article->read(null, $id);
It brings all fields of $id. Now, what I am trying to tweak to give one more condition in read() to get articles only if user logged in is related to it.
eg:
$this->Article->user_id = $user_id;
$this->Article->id = $id;
$this->Article->read();
And obviously it want work as read() brings data only w.r.t. $id (primary key).
My question:
Is there any way to tweak read function with condition more than $id ? Because it will just need to add a one line in all my controllers if it works ?
Or I have to use long code of find() function to get is the only option ?
Any best solution will be appreciable.
If you really must do thise, you could use OOP techniques to override the way the core method works.
Just copy the Model::read() method to your AppModel class and make the changes necessary.
You have to use find() if you want to make the search conditionally. Another option is to read the data only if the conditions hold:
$this->Article->id = $id;
if( $this->Article->field( 'user_id' ) == $user_id ) {
$this->Article->read();
}
(read() populates $this->data automatically.)