Create new entity from array with Phalcon Framework - phalcon

Is it possible to set Entities values automatically from array?
my entity inherets from \Phalcon\Mvc\Model and I want to set all values from array instead of querying database
is this feature already implemented?

Yes you can!
$myModel = new MyModel();
$myModel->save(['name' => 'Jeff']);
// even from post data, second argument is a whitelist of fields
$myModel->save($_POST, ['name']);
// The same for updating
$myModel = MyModel::findFirst();
$myModel->update(['name' => 'jodator']);
More here: Creating and updating records

$MyModel= new MyModel();
$MyModel->assign(array(
'id' => 1,
'name' => 'y'
));
//or
$MyModel->id = 1;
$MyModel->name = 'y';

Related

Laravel Row duplication inserted, with updateOrCreate method with Race-Condition

i have function in my controller that create a forecast :
public function updateOrCreate(Request $request, $subdomain, $uuid)
{
$fixture = Fixture::where('uuid',$uuid)->firstOrFail();
request()->validate([
'local_team_score' => 'integer|min:0',
'visitor_team_score' => 'integer|min:0',
'winner_team_id' => 'integer|nullable'
]);
if ($fixture->status !== "PENDING"){
return response()->json([
'message' => "You can not add or modify a forecast if the fixture is not pending"
], 403);
}
$winner_team = null;
// local team win
if ($request->local_team_score > $request->visitor_team_score) {
$winner_team = $fixture->localTeam;
}elseif ($request->local_team_score < $request->visitor_team_score){ //visitor win
$winner_team = $fixture->visitorTeam;
}else{ // draw
$winner_team = FixtureTeam::where('team_id',$request->winner_team_id)->first();
}
$user = auth('api')->user();
$platform = Platform::first();
$forecast = Forecast::updateOrCreate([
'user_id' => $user->id,
'fixture_id' => $fixture->id,
'platform_id' => $platform->id
],[
'local_team_score' => $request->local_team_score,
'visitor_team_score' => $request->visitor_team_score,
'winner_team_id' => is_null($winner_team) ? null : $winner_team->team_id
]);
$forecast->load('winnerTeam');
return new ForecastResource($forecast);
}
As you can see i use updateOrCreate methods to add or update a forecast.
The problem is when 2 requests from the same user run at the same time (and no forecast is already created) 2 row are inserted.
Do you have a solution ?
I See that the problem is not new but i could not find a solution https://github.com/laravel/framework/issues/19372
updateOrCreate does 2 steps:
tries to fetch the record
depending on the outcome does an update or a create.
This operation is not atomic, meaning that between step 1 and 2 another process could create the record and you would end up with duplicates (your situation).
To solve your problem you need following:
determine what columns would give the uniqueness of the record and add an unique index (probably compound between user_id, fixture_id, platform_id)
you need to let database handle the upsert (ON DUPLICATE KEY UPDATE in MySQL, ON CONFLICT (...) DO UPDATE SET in Postgres, etc). This can be achieved in Laravel by using the upsert(array $values, $uniqueBy, $update = null) instead of updateOrCreate.

Phalcon: save reference to new record in one transaction

Im trying to save a reference to a new object in a single transaction as shown here in the documentation under 'implicit transactions':
I am creating two new objects of the same class, one is then referencing the other. From the documentation, the save should be performed on TreeNodeA when TreeNodeB is saved and the ID will be passed to TreeNodeB->parent_tree_node_id
This doesnt seem to be working, and it still being passed as an object as Im getting the error on the save function:
Object of class TreeNodes could not be converted to string
I've tried writing a saveTreeParentNodeId function in the model and also setting it using the alias, but neither seem to work.
$treeNode = new TreeNodes();
$treeNode->setConnectionService(Registry::setConnection(MyModel::MAIN_DB));
$parentNode = $treeNode->findFirst();
$treeNodeA = new TreeNodes();
$treeNodeA->tree_id = $parentNode->tree_id;
$treeNodeA->tree_parent_node_id = $parentNode;
$treeNodeA->tree_level_id = 2;
$treeNodeA->node_desc = "Test Node A";
$treeNodeB = new TreeNodes();
$treeNodeB->tree_id = $parentNode->tree_id;
$treeNodeB->tree_parent_node_id = $treeNodeA;
$treeNodeB->tree_level_id = 3;
$treeNodeB->tree_desc = "Test Node B";
$treeNodeB->save();
The model:
class TreeNodes extends MyModel
{
public $node_id;
public $tree_id;
public $tree_parent_node_id;
public $tree_level_id;
public $node_desc;
public function getSource()
{
return "TreeNodes";
}
public function setTreeParentNodeId(TreeNodes $parentNode){
$this->tree_parent_node_id = $parentNode->node_id;
}
public function initialize()
{
parent::initialize();
$this->belongsTo(
'tree_id',
'Organisations',
'TreeID',
array(
'alias' => 'organisation',
'reusable' => true
)
);
$this->hasOne(
'tree_id',
'TreeType',
'tree_id',
array(
'alias' => 'type',
'reusable' => true
)
);
$this->hasOne(
'tree_parent_node_id',
'TreeNodes',
'node_id',
array(
'alias' => 'parentNode'
)
);
}
}
Update
By updating the model to use belongsTo, Phalcon recognises the parentNode.
$this->belongsTo(
'tree_parent_node_id',
'TreeNodes',
'node_id',
array(
'alias' => 'parentNode'
)
);
This enables $treeNodeA to save implicitly when $treeNodeB is saved.
$treeNodeA->parentNode = $parentNode;
Unfortunately, $treeNodeB with a reference to $treeNodeA as the parentNode is NOT saved. No error message is returned either, just 'true'.
In the documentation example you linked, they assign the $robotPart object to $robot->robotPart. robotPart refers to the linked RobotPart object and not to the ID ( to which you are trying to assign your object )
$treeNodeB = new TreeNodes();
$treeNodeB->tree_id = $parentNode->tree_id;
// $treeNodeB->tree_parent_node_id = $treeNodeA;
$treeNodeB->parentNode = $treeNodeA;
$treeNodeB->save();
You should use parentNode here because this is the name you gave to your relationship via hasOne.
I haven't tested this myself, but by following the documentation's logic, this should push you in the right direction.
Alter you model relationships so you have both sides of the relation
// let Phalcon know that "tree_parent_node_id" is a reference to "node_id"
$this->hasOne(
'tree_parent_node_id', // your column
'TreeNodes', // referenced table
'node_id', // referenced table column
array(
'alias' => 'parentNode',
'foreignKey' => true
)
);
// let Phalcon know that "node_id" is being referenced as a FK in "TreeNodes"
$this->belongsTo(
'node_id', // PK
'TreeNodes', // referenced table
'tree_parent_node_id', // referenced table column
array('foreignKey' => ['message' => 'FK constraint error between node_id and tree_parent_node_id'])
);

Findallbyattributes With Related Model

I'm trying to do a findAllByAttributes using a related model column as one of the criteria, but I keep getting a CDbException stating the column cannot be found.
Here's my model Relationship:
public function relations() {
return array(
'MetaData' => array(self::BELONGS_TO, 'ProjectMeta', 'wbse_or_io'),
);
}
And here's my attempted query:
$listing = ProjectIndex::model()->with('MetaData')
->findAllByAttributes(array(
'report_date'=>$reportDate,
'MetaData.cost_centre'=>$costCentre
)
);
From what I've read through Google/StackOverflow/these forums, I should be able to reference the cost_centre column in the MetaData relationship. But I keep getting the following error:
Table "tbl_project_index" does not have a column named "MetaData.cost_centre"
How do I reference the related table column?
Check this out
$listing = ProjectIndex::model()->with(
'MetaData'=>array(
'condition'=>'cost_centre = :cost_centre',
'params'=>array('cost_centre'=>$costCentre))
)
->findAllByAttributes(array('report_date'=>$reportDate));
The attributes in the attributes array cannot be for the related models. You can look at the source for findAllByAttributes for a better explanation. You can, however, pass the related attribute as a condition string or CDbCriteria array, in addition to Alex's answer.
$listing = ProjectIndex::model()->with('MetaData')->findAllByAttributes(
array('report_date'=>$reportDate),
'cost_centre = :cost_centre',
array(':cost_centre'=> $costCentre)
);
Or
$listing = ProjectIndex::model()->with('MetaData')->findAllByAttributes(
array('report_date'=>$reportDate),
array(
'condition' =>'cost_centre = :cost_centre',
'params'=>array(':cost_centre'=> $costCentre)
),
);

Adding a condition to every query in a model

I have a system where all tables in the MySQL database are populated with external data (synchronized with another system every 5 minutes). All tables have a column DELFLAG which is used to mark disabled entries.
So I have about 15 AR models in Yii that are linked to those tables. Whenever I make a query, I need to add something like $criteria->addCondition('DELFLAG=0'). This gets ugly if there are multiple tables present in the query, as every one of them has the flag. Also, there's a potential for error if I forget one of those conditions.
Here's how I do it now:
public function search($showtype = NULL) {
$criteria=new CDbCriteria;
if (isset($showtype)) {
$criteria->with = array(
'TSSSHOW',
'TSSSHOW.TSSSHOWTYPEITEM',
'TSSSHOW.TSSSHOWTYPEITEM.TSSSHOWTYPE'
);
$criteria->compare('TSSSHOWTYPEITEM.TSSSHOWTYPEID', $showtype);
$criteria->addCondition('TSSSHOW.DELFLAG=0');
$criteria->addCondition('TSSSHOWTYPEITEM.DELFLAG=0');
}
$exp = new CDbExpression("`TSSEVENT_START_DATETIME` > NOW()");
$criteria->addCondition($exp);
$criteria->addCondition('t.DELFLAG=0');
$criteria->together = true;
$criteria->order = 't.TSSEVENT_START_DATETIME ASC';
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
'pagination' => array(
'pageSize' => 15,
),
));
}
Is there a convenient way to include this condition into every query that includes these tables? Perhaps a special class which my models shall descend from (as opposed to the default CActiveRecord)?
I'd suggest declaring named scope for these models:
public function scopes()
{
return array(
'disabledEntry'=>array(
'condition'=>'DELFLAG=1',
),
);
}
Using the named scope: Model::model()->disabledEntry()->findAll();
You can provide scope when relaring to model in the with() statement as well: ModelA::model()->with('model:disabledEntry')->findAll();
But if you have to set this condition each time, you may set defaultScope:
public function defaultScope()
{
return array(
'condition' => 'DELFLAG=0',
);
}
Thus, this model by default would have this condition.
Update: Since you have the identically named columns in several models, YII can have some column name ambiguity troubles while building sql-query. If it is the case, use alias in scope declaration or use the following statement to set current table alias explicitly while declaring scope 'condition' => $this->getTableAlias(false, false) . '.DELFLAG=0',
You can create Your own class e.g. CustomActiveRecord extends CActiveRecord
then You should override findAll, findByPk methods with your criteria..
Other solution is to run this query 'DELETE FROM table_name WHERE DELFLAG=1' after every import..

Kohana 3.0.x ORM: Read additional columns in pivot tables

I'm using Kohana v3 and ORM, I have two models, Model_A and Model_B related by "has_many" through a pivot table, which has an additional column. I can save data in that column in the pivot table using the third parameter of the add() function, but I can't figure out how to read that column using ORM.
Any ideas? Thanks in advance.
You need to create a Model that is based on that pivot table if you want to access that additional column, let say we name it Model_A_B.
class Model_A_B extends ORM {
protected $_belongs_to = array(
'A' => array(),
'B' => array()
);
}
Then, if $a is an instance of Model_A and $b is an instance of Model_B, we get the Model_A_B instance by calling:
$ab = ORM::factory('A_B', array('A_id' => $a, 'B_id' => $b));
if ($ab->loaded()) {
// do stuff
}