Doctrine update entity in loop, persist or flush? - symfony

I have multiple loops like :
$bets = $this->em->getRepository('AppBundle:Bet')->getBetsForMatch($match_id);
foreach ($bets as $key => $bet) {
$devices = $this->em->getRepository('AppBundle:Device')->findBy(array('user' => $bets->getUser()));
foreach ($devices as $key => $device) {
//HERE I SEND A PUSH NOTIFICATION
if($this->rms_push->send($message)){
$device->getUser()->setBadge($device->getUser()->getBadge() + 1);
$this->em->flush();
}
}
}
So, I get all bets for a match, for each bet I get all devices saved for the user, and after that I need to update my user with : $device->getUser()->setBadge($device->getUser()->getBadge() + 1);
For now, I flush each time but I think there is a better way, ideas ?

You need only one flush, out of your loop:
foreach ($bets as $key => $bet) {
$devices = $this->em->getRepository('AppBundle:Device')->findBy(array('user' => $bets->getUser()));
foreach ($devices as $key => $device) {
//HERE I SEND A PUSH NOTIFICATION
if($this->rms_push->send($message)){
$device->getUser()->setBadge($device->getUser()->getBadge() + 1);
}
}
}
$this->em->flush();
Calling $this->_em->persist($obj) involves to create a new entry.
If you need to create or update depending on the entry exists or not, look at EntityManager::merge .
To preserve memory usage for large number of entries, look at batch processing.
Note SensioLabs insight (PHP source code quality analysis) raises a warning if your code calls EntityManager::flush inside a loop.

Related

Not the required data is displayed for the new user, because the data is taken from the cache of the previous user

I made a custom module that displays the weather in a particular city.
But I got these comments after the code review:
1. Interesting question, what happens to your cache data, if the site first comes to a person from the city of London, and then Paris?
As I understand it, it means that a person from Paris, when he enters the site, will see the weather in London, because it will be taken from the cache. But I put the data in the cache so that there are not too many requests, I made a request once, put the data in the cache, and the next time I took the data from the cache.
2. In the small function, you are calling the http://ip-api.com/json/ endpoint twice. What happens when the site is visited by a thousand people per minute?
Here, I do not understand what the problem is. If it meant that the connection limit to the resource would be exhausted, then how to solve this problem? In the getCity() function, put the data in the cache in the same way as I do in the build() function? But then the same problem comes up as in the first remark, if a person from another city visits the site, then the data from the cache (name of the city of London) will be taken and not the name of his real city. How then to be?
Can you please tell me what needs to be changed in my code?
Below I will write a slightly reduced code for my php file. Full version here: https://phpsandbox.io/n/sweet-forest-1lew-1wmof
// ....
use Drupal\Core\Cache\CacheBackendInterface;
use GuzzleHttp\Client;
//....
public function getCity() {
$ip = '193.62.157.66'; // static because for testing
try {
$response_ip = $this->httpClient->get('http://ip-api.com/json/' . $ip);
$response_data_ip = $response_ip->getBody();
$data_ip = json_decode($response_data_ip);
if ($data_ip->status == 'success') {
return $data_ip->city;
}
else {
return $this->configFactory->get('sydneypro_weather.settings')->get('weather_city');
}
}
catch (RequestException $e) {
return FALSE;
}
}
public function build() {
$client = $this->httpClient;
$api_key = $this->configFactory->get('sydneypro_weather.settings')->get('weather_api_key');
$cid = 'sydneypro_weather';
$weather_config = $this->configFactory->get('sydneypro_weather.settings');
if (!$weather_config) {
$this->logger->get('sydneypro_weather')->error('Config "sydneypro_weather.settings" is missing4');
return [];
}
if (empty($api_key) || empty($this->getCity())) {
return [
'#type' => 'markup',
'#markup' => $this->t('Please enter your API key and City in the Admin panel to see the weather'),
];
}
try {
if ($cache = $this->cacheBackend->get($cid)) {
$data = $cache->data;
}
else {
$response = $client->get('http://api.openweathermap.org/data/2.5/weather?q=' . $this->getCity() . ',&appid=' . $api_key . '&units=metric');
$response_data = $response->getBody();
$data = json_decode($response_data);
$this->cacheBackend->set($cid, $data, $this->time->getRequestTime() + 21600);
}
$build = [
'#theme' => 'weather_block',
'#data' => $data,
'#attached' => [
'library' => [
'sydneypro_weather/sydneypro_weather',
],
],
];
return $build;
}
// ....
You need to display some data according to a given city, so you should cache the data on a per-city basis, using a cache id that allows you to retrieve data for a specific city. In other words, $cid must contain a city name or identifier.
$city = $this->getCity();
$cid = 'sydneypro_weather:' . $city;
Using a variable for $city prevents getCity() to be called twice. Also you could set a cache that maps IP adresses to their corresponding city but it might not be a good idea as the number of (possible) distinct adresses populating the cache table could be very high.
Another approach would be to get the region/city of the user from his browser using javascript and/or cookies, and call the api only for those that does not share their location.

insert multiple rows is slow in symfony 3

I am doing insert multiple rows in symfony. My code inserts very slowly. Does anyone have any ideas that won't help me! Thank you !
$manager = $this->getContainer()->get('doctrine')->getEntityManager();
// data : object
foreach ($data as $value) {
$entitty = $stkModel->getRepository()->findOneBy(['phone' => $value->getPhone()]);
if ($entity) {
$stk = $stkModel->getEntity();
$stk->setName('mina');
$stk->setStatus(1);
$manager->persist($stk);
$manager->flush();
}
if ($stk) {
$entitty = $titleModel->getRepository()->findOneBy(['stkId' => $stk->getId()]);
$title = $titleModel->getEntity();
$title->setName('dev');
$title->setOrder(2);
$manager->persist($title);
$manager->flush();
}
// Here I take $title->getId();
}
First, you need to call persist only at entities that are created with new. But this will not give you speed performance.
The flush slows the application down every time you use it, because here the query will be send to the database. To speed up your application you have to minimize the use of flush. In your case, just do one flush after you updated your entites.
$manager = $this->getContainer()->get('doctrine')->getEntityManager();
// data : object
foreach ($data as $value) {
$entitty = $stkModel->getRepository()->findOneBy(['phone' => $value->getPhone()]);
if ($entity) {
$stk = $stkModel->getEntity();
$stk->setName('mina');
$stk->setStatus(1);
// $manager->persist($stk); <-- Don't need persist here because your object is coming out from the entity manager
}
if ($stk) {
$entitty = $titleModel->getRepository()->findOneBy(['stkId' => $stk->getId()]);
$title = $titleModel->getEntity();
$title->setName('dev');
$title->setOrder(2);
// $manager->persist($stk); <-- same as mention above
}
}
// call flush once at the end
$manager->flush();
In some cases, you need to call flush in every loop in a foreach. That might happen if you need to query it. But in your case you just search for a id, which didn't change anyway.

Php error Notice:Undefined offset:255715

hello I'm working on a symfony2 project , and when I'm trying to create associative array this issue happend => Notice: Undefined offset :25715
My code editor alert me that the error come from when I'am create my assoiative array $Tableau_comptes_dependants
here is code
foreach ($tableau_compte_fictifs as $tableau_compte_fictif) {
$Tableau_id_compte_fictifs[] = $tableau_compte_fictif["id"];
}// this array content two value 25715 and 31170
foreach ($Tableau_id_compte_fictifs as $Tableau_id_compte_fictif) {
$Mes_comptes_reels_dependants = $mes_comptesRepo-
>all_client_compte_dependant($Tableau_id_compte_fictif);
if (count($Mes_comptes_reels_dependants) > 0) {
foreach ($Mes_comptes_reels_dependants as
$Mes_comptes_reels_dependant)
{
if (!in_array($Mes_comptes_reels_dependant,
$Tableau_comptes_dependants[$Tableau_id_compte_fictif]))
{
$Tableau_comptes_dependants[$Tableau_id_compte_fictif[] =
$Mes_comptes_reels_dependant;
}
}
}
}
return new JsonResponse(
array(
'code' => 200,
'result' => true,
'comptes' => $Tableau_id_compte_fictifs,
)
);
please let me know what I'am doing wrong
The notice is generated from your call to in_array:
if (!in_array($Mes_comptes_reels_dependant,
$Tableau_comptes_dependants[$Tableau_id_compte_fictif]))
You try to access your array on an index that does not exist, 25715, since this is the value of your variable which you pass between the square brackets.
You should check first, if an index exists with isset, before accessing it.
That said, I think your code has a design flaw if you run into problems like this. You should try to refactor it or talk with your co workers about how to simplify it. I must admit, that I, although I could, won't try solving this problem for you, since your variable names are basically unreadable.

Analytics Real Time Active Users on Particular Page

I want to get the count of active users based on separate pages. What I'm getting is the count of Active Users on whole website.
I am using this code,
$metrics = 'ga:screenviews';
$optParams = array('dimensions' => 'ga:screenName');
try {
$results = $service->data_realtime->get(
$GA_VIEW_ID,
'rt:activeUsers',
$optParams);
} catch (apiServiceException $e) {
// Handle API service exceptions.
$error = $e->getMessage();
}
Is there any extra parameters to be added for getting for a particular page.
Yes, you need to apply filters to your query. Also please note, that dimensions and metrics differ from general reporting API, and start with rt:, in metrics, dimensions, and filters as well. Extending your code would look something like this:
$metrics = 'rt:screenViews';
$optParams = array('dimensions' => 'rt:screenName',
'filters' => 'rt:screenName==somescreen);
try {
$results = $service->data_realtime->get(
$GA_VIEW_ID,
'rt:activeUsers',
$optParams);
} catch (apiServiceException $e) {
// Handle API service exceptions.
$error = $e->getMessage();
}
Although not the latest version, but the syntax of filter operators is explained here.
Please find the full list of real time metrics and dimensions here.

Modifying a field collection programmatically missing hostEntity fields

I am trying to modify a field collection in a node that already exists so I can change an image on the first element in an array of 3. The problem is, the hostEntity info is not set when I do a entity_load or entity_load_single so when I do a:
$field_collection_item->save(true); // with or without the true
// OR
$fc_wrapper->save(true); // with or without the true
I get the following error:
Exception: Unable to save a field collection item without a valid reference to a host entity. in FieldCollectionItemEntity->save()
When i print_r the field collection entity the hostEntity:protected fields are indeed empty. My field collection is setup as follows:
field_home_experts
Expert Image <--- Want to change this data only and keep the rest below
field_expert_image
Image
Expert Name
field_expert_name
Text
Expert Title
field_expert_title
Text
Here is the code I am trying to use to modify the existing nodes field collection:
$node = getNode(1352); // Get the node I want to modify
// There can be up to 3 experts, and I want to modify the image of the first expert
$updateItem = $node->field_home_experts[LANGUAGE_NONE][0];
if ($updateItem) { // Updating
// Grab the field collection that currently exists in the 0 spot
$fc_item = reset(entity_load('field_collection_item', array($updateItem)));
// Wrap the field collection entity in the field API wrapper
$fc_wrapper = entity_metadata_wrapper('field_collection_item', $fc_item);
// Set the new image in place of the current
$fc_wrapper->field_expert_image->set((array)file_load(4316));
// Save the field collection
$fc_wrapper->save(true);
// Save the node with the new field collection (not sure this is needed)
node_save($node);
}
Any help would be greatly appreciated, I am still quite new to Drupal as a whole (end-user or developer)
Alright so I think I have figured this out, I wrote up a function that will set a field collection values:
// $node: (obj) node object returned from node_load()
// $collection: (string) can be found in drupal admin interface:
// structure > field collections > field name
// $fields: (array) see usage below
// $index: (int) the index to the element you wish to edit
function updateFieldCollection($node, $collection, $fields = Array(), $index = 0) {
if ($node && $collection && !empty($fields)) {
// Get the field collection ID
$eid = $node->{$collection}[LANGUAGE_NONE][$index]['value'];
// Load the field collection with the ID from above
$entity = entity_load_single('field_collection_item', array($eid));
// Wrap the loaded field collection which makes setting/getting much easier
$node_wrapper = entity_metadata_wrapper('field_collection_item', $entity);
// Loop through our fields and set the values
foreach ($fields as $field => $data) {
$node_wrapper->{$field}->set($data);
}
// Once we have added all the values we wish to change then we need to
// save. This will modify the node and does not require node_save() so
// at this point be sure it is all correct as this will save directly
// to a published node
$node_wrapper->save(true);
}
}
USAGE:
// id of the node you wish to modify
$node = node_load(123);
// Call our function with the node to modify, the field collection machine name
// and an array setup as collection_field_name => value_you_want_to_set
// collection_field_name can be found in the admin interface:
// structure > field collections > manage fields
updateFieldCollection(
$node,
'field_home_experts',
array (
'field_expert_image' => (array)file_load(582), // Loads up an existing image
'field_expert_name' => 'Some Guy',
'field_expert_title' => 'Some Title',
)
);
Hope this helps someone else as I spent a whole day trying to get this to work (hopefully I won't be a noob forever in Drupal7). There may be an issue getting formatted text to set() properly but I am not sure what that is at this time, so just keep that in mind (if you have a field that has a format of filtered_html for example, not sure that will set correctly without doing something else).
Good luck!
Jake
I was still getting the error, mentioned in the question, after using the above function.
This is what worked for me:
function updateFieldCollection($node, $collection, $fields = Array(), $index = 0) {
$eid = $node->{$collection}[LANGUAGE_NONE][$index]['value'];
$fc_item = entity_load('field_collection_item', array($eid));
foreach ($fields as $field => $data) {
$fc_item[$eid]->{$field}[LANGUAGE_NONE][0]['value'] = $data;
}
$fc_item[$eid]->save(TRUE);
}
I hope this helps someone as it took me quite some time to get this working.

Resources