Change save order of the related objects - php

I have a product table which is linked to a product_image table in a one-to-many relations
. On the same table I also have a i18n behavior. Which means another table, product_i18n with the same type of relation, one-to-many. I'm using PropelORMPlugin (Propel 1.6). By default it generates the folowing doSave method in my BaseProduct.php file.
protected function doSave(PropelPDO $con)
{
$affectedRows = 0; // initialize var to track total num of affected rows
if (!$this->alreadyInSave) {
$this->alreadyInSave = true;
// We call the save method on the following object(s) if they
// were passed to this object by their coresponding set
// method. This object relates to these object(s) by a
// foreign key reference.
if ($this->aCategory !== null) {
if ($this->aCategory->isModified() || $this->aCategory->isNew()) {
$affectedRows += $this->aCategory->save($con);
}
$this->setCategory($this->aCategory);
}
if ($this->isNew() || $this->isModified()) {
// persist changes
if ($this->isNew()) {
$this->doInsert($con);
} else {
$this->doUpdate($con);
}
$affectedRows += 1;
$this->resetModified();
}
if ($this->productImagesScheduledForDeletion !== null) {
if (!$this->productImagesScheduledForDeletion->isEmpty()) {
ProductImageQuery::create()
->filterByPrimaryKeys($this->productImagesScheduledForDeletion->getPrimaryKeys(false))
->delete($con);
$this->productImagesScheduledForDeletion = null;
}
}
if ($this->collProductImages !== null) {
foreach ($this->collProductImages as $referrerFK) {
if (!$referrerFK->isDeleted()) {
$affectedRows += $referrerFK->save($con);
}
}
}
if ($this->productI18nsScheduledForDeletion !== null) {
if (!$this->productI18nsScheduledForDeletion->isEmpty()) {
ProductI18nQuery::create()
->filterByPrimaryKeys($this->productI18nsScheduledForDeletion->getPrimaryKeys(false))
->delete($con);
$this->productI18nsScheduledForDeletion = null;
}
}
if ($this->collProductI18ns !== null) {
foreach ($this->collProductI18ns as $referrerFK) {
if (!$referrerFK->isDeleted()) {
$affectedRows += $referrerFK->save($con);
}
}
}
$this->alreadyInSave = false;
}
return $affectedRows;
}
I need to access a property of the ProductI18n object when saving the in ProductImage objects table (when saving a Product). The problem is that ProductI18n objects are saved after the ProductImage objects. Meaning that the property is empty when the Product is new (because that property is populated when saving a ProductI18n object based on some other properties). Is there any way to change how Propel generates the order of the saving of the related objects? Is there any other way to make this work without rewriting the doSave method?

While there may be a trick to getting this to work by reordering the foreign-key entries in your schema file, this could be fragile (if someone reorders it again, your code breaks). It may be simpler (if not efficient) to use a postInsert hook on the ProductImage class and access it's relevant ProductI18n entry to get the slug (if it doesn't have it already) and then save the ProductImage again.
class ProductImage extends BaseProductImage {
...
public function postInsert(PropelPDO $con = null) {
if (!$this->getSlug()) {
// get the slug from ProductI18n and update $this, then call ->save();
}
}
...
}

Related

How to set additional custom attribute in Eloquent Laravel while using Accessor

I'm trying to set additional custom attribute while using Accessor in Laravel model.
Example:
I'm calculating promotion price and setting this new attribute, but in additional want to set "$promo = 1 || $promo = 0" using the same logic.
The very cut example just with logic. The real logic is far deeper, that's why I don't want to duplicate the accessor:
public function getFinalPriceAttribute($value)
{
if($this->promotion == true) {
$final_price = $this->price * 100;
//here I want to add new attribute (promo = 1)
//Something like using another method here to setAttribute. Example: setPromoAttribute(1)
} else {
$final_price = $price;
//here I want to add new attribute (promo = 0)
//Something like using another method here to setAttribute. Example: setPromoAttribute(0)
}
return $final_price;
}
protected $appends = ['final_price', 'on_sale'];
}
I can easily duplicate the whole getFinalPriceAttribute(), but make no sense to have exactly the same code in two getAttribute() accessors. Any idea?
I don't know whether this is the correct implementation of what you want to achieve but here are two hacks:
1.
Create a virtual relation for this row in accessor with a new Illuminate\Database\Eloquent\Collection and then push the item promo value into it. But THIS WILL BE AN ARRAY.
public function getFinalPriceAttribute($value)
{
$this->setRelation('promo', new Collection());
if($this->promotion == true) {
$final_price = $this->price * 100;
$this->promo->push(1);
} else {
$final_price = $price;
$this->promo->push(1);
}
return $final_price;
}
So in response, you'll get promo as array like:
{
"promotion": true,
"promo": [
1
]
}
or
{
"promotion": false,
"promo": [
0
]
}
2.
Create two accessors for promo. One as promo0 & other is promo1 and append the attribute dynamically(and conditionally) with append(). But this way you'll get two different attributes in response conditionally.
public function getPromo1Attribute()
{
return true;
}
public function getPromo0Attribute()
{
return true;
}
public function getFinalPriceAttribute($value)
{
if($this->promotion == true) {
$final_price = $this->price * 100;
$this->append('promo1');
} else {
$final_price = $price;
$this->append('promo0');
}
return $final_price;
}
And this will return the response like:
{
"promotion": true,
"promo1": true
}
or
{
"promotion": false,
"promo0": true
}
I hope this will help at least someone in the future.

Save attribute of model in database in PHP in OctoberCMS

Hi I have problem when i tried to save attribute of model to database. I write in OctoberCMS and i have this function:
public function findActualNewsletter()
{
$actualNewsletter = Newsletter::where('status_id', '=', NewsletterStatus::getSentNowStatus())->first();
if (!$actualNewsletter) {
$actualNewsletter = Newsletter::where('send_at', '<=', date('Y-m-d'))->where('status_id', NewsletterStatus::getUnsentStatus())->first();
$actualNewsletter->status_id = NewsletterStatus::getSentNowStatus();
dd($actualNewsletter);
}
return $actualNewsletter;
}
getSentNowStatus()=2;
getUnsentStatus()=1;
dd($actualNewsletter) in my if statement show that status_id = 2 But in database i still have 1. I used this function in afterSave() so i dont need:
$actualNewsletter->status_id = NewsletterStatus::getSentNowStatus();
$actualNewsletter->save();
becosue i have error then i use save in save.
Of course i filled table $fillable =['status_id']. And now i dont know why its not save in database when it go to my if. Maybe someone see my mistake?
If you are trying to modify the model based on some custom logic and then save it, the best place to put it is in the beforeSave() method of the model. To access the current model being saved, just use $this. Below is an example of the beforeSave() method being used to modify the attributes of a model before it gets saved to the database:
public function beforeSave() {
$user = BackendAuth::getUser();
$this->backend_user_id = $user->id;
// Handle archiving
if ($this->is_archived && !$this->archived_at) {
$this->archived_at = Carbon\Carbon::now()->toDateTimeString();
}
// Handle publishing
if ($this->is_published && !$this->published_at) {
$this->published_at = Carbon\Carbon::now()->toDateTimeString();
}
// Handle unarchiving
if ($this->archived_at && !$this->is_archived) {
$this->archived_at = null;
}
// Handle unpublishing, only allowed when no responses have been recorded against the form
if ($this->published_at && !$this->is_published) {
if (is_null($this->responses) || $this->responses->isEmpty()) {
$this->published_at = null;
}
}
}
You don't have to run $this->save() or anything like that. Simply modifying the model's attributes in the beforeSave() method will accomplish what you desire.

Working with multiple objects of the same type (PHP)

What is the corecte way to handle with al lot objects of the same type?
Example:
When i get a list of notes from the database with zend framework i get a rowset which contains an array with note data.
If the number of notes in the database is 20 records large it's no problem to create a note object for every note in the database. But if the database contains 12.500 note records what shall i do than? Try to create 12.500 objects is possible but it's shure isn't quick enough.
Ty, Mark
This is the code i use.
Code to get the data from the database:
if (is_numeric($id) && $id > 0) {
$select = $this->getDao()->select();
$select->where('methode_id = ?', $id);
$select->order('datum DESC');
$rowset = $this->getDao()->fetchAll($select);
if (null != $rowset) {
$result = $this->createObjectArray($rowset);
}
}
createObjectArray function:
protected function createObjectArray(Zend_Db_Table_Rowset_Abstract $rowset)
{
$result = array();
foreach ($rowset as $row) {
$model = new Notes();
$this->populate($row, $model);
if (isset($row['id'])) {
$result[$row['id']] = $model;
} else {
$result[] = $model;
}
}//endforeach;
return $result;
}
Populate function
private function populate($row, $model)
{
// zet de purifier uit om overhead te voorkomen
if (isset($row['id'])) {
$model->setId($row['id']);
}
if (isset($row['type'])) {
$model->setType($row['type']);
}
if (isset($row['tekst'])) {
$model->setLog($row['tekst']);
}
if (isset($row['methode_id'])) {
$model->setSurveyMethodId($row['methode_id']);
}
if (isset($row['klant_id'])) {
$model->setCustomerId($row['klant_id']);
}
if (isset($row['gebruiker_aangemaakt_tekst'])) {
$model->setCreatedByUser($row['gebruiker_aangemaakt_tekst']);
}
if (isset($row['gebruiker_gewijzigd_tekst'])) {
$model->setUpdatedByUser($row['gebruiker_gewijzigd_tekst']);
}
if (isset($row['gebruiker_aangemaakt'])) {
$model->setCreatedByUserId($row['gebruiker_aangemaakt']);
}
if (isset($row['gebruiker_gewijzigd'])) {
$model->setUpdatedByUserId($row['gebruiker_gewijzigd']);
}
if (isset($row['datum_aangemaakt'])) {
$model->setDateCreated($row['datum_aangemaakt']);
}
if (isset($row['datum_gewijzigd'])) {
$model->setDateUpdated($row['datum_gewijzigd']);
}
$model->clearMapper();
return $model;
}
You could page your requests, so you only get a set amount of notes back each time. Although I can't see a problem with "only 12,500" objects, unless your object creation is doing something costly, i.e more queries on the database etc.
I am not so sure about your question.
For eg :
//Create a single object
$obj = new NoteObject();
//Set the properties if it differs
$obj->setX( $row );
//Make use of the method
$obj->processMethod();
So this way you just need a single object. Lets see the code if its not to give you a right answer.
Edit :
What I thought was in
protected function createObjectArray(Zend_Db_Table_Rowset_Abstract $rowset)
{
$result = array();
//Create the model here
$model = new Notes();
foreach ($rowset as $row) {
//Yes populate the values
$this->populate($row, $model);
/*
And not like saving the object here in array
if (isset($row['id'])) {
$result[$row['id']] = $model;
} else {
$result[] = $model;
}*/
//Do some calculations and return the result in array
$result[$row['id']] = $this->doSomething();
}//endforeach;
return $result;
}
I am not sure why you are keeping this object there itself. Any reuse ? the probably paginate and do :-)

Implementing not automatic badges with PHP and MYSQL

I have users' table users, where I store information like post_count and so on. I want to have ~50 badges and it is going to be even more than that in future.
So, I want to have a page where member of website could go and take the badge, not automatically give him it like in SO. And after he clicks a button called smth like "Take 'Made 10 posts' badge" the system checks if he has posted 10 posts and doesn't have this badge already, and if it's ok, give him the badge and insert into the new table the badge's id and user_id that member couldn't take it twice.
But I have so many badges, so do I really need to put so many if's to check for all badges? What would be your suggestion on this? How can I make it more optimal if it's even possible?
Thank you.
optimal would be IMHO the the following:
have an object for the user with functions that return user specific attributes/metrics that you initialise with the proper user id (you probably wanna make this a singleton/static for some elements...):
<?
class User {
public function initUser($id) {
/* initialise the user. maby load all metrics now, or if they
are intensive on demand when the functions are called.
you can cache them in a class variable*/
}
public function getPostCount() {
// return number of posts
}
public function getRegisterDate() {
// return register date
}
public function getNumberOfLogins() {
// return the number of logins the user has made over time
}
}
?>
have a badge object that is initialised with an id/key and loads dependencies from your database:
<?
class Badge {
protected $dependencies = array();
public function initBadge($id) {
$this->loadDependencies($id);
}
protected function loadDependencies() {
// load data from mysql and store it into dependencies like so:
$dependencies = array(array(
'value' => 300,
'type' => 'PostCount',
'compare => 'greater',
),...);
$this->dependencies = $dependencies;
}
public function getDependencies() {
return $this->dependencies;
}
}
?>
then you could have a class that controls the awarding of batches (you can also do it inside user...)
and checks dependencies and prints failed dependencies etc...
<?
class BadgeAwarder {
protected $badge = null;
protected $user = null;
public function awardBadge($userid,$badge) {
if(is_null($this->badge)) {
$this->badge = new Badge; // or something else for strange freaky badges, passed by $badge
}
$this->badge->initBadge($badge);
if(is_null($this->user)) {
$this->user = new User;
$this->user->initUser($userid);
}
$allowed = $this->checkDependencies();
if($allowed === true) {
// grant badge, print congratulations
} else if(is_array($failed)) {
// sorry, you failed tu full fill thef ollowing dependencies: print_r($failed);
} else {
echo "error?";
}
}
protected function checkDependencies() {
$failed = array();
foreach($this->badge->getDependencies() as $depdency) {
$value = call_user_func(array($this->badge, 'get'.$depdency['type']));
if(!$this->compare($value,$depdency['value'],$dependency['compare'])) {
$failed[] = $dependency;
}
}
if(count($failed) > 0) {
return $failed;
} else {
return true;
}
}
protected function compare($val1,$val2,$operator) {
if($operator == 'greater') {
return ($val1 > $val2);
}
}
}
?>
you can extend to this class if you have very custom batches that require weird calculations.
hope i brought you on the right track.
untested andp robably full of syntax errors.
welcome to the world of object oriented programming. still wanna do this?
Maybe throw the information into a table and check against that? If it's based on the number of posts, have fields for badge_name and post_count and check that way?

deep copy of doctrine record

I want to make a deep copy/clone of a doctrine record in a symfony project.
The existing copy($deep)-method doesn't work properly with $deep=true.
For an example let's have a look at a classroom lesson. This lesson has a start and end date and between them there are several breaks. This classroom is in a buildung.
lesson-break is a one-to-many relationship, so a lot of breaks could be inside a lesson.
lesson-building is a many-to-one relationship, so a lesson could only be in ONE Building.
If I want to make a copy of the room the breaks should be copied also. The building should stay the same (no copy here).
I found some examples on the web which create a PHP class which extends from the sfDoctrineRecord and overrides the copy-method.
What I tried was:
class BaseDoctrineRecord extends sfDoctrineRecord {
public function copy($deep = false) {
$ret = parent::copy(false);
if (!$deep)
return $ret;
// ensure to have loaded all references (unlike Doctrine_Record)
foreach ($this->getTable()->getRelations() as $name => $relation) {
// ignore ONE sides of relationships
if ($relation->getType() == Doctrine_Relation::MANY) {
if (empty($this->$name))
$this->loadReference($name);
// do the deep copy
foreach ($this->$name as $record)
$ret->{$name}[] = $record->copy($deep);
}
}
return $ret;
}
}
Now this causes in a failure: Doctrine_Connection_Mysql_Exception: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2-1' for key 'PRIMARY'
So I need to "null" the id of the new record ($ret) because this should be a new record. Where and how could/should I do it?
UPDATE:
The error is fixed with following code:
class BaseDoctrineRecord extends sfDoctrineRecord {
public function copy($deep = false) {
$ret = parent::copy(false);
if($this->Table->getIdentifierType() === Doctrine_Core::IDENTIFIER_AUTOINC) {
$id = $this->Table->getIdentifier();
$this->_data[$id] = null;
}
if(!$deep) {
return $ret;
}
// ensure to have loaded all references (unlike Doctrine_Record)
foreach($this->getTable()->getRelations() as $name => $relation) {
// ignore ONE sides of relationships
if($relation->getType() == Doctrine_Relation::MANY) {
if(empty($this->$name)) {
$this->loadReference($name);
}
// do the deep copy
foreach($this->$name as $record) {
$ret->{$name}[] = $record->copy($deep);
}
}
}
return $ret;
}
}
But it doesn't work well. In the DoctrineCollection lesson->Breaks all new breaks are fine. But they aren't saved in the database.
I want to copy a lesson and add 7 days to it's time:
foreach($new_shift->Breaks as $break) {
$break->start_at = $this->addOneWeek($break->start_at);
$break->end_at = $this->addOneWeek($break->end_at);
$break->save();
}
So as you see, the breaks are saved, but it seems they are not in the db.
This works for me, it's a variant from the question code:
public function realCopy($deep = false) {
$ret = self::copy(false);
if(!$deep) {
return $ret;
}
// ensure to have loaded all references (unlike Doctrine_Record)
foreach($this->getTable()->getRelations() as $name => $relation) {
// ignore ONE sides of relationships
if($relation->getType() == Doctrine_Relation::MANY) {
if(empty($this->$name)) {
$this->loadReference($name);
}
// do the deep copy
foreach($this->$name as $record) {
$ret->{$name}[] = $record->realCopy($deep);
}
}
}
// this need to be at the end to ensure Doctrine is able to load the relations data
if($this->Table->getIdentifierType() === Doctrine_Core::IDENTIFIER_AUTOINC) {
$id = $this->Table->getIdentifier();
$this->_data[$id] = null;
}
return $ret;
}
I can't believe I'm working with Doctrine 1.2 in 2017.

Categories