I've got two tables: step and links joined 1:n. I'm aiming to maintain the links through the step objects. I retrieve all steps from the database and populate the relation with the links table. I persist the step object containing a collection of links to JSON and return it to the front end using REST.
That means that if a step is linked or unlinked to another step in the front end I send the entire step back to the backend including a collection of links. In the back end I use the following code:
public function put($processStep) {
if (isset($processStep['Processesid']) && isset($processStep['Coordx']) && isset($processStep['Coordy'])) {
$p = $this->query->findPK($processStep['Id']);
$p->setId($processStep['Id']);
$p->setProcessesid($processStep['Processesid']);
if (isset($processStep['Flowid'])) $p->setFlowid($processStep['Flowid']);
if (isset($processStep['Applicationid'])) $p->setApplicationid($processStep['Applicationid']);
$p->setCoordx($processStep['Coordx']);
$p->setCoordy($processStep['Coordy']);
$links = $p->getLinksRelatedByFromstep();
$links->clear();
foreach ($processStep['Links'] as $link) {
if (!isset($link['Linkid'])) {
$newLink = new \Link();
$newLink->setFromstep($link['Fromstep']);
$newLink->setTostep($link['Tostep']);
$links->prepend($newLink);
}
}
$p->save();
return $p;
} else {
throw new Exceptions\ProcessStepException("Missing mandatory fields.", 1);
}
}
I'm basically deleting every link from a step and based upon the request object I recreate the links. This saves me the effort to compare what links are deleted and added. The insert work like a charm Propel automatically creates the new links. Thing is it doesn't delete like it inserts. I've checked the object that is being persisted ($p) and I see the link being deleted but in the MySQL log there is absolutely no action being performed by Propel. It looks like a missing member from the link collection doesn't trigger a dirty flag or something like that.
Maybe I'm going about this the wrong way, I hope someone can offer some advice.
Thanks
To delete records, you absolutely always have to use delete. The diff method on the collection is extremely helpful when determining which entities need added, updated, and deleted.
Thanks to Ben I got on the right track, an explicit call for a delete is not needed. I came across a function called: setRelatedBy(ObjectCollection o) I use this function to provide a list of related objects, new objects are interpreted as inserts and omissions are interpreted as deletes.
I didn't find any relevant documentation regarding the problem so here's my code:
$p = $this->query->findPK($processStep['Id']);
$p->setId($processStep['Id']);
$p->setProcessesid($processStep['Processesid']);
$p->setCoordx($processStep['Coordx']);
$p->setCoordy($processStep['Coordy']);
if (isset($processStep['Flowid'])) $p->setFlowid($processStep['Flowid']);
if (isset($processStep['Applicationid'])) $p->setApplicationid($processStep['Applicationid']);
//Get related records, same as populaterelation
$currentLinks = $p->getLinksRelatedByFromstep();
$links = new \Propel\Runtime\Collection\ObjectCollection();
//Check for still existing links add to new collection if so.
//This is because creating a new Link instance and setting columns marks the object as dirty creating an exception due to duplicate keys
foreach ($currentLinks as $currentLink) {
foreach ($processStep['Links'] as $link) {
if (isset($link['Linkid']) && $currentLink->getLinkid() == $link['Linkid']) {
$links->prepend($currentLink);
break;
}
}
}
//Add new link objects
foreach ($processStep['Links'] as $link) {
if (!isset($link['Linkid'])) {
$newLink = new \Link();
$newLink->setFromstep($link['Fromstep']);
$newLink->setTostep($link['Tostep']);
$links->prepend($newLink);
}
}
//Replace the collection and save the processstep.
$p->setLinksRelatedByFromstep($links);
$p->save();
Related
I know that in Doctrine (as a general rule) it is better to flush() after persisting all the entities/objects to the database, but in the following case I think it could be useful to do the opposite.
Example:
Imagine that you are cycling through a list of sport results like this one:
playerA_unique_tag (string), playerB_unique_tag (string), result
In the database, playerA and playerB are FOREIGN KEYS (that point to a User entity). So, the database structure would be similar to this one:
Match record
id, playerA_fk, playerB_fk, result
User records
id, playerA_unique_tag, (etc... many other fields)
id, playerB_unique_tag, (etc... many other fields)
Example of a script
$sportResultsArray = array();
foreach($sportResultsArray as $sportResult){
$playerA_tag = $sportResult["$playerA_unique_tag"];
$db_playerA = db->getRepository("App:User")->findOneByTag($playerA);
if(!$db_playerA){
$db_playerA = new User();
$db_playerA ->setPlayer_unique_tag($playerA_tag);
$em->persist($db_playerA );
}
$match = new Match();
$match ->setplayerA($db_playerA );
/*Same thing would be done for playerB*/
$em->persist($match );
}
Problem:
Of course playerA will play MULTIPLE matches, and each time I have to somehow retrieve the corresponding User object and pass it to the new Match object.
But how can I do that if I haven't flushed playerA User object yet.
The only two alternatives I can think of are:
1- Flushing the User entity (and ONLY the User entity) after it is created
2- Create a temporary array of objects like this:
array('playerA_unique_tag' => playerA_Object, etc.)
Problem with option_1:
I have tried $em->flush($db_playerA); but every entity that was persisted to the Entity Manager also gets flushed (contrary to what written here: http://www.doctrine-project.org/api/orm/2.5/source-class-Doctrine.ORM.EntityManager.html#338-359). Basically, the result is the same as $em->flush();
Problem with option_2:
Isn't it a bad and inefficient workaround?
Consider to work with in-memory registry of players as following:
// init registry
$players = [];
foreach ($sportResultsArray as $sportResult) {
$players[$sportResult["$playerA_unique_tag"]] = null;
$players[$sportResult["$playerB_unique_tag"]] = null;
}
// fetch all at once
$existing = $db->getRepository("App:User")->findBy(['tag' => array_keys($players)]);
// fill up the registry
foreach ($existing as $player) {
$players[$player->getTag()] = $player;
}
// match them up
foreach ($sportResultsArray as $sportResult) {
$playerA_tag = $sportResult["$playerA_unique_tag"];
if ($players[$playerA_tag] === null) {
$players[$playerA_tag] = new User();
$players[$playerA_tag]->setPlayer_unique_tag($playerA_tag);
$em->persist($players[$playerA_tag]);
}
$match = new Match();
$match->setplayerA($players[$playerA_tag]);
/*Same thing would be done for playerB*/
$em->persist($match);
}
// finally
$em->flush();
I want to delete all record according to id and then insert record in same table,I tried many ways but can't find solution please help me.
Basically as per the document id i want to delete all document but it is not working.
Here is my controller code:
foreach ($receievers as $user) {
$this->shareRepo->deleteSharedDoc($resourceId);
$this->shareRepo->saveshareSharedDoc($resourceId, $user->id,$this->getCurrentUser());
}
The repository code:
function saveSharedDoc($resourceId, $sharedWith, $resourceOwnerId){
$shareDocs = new ShareDocs;
$shareDocs->resource_id = $resourceId;
$shareDocs->shared_with = $sharedWith;
$shareDocs->user_id = $resourceOwnerId;
$shareDocs->shared_on = $this->getCurrentDateTime();
$shareDocs->token = str_random(20);
$shareDocs->save();
return $shareDocs->token;
}
function deleteSharedDoc($resourceId){
$network = ShareDocs::where('resource_id','=',$resourceId);
$result=$network->delete();
return $result;
}
Please help me out
It's seems you're doing it correctly. But there are two things that you have to change.
You are calling to saveshareSharedDoc method within foreach loop to save data. but actual method name on your repo is saveSharedDoc. (there two "share" words on loop)
you can return deleted rows directly return ShareDocs::where('resource_id', $resourceId)->delete();
I have two models, Position and User. They have a One to many relation between them.
When I delete a position, I want all the related users to be detached from that position and attached to a different one (found by id).
I'm sure it's simple enough, but I've tried doing it in a foreach loop, without success:
public function postDelete($position)
{
$positionMembers = $position->users()->get();
foreach ($positionMembers as $member) {
$member->position_id = '4';
// fixed copy/paste var name error
$member->save()
}
// Was the position deleted?
if($position->delete()) {
// Redirect to the position management page
return Redirect::to('admin/positions')->with('success', Lang::get('admin/positions/messages.delete.success'));
}
// There was a problem deleting the position
return Redirect::to('admin/positions')->with('error', Lang::get('admin/positions/messages.delete.error'));
}
I've also tried:
$member->position()->associate($this->position->find(4));
but it doesn't work either. The position_id field always remains unchanged. Is there a more recommended way?
First off define without success, because it says nothing, and the code you're showing should work.
Anyway, I would suggest different approach, for using Eloquent save in a loop isn't the best way:
public function postDelete($position)
{
DB::transaction(function () use ($position, &$deleted) {
// run single query for update
$position->users()->update(['position_id' => 4]);
// run another query for delete
$deleted = $position->delete();
});
// Was the position deleted?
if($deleted) {
// Redirect to the position management page
return Redirect::to('admin/positions')->with('success', Lang::get('admin/positions/messages.delete.success'));
}
// There was a problem deleting the position
return Redirect::to('admin/positions')->with('error', Lang::get('admin/positions/messages.delete.error'));
}
With this, you make sure users don't get updated if there's some error(exception thrown) when deleting position and you execute 2 queries, no matter how many users there are to update.
TL;DR is at the end to cut to the chase.
I have a lot of belongsTo() models that can have any number of records, and I'm trying to bind them to an edit form. I have the following foreach that creates the form elements:
#foreach ($department->department_10 as $key => $value)
{{ Form::select(
'department_10['.(isset($value->pk_department_10)?$value->pk_department_10:0).']',
$department_10_opts,
(isset($value->department_10)?$value->department_10:''),
array('class'=>'form-control input-md department_10', 'placeholder'=>'Other Types of Service')) }}
#endforeach
Since there can be 0 records (rows?) that belong to the model, to simplify my #foreach, I wanted to create a "blank" instance of the model. Additionally, because I'm going to have to deal with about 70 more cases like this, I created a function that would create the new blank model. Here's the function (in my controller for lack of a better place):
function mkBlankModel($parentModel, $newModel){
if(count($parentModel->$newModel) === 0){
$parentModel->$newModel[0] = new $newModel();
$parentModel->$newModel[0]->fk_department = $parentModel->pk_department;
$parentModel->$newModel[0]->$newModel = '';
}
return $parentModel;
}
When I run it, I don't get any errors, but I do get unexpected results and I can't really make sense of them:
Test Step 1) View the edit page while loading a record with 2 department_10's. It works as expected; loads two fields properly.
Test Step 2) View the edit page while loading a record with 0 department_10's. The page loads but without any fields. Because apparently my function didn't work, so I verify by dumping dump($department->$department_10) and it confirms this.
Test Step 3) I replace the $parentModel->$newModel[0] with $parentModel->department_10[0] like so:
function mkBlankModel($parentModel, $newModel){
if(count($parentModel->$newModel) === 0){
$parentModel->department_10[0] = new $newModel();
$parentModel->department_10[0]->fk_department = $parentModel->pk_department;
$parentModel->department_10[0]->$newModel = '';
}
return $parentModel;
}
And both scenarios (with records and without) work just fine. So my problem likely isn't Laravel specific, but I'm just curious how I can accomplish this.
TL;DR:
I'm trying to create a model instance for a parent model, if one doesn't exist, so a blank field will be created by my #foreach loop in my edit.blade.php's form. I can do this just fine if I manually spell out the model's name when creating it, but since I'll be doing this frequently, I'd prefer to define the class, and populate it with a string.
So I figured it out; I needed to wrap the $newModel in curly-brackets:
function mkBlankModel($parentModel, $newModel){
if(count($parentModel->$newModel) === 0){
$parentModel->{$newModel}[0] = new $newModel();
$parentModel->{$newModel}[0]->fk_department = $parentModel->pk_department;
$parentModel->{$newModel}[0]->$newModel = '';
}
return $parentModel;
}
More info here: http://php.net/manual/en/language.types.string.php#language.types.string.parsing.complex
I am building an application where the user can edit some data and then gets presented with a screen where he can confirm (and comment on) his edits.
In the confirmation form I display the changes that have been made to the entity. This works for "normal" fields. Here is some code that works for checking a single field:
// create $form
// bind $form
if ($form->isValid() {
$data = $form->getData();
// example, get changes of a "normal" field
if ($data['color'] != $entity->getColor()) {
// do something with changes
}
}
But I can't do the same for a relation (example ManyToMany with Users) :
if ($data['users'] != $entity->getUsers()
doesn't work because $data['users'] and $entity->getUsers() refer to the same persistent collection. It is possible to call this function to see if there are changes:
if ($data['users']->isDirty())
but it isn't possible to see what changes were made.
The second problem with the above is that if all items are removed from the persistent collection, Doctrine does not mark it as "changed" (isDirty() = true), so I can't catch the specific change where the user removes all "users" from the entity in the form.
Please note that the code all works, the only problem I have is that I am unable to view/process the changes made on the confirmation step.
Doctrine\ORM\PersistentCollection has internal API (public) methods getSnapshot, getDeleteDiff, getInsertDiff that can be used during lifecycle events of the Doctrine\ORM\UnitOfWork. You could for example check the insert diff of a persistent collection during onFlush.
Solved it like this:
1) To get changes that will be made directly to the Entity, use the following:
// create form
// bind form
// form isValid()
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
$changeset = $uow->getEntityChangeSet($entity);
print_r($changeset);
2a) To get changes to the relations, use the answer from Lighthart above:
$oldUsers = $entity->getUsers()->toArray();
// bind form
// form isValid
$newUsers = $entity->getUsers()->toArray();
// compare $oldUsers and $newUsers
2b) Use these methods on Persistent Collection to find inserts / deletes:
$newUsers = $entity->getUsers();
$inserted = $newUsers->getDeleteDiff();
$deleted = $newUsers->getInsertDiff();
The only problem with (2b) is that if ALL users are removed and none added then getDeleteDiff() is empty which appears to be a Doctrine bug/idiosyncrasy
Store the original collection in a variable before bind and then compared the new collection after bind. PHP has quite a few array comparison functions, and collections are readily turned into native arrays by $collection->toArray();
eg:
// create form
$oldusers=$entity->getUsers()->toArray();
// bind form
if ($form->isValid() {
$data = $form->getData();
if ($data['users'] != $oldusers) {
// do something with changes
}
}