set a field in pivot table whenever a record insert or update happens in laravel - eloquent

OK, recently I am implementing an RBAC based system in laravel.
I have these two classes as my models:
class User extends Authenticatable
{
public function roles(){
return $this->belongsToMany(\App\Role::class, 'user_role', 'user_id', 'role_id')->withPivot('is_expired', 'assigned_at', 'expire_at');
}
}
class Role extends Model
{
public function users(){
return $this->belongsToMany(\App\User::class, 'user_role', 'role_id', 'user_id')->withPivot('is_expired', 'assigned_at', 'expire_at');
}
}
it works fine BUT, I want to set a default value for the expire_at attribute of the pivot table based on an attribute of the Role model. for example I have a period attribute on Roles table, and its a number representing number of months.
So i want when a role assigned to a user (inserted in pivot table) the value of expire_at set to currentDatetime + thePeriodNum months and save in the pivot table.
how can I achieve this?
I have tried laravel custom pivot class and mutators but it seems not working or I did something wrong.
somebody mentioned that the mutators dont get triggered when using attach()/detach() methods so I think even if it was working i could not see the difference.
Someone mentioned its possible with observers but I have no Idea what is an observer im noob.
So thats all, it would be really good for me if anybody could help me through this mess I'm in right now.
Thanks in advance.

It is possible to attach the new role and set the expires_at column at the same time. This would avoid needing to use observers (listeners of model events) within your code.
The code would look like the following:
$role = Role::find($roleId);
$expireAt = now()->addMonths($role->period)->toDateTimeString();
// or Carbon::now()->addMonths($role->period)->toDateTimeString();
User::find($userId)->roles()->attach($role->id, ['expire_at' => $expireAt]);
Here, the role is found. A timestamp is created by taking the current time, adding the addition months based on the role's period (this should be an integer value).
Finally, this is added to the attachment of the role to the user.
Add as a model method
This can all be added as a function/method on the User model which would clean up the code into one action, ->addRole($roleId):
// Within App\User.php
public function addRole($roleId)
{
$role = \App\Role::find($roleId);
$expiresAt = now()->addMonths($role->period)->toDateTimeString();
$this->roles()->attach($role->id, [ 'expire_at' => $expiresAt ]);
}
This can then be called with the following:
$user = User::find($id);
$user->addRole($roleId);
I hope this help.

Related

Symfony2 form validation of Doctrine2 object: accessing previous values on updates

I've written a class validator as a service and passed the doctrine entity manager to this validator. At this point everything works fine.
But now I need the unchanged object of $entry which is been updated in the form - or at least the previous values. I've tried some approaches, but did not succeed:
public function isValid($entry, Constraint $constraint)
{
$oldEntry = $this->em->getRepository('SomeBundle:Entry')->findOneBy(array('id' => $entry->getId()));
Doctrine fetches the same (changed) object as expected. But trying to refresh the object will reset both versions of the object:
$newEntry = clone $entry;
$this->em->detach($newEntry);
$this->em->refresh($entry);
$hoursOfOldEntry = $entry->calculateHours();
$this->em->merge($newEntry);
Another option could be to save the values of the object as array, refresh the object and reassign the saved values again after working on the original values. But this does not seem to be the best way, especially if the are many relations. I don't wont to touch the object within a validator, I just need the previous values!
Another approach could be using Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity). But I don't think it's a good idea to use internal doctrine methods in a validator!
So how do I get the original object or the change set in a class validator?
This won't get you the original entity, but should get you a key/value array of the original fields:
$uow = $em->getUnitOfWork();
$originalData = $uow->getOriginalEntityData($entry);
http://www.doctrine-project.org/api/orm/2.0/source-class-Doctrine.ORM.UnitOfWork.html#2210

How do I tell Play Framework 2 and Ebean to save null fields?

I'm using Play Framework 2 and Ebean. When a user submits a form to edit an existing object in the database, it doesn't save null values. I guess this is to prevent overwriting fields that aren't in the form with null. But how can I let them set fields in the form to null if they need to?
For example, the user edits an Event object. Event.date is 1/1/13. The user sets the Event.date field in the form to empty and submits the form. Inspecting Event.date in the debugger shows its value is null. I save the Event. If I look at the Event in the database, its value is still 1/1/13.
Edit: It seems there is a method for this. The only problem is it doesn't work on nested entities. Any solutions for this?
update(Object bean,Set<String> properties)
Create an ebean.properties file right next to the application.conf file and add this line to it:
ebean.defaultUpdateNullProperties=true
Null properties in Ebean are considered as unloaded, so to prevent accidental nulling properties that shouldn't be nulled, they are just excluded.
Because of this reverting Date (and other fields) to null in Ebean is... hard :). Last time when I had to do the same thing (revert Date) I used second query to do just... nulling the Date (after event.update(Object o)):
public static Result updateEvent(){
Form<Event> eventForm = form(Event.class).bindFromRequest();
// do some validation if required...
Event event = eventForm.get();
event.update(event.id);
if (eventForm.get().date == null){
Ebean
.createUpdate(Event.class, "UPDATE event SET date=null where id=:id")
.setParameter("id", page.id).execute();
}
}
On the other hand, if you are using comparison, for filtering events (always selecting newer than X), you can just set the date to very 'old' value, which also should do the trick. In this case you'll update the object only once.
private static final Date VERY_OLD_DATE = new GregorianCalendar(1, 0, 1).getTime();
public static Result updateEvent(){
Form<Event> eventForm = form(Event.class).bindFromRequest();
Event event = eventForm.get();
if (eventForm.get().date == null){
event.date = VERY_OLD_DATE;
}
event.update(event.id);
}
In this case in your HTML form you will need to clear the value of the form's field (or just send every time date like 0001-01-01), however it can be done easily even with JavaScript.

Add ‘subscription_date’ and ‘user_ip_address’ fields for Newsletter Subscriber and show it in admin panel in Magento

I want to add two custom fields for newsletter subscriber at which date customer subscribed and its ip address:
1) I had added two columns in ‘newsletter_subscriber’ table.
How this can be achieved?
In the file app/code/core/Mage/Newsletter/Model/Subscriber.php
I found the the functions like:
$this->setSubscriberEmail($email);
$this->setStoreId(Mage::app()->getStore()->getId());
$this->setCustomerId(0);
But i did not found its code.
I think I also save data like that but how it will be possible ? where I have to define and declare code for the function like $this->seSubscriptionDate();
And how to display in Admin panel under Newsletter->Newsletter Subscriber ?
I had not find the solution or help from any person.
I bang my head and find the solution its very simple hope in future may some one’s time save by this post:
1) Add columns in the table “newsletter_subscriber” say in my case “subscription_date” and “sub_ip_address”
2) Add following two lines in the file # two places
app\code\core\Mage\Newsletter\Model\Subscriber.php
**$this->setSubscriptionDate(date("d-m-Y"));
**$this->setSubIpAddress($_SERVER['REMOTE_ADDR']);
One place: in the function: public function subscribe($email)
Before: $this->save();
Second place: in the function: public function subscribeCustomer($customer)
Before: $this->save();
Now data will be added in the table
Now to show in admin panel
1) Open the file app\code\core\Mage\Adminhtml\Block\Newsletter\Subscriber\Grid.php
Just add two required columns in it like
$this->addColumn('subscription_date', array(
'header' => Mage::helper('newsletter')->__('Subscription Date'),
'index' => 'subscription_date',
'default' => '----'
));
$this->addColumn('sub_ip_address', array(
'header' => Mage::helper('newsletter')->__('IP Address'),
'index' => 'sub_ip_address',
'default' => '----'
));
Now finish.
** This is the point where I spent my time and at the end I added this function on hit and trieal basis but it works.
Someone from Core magento team plz explain why this {setSubscriptionDate()}function work ? I had not declared anybody of this function.
It seems it shows intellisense to detect table field?
I also had the similar problem. However, in my case I am supposed to add a "country" and "gender" field in the newsletter form. So, on digging into the core for hours I figured this out. Below is the explaination:
In the app/design/frontend//default/template/newsletter/subscribe.phtml,
the form has getFormActionUrl() ?> in its action field. This referes to app/code/core/Mage/Newsletter/Block/Subscribe.php
The $this object used in the subscirbe.phtml file referes to this class (i.e. Mage_Newsletter_Block_Subscribe) and thus $this->getFormActionUrl() in the subscribe.phtml file refers to the function getFormActionUrl() of this class.
Now, this getFormActionUrl() is returning a method $this->getUrl('newsletter/subscriber/new', array('_secure' => true)) which belongs to its parent Mage_Core_Block_Template (I mean the method getURL()). This part is not improtant to us.
See the parameter passed to the getURL() function which is newsletter/subscriber/new
The first part "newsletter" is the module name (app/code/code/Mage/newsletter), the second part "subscriber" is the controller name (app/code/code/Mage/newsletter/controllers/SubscriberController) and the third part "new" is the method newAction in the controller SubscriberController.
Controller names are suffixed with Controller and function names are suffixed by Action. (Thanks to Phalcon framework to help understand this)
Now in the newAction() method you can see that by default, only the email is being posted
as
$email = (string) $this->getRequest()->getPost('email');
What I did was, I cloned the subscribe.phtml template with a name custom-newsletter.phtml and add two fields with name "country" and "gender". Then added the following lines in the newAction():
$country = (string) $this->getRequest()->getPost('country');
$gender = (string) $this->getRequest()->getPost('gender');
Now at line number 67 of the SubscriberController (inside the newAction() method) there is a code :
$status = Mage::getModel('newsletter/subscriber')->subscribe($email);
This line is calling the subscribe method of app/code/code/Mage/newsletter/Model/Subscriber.php and is passing the $email that is posted from newsletter form into it. I modified the line as:
$status = Mage::getModel('newsletter/subscriber')->subscribe($email,$country,$gender);
Now, I have to edit the app/code/code/Mage/newsletter/Model/Subscriber.php
When we talk about Models, we are talking about the database table the Model refers to. The model name is Subscriber and it belongs to module Newsletter so, the database table that this model affects is newsletter_subscriber
From this part on #Hassan Ali Sshahzad's question will be answered.
I added two new columns there with the name: subscriber_country and subscriber_gender.
Now, Magento' system automatically makes available a getter and a setter function to these columns with the following name:
function getSubscriberCountry(){}
function setSubscriberCountry($country_name){}
So, all I had to do in the model was:
Edit the method subscribe($email) to subscribe($email,$country,$gender)
add the following code in the subscribe($email,$country,$gender) function right before the try statement as follows:
$this->setSubscriberCountry($country);
$this->setSubscriberGender($gender);
try{
$this->save()
Hassan's method played a key role in my understanding of the MVC of Magento so its a return from my side.

Entity Framework Views and Linq .Where

I have a very small entity framework setup containing only a few related classes/tables and a view. I need to be able to pull a specific record from this view, namely, I need to be able to grab the record that meets two criteria, it has a specific ProfileID and a specific QuoteID.
This line is what's causing the problem:
TWProfileUpchargeTotal upchargeTotals = _context.TWProfileUpchargeTotals.Where(p => p.Profileid == profile.id && p.quoteid == _quote.quoteid).First();
I'm looping through the profiles I know about and getting their information from the view, so profile.id changes each time.
The first time this code executes it gets the correct record from the view.
The second and third (and presumably beyond that) time it executes, it retrieves the exact same record.
Any idea why or what I'm doing wrong here?
Thanks, in advance.
You've been bitten by the LINQ "gotcha" called closure. The following post (and many others) on SO detail this:
closure
What you need to do is declare a variable WITHIN the foreach you've ommited from the above code and assign the profile.id to this and use this in the Where clause.
foreach(Profile profile in ListOfProfiles)
{
var localProfile = profile;
TWProfileUpchargeTotal upchargeTotals = _context.TWProfileUpchargeTotals.Where(p => p.Profileid == localProfile.id && p.quoteid == _quote.quoteid).First();
}

Symfony: Model Translation + Nested Set

I'm using Symfony 1.2 with Doctrine. I have a Place model with translations in two languages. This Place model has also a nested set behaviour.
I'm having problems now creating a new place that belongs to another node. I've tried two options but both of them fail:
1 option
$this->mergeForm(new PlaceTranslationForm($this->object->Translation[$lang->getCurrentCulture()]));
If I merge the form, what happens is that the value of the place_id field id an array. I suppose is because it is waiting a real object with an id. If I try to set place_id='' there is another error.
2 option
$this->mergeI18n(array($lang->getCurrentCulture()));
public function mergeI18n($cultures, $decorator = null)
{
if (!$this->isI18n())
{
throw new sfException(sprintf('The model "%s" is not internationalized.', $this->getModelName()));
}
$class = $this->getI18nFormClass();
foreach ($cultures as $culture)
{
$i18nObject = $this->object->Translation[$culture];
$i18n = new $class($i18nObject);
unset($i18n['id']);
$i18n->widgetSchema['lang'] = new sfWidgetFormInputHidden();
$this->mergeForm($i18n); // pass $culture too
}
}
Now the error is:
Couldn't hydrate. Found non-unique key mapping named 'lang'.
Looking at the sql, the id is not defined; so it can't be a duplicate record (I have a unique key (id, lang))
Any idea of what can be happening?
thanks!
It looks like the issues you are having are related to embedding forms within each other, which can be tricky. You will likely need to do things in the updateObject/bind methods of the parent form to get it to pass its values correctly to its child forms.
This article is worth a read:
http://www.blogs.uni-osnabrueck.de/rotapken/2009/03/13/symfony-merge-embedded-form/comment-page-1/
It gives some good info on how embedding (and mergeing) forms work. The technique the article uses will probably work for you, but I've not used I18n in sf before, so it may well be that there is a more elegant solution built in?