Propel saving tags - propel

I have a Post model and I am inserting the Tags for the posts like the one below. When I am editing there can be some tags being removed. So what is the right way to remove the tags and re-insert ?
$post->setTitle($data['title']);
$post->setBody($data['body']);
$post->setSlug($data['slug']);
$tags = explode(',', $data['tags']);
// Want to remove the tags
foreach ($tags as $tag) {
$tagobj = TagQuery::create()->findOneByName($tag);
if (! $tagobj) {
$tagobj = new Tag();
$tagobj->setName($tag);
$tagobj->save();
}
$post->addTag($tagobj);
}
$post->save();
Does propel can insert in a single query or is this a worst approach .
I have asked the question in propel group, but :-( https://groups.google.com/d/msg/propel-users/x6PH_DwLtVE/H84o1cu4W4kJ
The full source code is here
The goal is to re-save the tags, when one tag is removed or one tag is added. What to do ? .
1st priority.
Optimization is second priority.
Update2 :
I modified the code as something like below with the reply I got
$tags = explode(',', $data['tags']);
foreach ($tags as $tag) {
$tagobj = TagQuery::create()->findOneByName($tag);
if (! $tagobj) {
$tagobj = new Tag();
$tagobj->setName($tag);
$tagobj->save();
}
}
// var_dump($tags);
$tagcollection = TagQuery::create()->findByName($tags);
// var_dump($tagcollection);
// exit;
$post->setTags($tagcollection);
Now I am getting array to string conversion error .
Notice: Array to string conversion in /var/www/harisample/vendor/propel/propel/src/Propel/Runtime/Connection/StatementWrapper.php on
line 171 Call Stack: 0.0001 131940
{main}() /var/www/harisample/web/index.php:0 0.0243 1259056
Aura\Framework\Bootstrap\Web->exec() /var/www/harisample/web/index.php:13 0.0243 1259108
Aura\Framework\Web\Controller\Front->exec() /var/www/harisample/package/Aura.Framework/src/Aura/Framework/Bootstrap/Web.php:71 0.0243 1259436
Aura\Framework\Web\Controller\Front->request() /var/www/harisample/package/Aura.Framework/src/Aura/Framework/Web/Controller/Front.php:168 0.0314 1694584
Aura\Web\Controller\AbstractPage->exec() /var/www/harisample/package/Aura.Framework/src/Aura/Framework/Web/Controller/Front.php:222 0.0316 1699500
Aura\Web\Controller\AbstractPage->action() /var/www/harisample/package/Aura.Web/src/Aura/Web/Controller/AbstractPage.php:168 0.0316 1699576
Aura\Web\Controller\AbstractPage->invokeMethod() /var/www/harisample/package/Aura.Web/src/Aura/Web/Controller/AbstractPage.php:206 0.0316 1699960
ReflectionMethod->invokeArgs() /var/www/harisample/package/Aura.Web/src/Aura/Web/Controller/AbstractPage.php:231 0.0316 1699976
Hari\Sample\Web\Post\Page->actionEdit() /var/www/harisample/package/Aura.Web/src/Aura/Web/Controller/AbstractPage.php:231 0.0856 5802116 1
Hari\Sample\Model\Base\Post->save() /var/www/harisample/package/Hari.Sample/src/Hari/Sample/Web/Post/Page.php:127 0.0874 5808356 1
Hari\Sample\Model\Base\Post->doSave() /var/www/harisample/package/Hari.Sample/src/Hari/Sample/Model/Base/Post.php:930 0.0881 5813420 1
Hari\Sample\Model\Base\PostTagQuery->delete() /var/www/harisample/package/Hari.Sample/src/Hari/Sample/Model/Base/Post.php:1000 0.0881 5813700 1
Propel\Runtime\ActiveQuery\ModelCriteria->delete() /var/www/harisample/package/Hari.Sample/src/Hari/Sample/Model/Base/PostTagQuery.php:557 0.0881 5814628 1
Propel\Runtime\ActiveQuery\Criteria->doDelete() /var/www/harisample/vendor/propel/propel/src/Propel/Runtime/ActiveQuery/ModelCriteria.php:1324 0.0883 5817716 1
Propel\Runtime\Connection\StatementWrapper->execute() /var/www/harisample/vendor/propel/propel/src/Propel/Runtime/ActiveQuery/Criteria.php:2408 0.0883 5817772 1
PDOStatement->execute() /var/www/harisample/vendor/propel/propel/src/Propel/Runtime/Connection/StatementWrapper.php:171
Thanks

I guess your goal is optimizing the number of (mysql ?) request, isn't it ?
I think this is not possible, mainly because Propel object saving rely on other steps - think of preSave(), postSave(), behaviors also - needing a single saving query execution for every object.
By trying to make an optimized query, you would loose the benefit of the Propel saving workflow and relations management.
On the other hand, I am not sure about the way clearTags() really works, I think it just remove object references but does not delete records in the database.
You must have in your BasePost.php file a setTags() method that will actually replace any previous relation with the new object collection you provide.

I'm making sth similar, but still something is not good. Maybe you can try, maybe it will work on your project.
$tagNames = $tags->getTags();
$tagsArray = explode(',', $tagNames);
$postTagToDelete = PostTagQuery::create()->filterByPostId($post->getId())->find();
if ($postTagToDelete) {
$postTagToDelete->delete();
}
foreach ($tagsArray as $tagName) {
$tag = TagQuery::create()->filterByName($tagName)->findOne();
//when i find an existing tag,
// there is no need to create another one
//I just simply add it **(it's not working here)**
if ($tag != null) {
$post->addTag($tag);
} else {
//when tag is new
$tag = new Tag();
$tag->setName($tagName);
$post->addTag($tag);
}
}
$post->save()
See my problem here.
To be more specific, let's pretend that I have four elements in $tagsArray.
[first, second, third, fourth]
Every of them IS the database already, so it gonna enter first if four times.
The problem is that only second, third and fourth will be saved. There will be no first . Why?
Another example is that if I have array[first] and do the same (first is in the databse already) it will be saved every only the second time. So I have sth like is in database, database empty, is in database, database empty,[...] every request attempt.

Related

Laravel - Eloquent multiple delete vs destroy array

I was wondering what's the best way to destroy multiples database entries with ELOQUENT and I don't find a way to determine that.
So I have 3 array of id's (2 with ints, 1 with strings).
Is it better to go with a foreach and ->delete() every entry or destroy the array ?
When I look at the destroy function, it states the following :
We will actually pull the models from the database table and call
delete on each of them individually so that their events get fired
properly with a correct set of attributes in case the developers
wants to check these.
And the code clearly shows :
$key = $instance->getKeyName();
foreach ($instance->whereIn($key, $ids)->get() as $model) {
if ($model->delete()) {
$count++;
}
}
So I guess there's no real difference and the destroy function is just to avoid the use of a foreach. Can anyone confirm or inform and explain ?
Thanks :)
At first you need to know the difference between destroy and delete, destroy is think to be used for removing an entity (object/model) and delete for being used on a query builder.
Both are different ways but they have the same purpose you can do like:
Model::destroy(array(1, 2, 3));
or
$ids = explode(",", [1,2,3]);
$model->find($ids)->each(function ($model, $key) {
//Do things before deleting
$model->delete();
});
But as you can see the first one is just more direct, on the second one you can do custom things before deleting.

Laravel - Collection with relations take a lot of time

We are developing an API with LUMEN.
Today we had a confused problem with getting the collection of our "TimeLog"-model.
We just wanted to get all time logs with additional informationen from the board model and task model.
In one row of time log we had a board_id and a task_id. It is a 1:1 relation on both.
This was our first code for getting the whole data. This took a lot of time and sometimes we got a timeout:
BillingController.php
public function byYear() {
$timeLog = TimeLog::get();
$resp = array();
foreach($timeLog->toArray() as $key => $value) {
if(($timeLog[$key]->board_id && $timeLog[$key]->task_id) > 0 ) {
array_push($resp, array(
'board_title' => isset($timeLog[$key]->board->title) ? $timeLog[$key]->board->title : null,
'task_title' => isset($timeLog[$key]->task->title) ? $timeLog[$key]->task->title : null,
'id' => $timeLog[$key]->id
));
}
}
return response()->json($resp);
}
The TimeLog.php where the relation has been made.
public function board()
{
return $this->belongsTo('App\Board', 'board_id', 'id');
}
public function task()
{
return $this->belongsTo('App\Task', 'task_id', 'id');
}
Our new way is like this:
BillingController.php
public function byYear() {
$timeLog = TimeLog::
join('oc_boards', 'oc_boards.id', '=', 'oc_time_logs.board_id')
->join('oc_tasks', 'oc_tasks.id', '=', 'oc_time_logs.task_id')
->join('oc_users', 'oc_users.id', '=', 'oc_time_logs.user_id')
->select('oc_boards.title AS board_title', 'oc_tasks.title AS task_title','oc_time_logs.id','oc_time_logs.time_used_sec','oc_users.id AS user_id')
->getQuery()
->get();
return response()->json($timeLog);
}
We deleted the relation in TimeLog.php, cause we don't need it anymore. Now we have a load time about 1 sec, which is fine!
There are about 20k entries in the time log table.
My questions are:
Why is the first method out of range (what causes the timeout?)
What does getQuery(); exactly do?
If you need more information just ask me.
--First Question--
One of the issues you might be facing is having all those huge amount of data in memory, i.e:
$timeLog = TimeLog::get();
This is already enormous. Then when you are trying to convert the collection to array:
There is a loop through the collection.
Using the $timeLog->toArray() while initializing the loop based on my understanding is not efficient (I might not be entirely correct about this though)
Thousands of queries are made to retrieve the related models
So what I would propose are five methods (one which saves you from hundreds of query), and the last which is efficient in returning the result as customized:
Since you have many data, then chunk the result ref: Laravel chunk so you have this instead:
$timeLog = TimeLog::chunk(1000, function($logs){
foreach ($logs as $log) {
// Do the stuff here
}
});
Other way is using cursor (runs only one query where the conditions match) the internal operation of cursor as understood is using Generators.
foreach (TimeLog::where([['board_id','>',0],['task_id', '>', 0]])->cursor() as $timelog) {
//do the other stuffs here
}
This looks like the first but instead you have already narrowed your query down to what you need:
TimeLog::where([['board_id','>',0],['task_id', '>', 0]])->get()
Eager Loading would already present the relationship you need on the fly but might lead to more data in memory too. So possibly the chunk method would make things more easier to manage (even though you eagerload related models)
TimeLog::with(['board','task'], function ($query) {
$query->where([['board_id','>',0],['task_id', '>', 0]]);
}])->get();
You can simply use Transformer
With transformer, you can load related model, in elegant, clean and more controlled methods even if the size is huge, and one greater benefit is you can transform the result without having to worry about how to loop round it
You can simply refer to this answer in order to perform a simple use of it. However incase you don't need to transform your response then you can take other options.
Although this might not entirely solve the problem, but because the main issues you face is based on memory management, so the above methods should be useful.
--Second question--
Based on Laravel API here You could see that:
It simply returns the underlying query builder instance. To my observation, it is not needed based on your example.
UPDATE
For question 1, since it seems you want to simply return the result as response, truthfully, its more efficient to paginate this result. Laravel offers pagination The easiest of which is SimplePaginate which is good. The only thing is that it makes some few more queries on the database, but keeps a check on the last index; I guess it uses cursor as well but not sure. I guess finally this might be more ideal, having:
return TimeLog::paginate(1000);
I have faced a similar problem. The main issue here is that Elloquent is really slow doing massive task cause it fetch all the results at the same time so the short answer would be to fetch it row by row using PDO fetch.
Short example:
$db = DB::connection()->getPdo();
$query_sql = TimeLog::join('oc_boards', 'oc_boards.id', '=', 'oc_time_logs.board_id')
->join('oc_tasks', 'oc_tasks.id', '=', 'oc_time_logs.task_id')
->join('oc_users', 'oc_users.id', '=', 'oc_time_logs.user_id')
->select('oc_boards.title AS board_title', 'oc_tasks.title AS task_title','oc_time_logs.id','oc_time_logs.time_used_sec','oc_users.id AS user_id')
->toSql();
$query = $db->prepare($query->sql);
$query->execute();
$logs = array();
while ($log = $query->fetch()) {
$log_filled = new TimeLog();
//fill your model and push it into an array to parse it to json in future
array_push($logs,$log_filled);
}
return response()->json($logs);

Laravel Eloquent ORM Soft delete and restore many items for me at the same time

i think there is a bug in softdelete and restore with Eloquent ORM in Laravel .
i have a table like these
My Table Image
UPDATED : i made a video now for the problem : VIDEO OF MY PROBLEM
and my code to softdelete is
try {
$p= Post::findOrFail($id);
} catch (Exception $e) {
return "error";
}
$p->delete();
return "Post deleted";
and my code for restore is :
`try {
$p= Post::withTrashed()->findOrFail($id);
} catch (Exception $e) {
return "error";
}
$p->restore();
return "Post restored";`
..
the problem is sometimes :
when i delete post number 3 , it delete number 3 and number 2 at the same time and sometimes work fine , n with restore same thing too sometimes i restore for example number 3 n it restore number 1 also at the same time.
i didnt understand why , i tried different code like 'where' statement n 'find' n 'destroy'.
like :
Post::withTrashed()->where('id', $id)->restore();
Post::find($id)->delete();
but same problem sometimes delete n restore work normally sometimes go crazy n delete or restore many items at the same time .
i tried also different version of laravel 5.2 n 5.4 .
i use mysql 5.6.35 , mamp server php 7.1.1.
I hope this code helpful for you.
$products = Product::onlyTrashed()
->whereIn('id', $request->product_ids)
->restore();
You can delete it by calling on Model instance:
$post = Post::find($id);
$post->delete();
Since you already know your model Id, you might do this:
Post::destroy($id)
Or delete it by Query:
$deletedPost = Post::where('id', $id)->delete();
Btw, i dont think you need those try/catch there. Try without it. Also, if you have problems, try to do dd($id) and see what it dumps. If its ok, move on and do dd($p) and see if you are getting the proper model.
Take a good look at your variables, dump them or if you can use debugger, go line by line to see where it goes wrong.
Hope this helped a bit :)
Good luck with problem!
You don't need try catch block to soft delete. Eloquent provides delete() and restore().
Add Illuminate\Database\Eloquent\SoftDeletes trait
and protected $dates = ['deleted_at'] to your model
add a deleted_at column to your database table(use helper/manually migrate).
Now you can call delete() and restore() method onto your model.
This will set current datetime value to the deleted_at column (initially they are null).
Hope this will help.
laravel-5.4 doc: soft-delete

Magento search queries yielding empty results in API

I have this chunk of code:
//to-do
public function searchVehicles($terms, $offset=1, $order='ASC')
{
if (trim($terms) == '') {
return array();
}
$query = $this->_getQuery($terms);
$query->setStoreId(1);
if ($query->getId()) {
$query->setPopularity($query->getPopularity()+1);
}
else {
$query->setPopularity(1);
}
$query->prepare();
$query->save();
$collection = Mage::getResourceModel('catalog/product_collection');
$collection->getSelect()->joinInner(
array('search_result' => $collection->getTable('catalogsearch/result')),
$collection->getConnection()->quoteInto(
'search_result.product_id=e.entity_id AND search_result.query_id=?',
$query->getId()
),
array('relevance' => 'relevance')
);
$collection->setStore(1);
//Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($collection);
//Mage::getSingleton('catalog/product_visibility')->addVisibleInSearchFilterToCollection($collection);
return $this->_listProductCollection($collection, $offset, $order);
}
Which is inside a Resource class and reachable via SOAP.
Before we start: Yes, I remember to do the cache flushing and recompiling process - I clarify because this is an usual issue to newbies like me xDDD.
Now: I can access such method but it returns [].
SPECIAL NOTE: $this->_listProductCollection($collection, $offset, $order); WORKS since i'm using the same method in other collections fetched from other methods in the same resource, and have no trouble at all.
Let me review the intention of my code since I'm a newbie at Magento (I'm using version 1.6.2).
The code is based on the CatalogSearch/ResultController controller's indexAction() method, and tried to learn about it.
An empty query will yield an empty result and will not bother the Magento search engine.
There's only a Store (id = 1) in the site and the search query is created like this:
private function _getQuery($terms)
{
$query = Mage::getModel('catalogsearch/query')->loadByQuery($terms);
if (!$query->getId()) {
$query->setQueryText($terms);
}
return $query;
}
The query increases it's popularity (I took this code from the controller. I assume this is for statistical purposes only).
The query is prepared (I think this means: the MySQL internal query is prepared) so I can fetch it later.
The query is saved - AFAIK this means that the query results are iterated and cached so a subsequent same query will only fetch the stored results instead of processing the search again.
At this point the query will have an ID.
I get the whole Product collection, and join it with the search result table. SEEMS that the results table has - at least (queryId, matchedProductId). I only keep the products having IDs in the matched results, and from store 1.
I list the products.
Note that the filters are currently commented.
However, the returned list is [] (an empty list) when I hit this API entry point, althought searching in the usual search bar gives me the expected result.
Question: What am I missing? What did I misunderstood in the process?

CakePHP Pagination sort() on Related Models

I have two models: Plans and PlanDetails.
Relationship is: PlanDetails hasMany Plans. Plans belongTo PlanDetails.
In the PlanDetails view.ctp, I am pulling in related Plans.
I am trying to sort the Plans by ANY field (I've tried them all), and I cannot get it working. I assume I am overlooking something very simple.
Here is my base code:
PlanDetail >> view.ctp:
...foreach ($planDetail['Plan'] as $plan_edit) :
$class = null;
if ($i++ % 2 == 0) {
$class = ' class="altrow"';
}...
<?php echo $this->Paginator->sort('Plan ID', 'Plan.id'); ?>...
...<?php echo $plan_edit['id']; ?>
plan_details_controller.php:
...function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid plan detail', true));
$this->redirect(array('action' => 'index'));
}
$this->PlanDetail->recursive = 2; // To run the editable form deeper.
$this->set('planDetail', $this->PlanDetail->read(null, $id));
$this->set('plan', $this->paginate('Plan'));
}...
I should add, no errors are being thrown and the sort() arrows on the ID field are showing as expected, but the sort order DOES not change when clicked either way.
Sorry, I'm not able to comment on the question itself, but I've noticed that in your action, you set planDetail to be the PlanDetail record you read (with recursive set to 2), and then you set plan to be the result of the paginate call.
Then, in your view template, you're iterating over $planDetail's contained Plan association, like this:
foreach ($planDetail['Plan'] as $plan_edit):
But in order to get the sorting and pagination done, you need to be displaying the results of the paginate call i.e. iterate over the records contained in $plan.
Do a debug($plan) in your view template to see what results you get there and to see if the records' ordering changes when you sort by different fields.
Also, perhaps you're using syntax I'm not aware of, but if you simply call $this->paginate('Plan') in your controller, I don't know that you're going to get only the related Plan records for your particular PlanDetail record. (There's nothing tying the $id passed into your view action with the Plan records.) You might need to add some conditions to the paginate call, like so:
$this->paginate['Plan'] = array('conditions' => array('Plan.plan_detail_id' => $id));
$this->set('plans', $this->paginate('Plan'));
Here is what I did to solve this. Based on some helpful direction from johnp & tokes.
plan_details/view.ctp:
...$i = 0;
foreach ($plan as $plan_edit) : // note $plan here.
}...
In my plan_details_controller.php view action:
$conditions = array("Plan.plan_detail_id" => "$id");
$this->set('plan', $this->paginate('Plan', $conditions)); // note "plan" in the first argument of set (this is required to pass the results back to the view). Also. The $condition is set to return ONLY plans that match the plan_detail_id of id (on the plan_details table).
And in my view, in order to get my results (because I changed the array name), I had to change the way I was getting the values to:
$plan_edit['Plan']['modified'] // note I placed ['Plan'] in front of these calls as the array called for this to get the data...
Well until the next problem! SOLVED.

Resources