Symfony2: is ArrayCollection better than multiple db queries? - php

In Symfony2 I have two entities (Companies and Employees) with a One-to-Many relation (one company can have multiple employees).
Each Employee object has an id, a company_id (foreign key), a badge_number, a last_updated datetime field, name, etc
Easy, right?
Now comes the tricky part...
Each day I get (from an API) a $company_upd array that contains the list of all the companies that updated some of their employees data. Then, for each company in $company_upd, I retrieve from the API a $company_employees_list array that cointains the new list of employees .
Then I loop through the array to identify any new/updated record that needs to be persisted in my local database. Like this:
foreach ($company_employees_list as $employee){
$local_employee = $repo->findOneBy("badge_number" => $employee["badge_number"];
//if there is already an employee record with the same badge_number
if($local_employee){
//compare the last_updated field to see if this employee record needs to be updated
if($employee["badge_number"] > $local_employee->getLastUpdated()){
//update the record and persist to the database
$local_employee->setSomething($employee["some_value"];
}
} else {
//create a new employee object and persist it to the database
$new_employee = new Employee();
$new_employee->setSomething('some_value');
$em->persist($new_employee);
}
}
$em->flush();
Of course, this approach works fine BUT I'm doing a query foreach employee foreach company!! (to retrieve the corresponding object and update it)
Is it possible (and would it be better) to use the ArrayCollection? I mean: fetch the employees ArrayCollection ONE TIME and then, work with it. Search if there is already an employee record with a specific badge_number and eventually update it and persist it to the database?
So, basically, I would like to do everything I did in the code above but using Array_Collection instead of separate queries.
But only if this new approach is faster/better.
If it is indeed possible (and faster), could you please write some mockup code to better understand HOW to manipulate an ArrayCollection and do what I want?
//Fetch the local db employees ArrayCollection
$employees_ar_coll = $company->getEmployees();
//What now???? =)
P.s. The API works like this, can't change that.

Related

How to compare and update an Eloquent Collection based on another Collection in Laravel

I have a model in Laravel called Player. This player data is pulled from an external API. I am trying to update this external data on a scheduled basis from the external API. The external API data is the authoritative source on what should be in the players table.
What I currently have is two collections, one is the data from the database, the other is the external API data. I constructed new Player models in a collection based on the API data.
What I essentially have right now is this:
Collection $playersInDatabase; // Eloquent Collection of type Player
Collection $playersFromApi; // Collection of type Player
The $playersFromApi data is just the JSON API data converted into new Player models and added to the collection.
My problem is that I can't just wipe the whole players table as I am only modifying a subset of the table at a time. Is there an effective way to use Laravel to compare these two? I want to add any new Player models to the database that don't exist, update any existing Player model that has differing data, and then also delete any records that the API data no longer has but are still in the database (stale records).
The only way I can think of to do this involves iterating over the collections multiple times to accomplish what I want to do and I feel like there is an easier more elegant way to do this that better utilizing the framework.
For reference here is what the players table looks like. I am currently using seeder data:
You could do something like this. No comparisons necessary, just updateOrCreate() and delete any ids not updated for the corresponding faction in a single DB call.
// the faction id you are querying from API
$faction_id = ...;
// for storing updated model ids
$updated_ids = [];
foreach ($playersFromApi as $playerFromApi) {
// update record or create a new one
$player = Player::updateOrCreate(
[
// conditions to meet
'...' => $playerFromApi['...']
],
[
// data to update
'...' => $playerFromApi['...']
]
);
// store id of updated model
$updated_ids[] = $player->id;
}
// delete models not updated
Player::where('faction_id',$faction_id)->whereNotIn('id',$updated_ids)->delete();

Using special union functions inside Laravel Eloquent model

I've started developing a website using Laravel, and im pretty much finding everything through the official documentation and answers that I find here. However there is 1 thing that -even though i've found a way to do-, I have a feeling that could be done in another, more optimized way than the one I'm doing it right now. Let me explain.
For my website, I have a table called "players", which has data about some players extracted from a football game. Let's say the structure is like this:
ID (int, primary key, A_I)
GameID (int, unique) //what the game uses
PlayerName
Data (basically many different columns)
Since the purpose of my website is to allow users to do modifications on the game content, I also have another another table that I call "userplayers", which I use for doing modifications on the players that exist on the original table, or for adding new ones. The structure is like the "players" table, however with just one column added, called userID, which is to identify which modification belongs to which user.
ID (int, primary key, A_I)
GameID (int, unique together with userID)
PlayerName
Data (basically many different columns)
UserID (int, unique together with GameID)
If I add an entry on the userplayers table, if that entry has the same GameID as any entry on the players table (and the user that has created it is logged in), then on runtime it overwrites the players' table entry. If the GameID of the new entry doesnt exist on the original players table, then it just gets appended to it.
If the user is not logged in, then I just return the players table.
By using eloquent laravel model I can easily retrieve the players table for when the user is not logged in. However, I can't figure out an efficient way to return the whole DB + the user created content with just using core eloquent model functions.
In the past (without Laravel) I was using this DB query:
SELECT * FROM (SELECT *, NULL FROM players WHERE NOT EXISTS (SELECT * FROM userplayers WHERE players.gameid=userplayers.gameid AND userId=$userId) UNION (SELECT * FROM userplayers WHERE userId=$userId)) AS pl;
And the way I've "found" to do something like this in Laravel is by adding a local scope inside the Players model like this:
public function scopeIncludeCustom($query, $user)
{
return \DB::select('SELECT * FROM (SELECT *, NULL FROM players WHERE NOT EXISTS (SELECT * FROM userplayers WHERE userplayers.gameid=players.gameid AND userId='.$user.') UNION (SELECT * FROM userplayers WHERE userId='.$user.')) AS players_full);
}
However you can understand that this doesn't return the $query as intended by scopes, but just a php array, which im returning back, and I think that's not the correct way to do this. For example, when I'm just searching the players table (without using user created content), it takes a much much shorter time to return results than returning results with the custom content.
Any ideas are hugely appreciated.
So, after some days of researching my issue and possible solutions, I came up with this solution.
So, lets take this step by step.
I had a "Player" model, that fetched data from the "players" table.
I also had a "Userplayer" model, that fetched data from the "userplayers" table.
I had to create a relation between those 2 models. An entry in the "players" table may have many entries related to them in the "userplayers" table. So, in the Player Model I added this:
public function userplayers()
{
return $this->hasMany('App\Userplayer', 'gameid', 'gameid');
}
While in the Userplayer Model, I added this:
public function player()
{
return $this->belongsTo('App\Player', 'gameid', 'gameid');
}
When requesting data, the first step was to remove every row from the "players" table that had the same "GameId" as any row returned from the "userplayers" table, which also had a restriction that the "userId" in every row of this table must be a specific one.
This is the code that does this in my SearchPlayerController:
$orig_players = \App\Player::whereDoesntHave('userplayers', function ($query) use($user)
{
$query->where('userId', $user);
})->get();
At the same time, I need to get every row from the "userplayers" table that has the "userid" I want
$userplayers = \App\Userplayer::where([
['pesid', $search]
])->get();
Now, I just merge the 2 results (I can't use UNION because the data I fetch from the "players" table has one less column).
$players = $orig_players->merge($userplayers)
->sortBy('registeredName')
->take($limit);
And everything works perfectly fine, and a lot faster than before!

Insert model unless it exists and attach it

I'm a Laravel noob rewriting some old code to Laravel.
I have a system for managing purchases and games and I'm writing the store method of the PurchaseController. The form for creating new purchases contains data about the purchase and an array with data about the games.
There is a many-to-many relationship between games and purchases: a purchase can contain many games and a game may be linked to multiple purchases.
The thing is that the game may already exist in the database. I want to do the following:
Insert the new purchase into the database (this part I got sorted out already ;))
Check if the POSTed name of the game already exists in the database.
If it exists, attach it to the newly inserted purchase. If it doesn't exist, insert it and attach it to the newly inserted purchase.
I don't want to update the game if it already exists in the database, just to attach it to the purchase.
I've looked into firstOrCreate but that doesn't do what I want. It checks on all the arguments you feed it, you can't just make it check only the name (this issue basically).
The undocumented method updateOrCreate does accept two arrays (one for attributes to check on, another for values to insert) but it updates the record if it exists, which is not what I want.
So, is there a nice, proper way to do this with Eloquent or do I simply need to manually write some code that checks if the game exists in the database and inserts the game unless that's the case?
EDIT:
It seems that this is possible with firstOrCreate after all in Laravel 5.3: https://github.com/laravel/framework/pull/13236
firstOrCreate is what you need, but you can feed it just the game name, then attach it to your purchase.
$game = Game::firstOrCreate(['name' => $gameName]);
$purchase = new Purchase(['otherArgs' => ...]);
$purchase->games()->attach($game);
I was probably overthinking this too much. The following code does what I want:
// Insert games (unless they exist) and attach to new purchase
foreach($request->games as $game) {
$gameModel = Game::firstOrNew(['name' => $game['name']]);
if(!$gameModel->exists) {
$gameModel->status_id = $game['status'];
$gameModel->note = $game['note'];
$gameModel->save();
}
$gameModel->purchases()->attach($purchase->id);
}
I just thought maybe there was a nicer/shorter way to do this.

yii relational complex active record task

I'm trying to get used to relational active record but things look too complicated for now.
If it's not difficult please point me in right direction.
I have 4 tables.
Users
userID[pk],userName
Cars
car_id[pk],userID[fk to Users],car_nickname,make_id[fk to Makes],model_id[fk to Models]
Makes
make_id[pk],make_name
Models
model_id[pk],model_name,make_id[fk to Makes]
Now input data is userName,make_name,model_name and task is to get car_nickname from Cars table.
Is this possible using relations or should I do it step by step checking makes,models,users for IDs and then puting all IDs into Cars to get car_nickname ?
You can yous only one Model with reliations to all tables. And create one _form.php in the Views, where there will only fields you need.
Some halpfull information. if you save some data into some table and you need saved data id, you caN use $newid = $model->getPrimaryKey(); and assign to new variable , wich will save into other tables.
I think this is short way to solve problem

CodeIgniter: datamapper ORM delete() a relation;

I have a tables called users, countries, and countries_users.
The documentation states that to delete a simple relationship you perform:
// Get user foo
$u = new User();
$u->where('username', 'foo')->get();
// Get country object for Australia
$c = new Country();
$c->where('name', 'Australia')->get();
// Delete relation between user foo and country Australia
$u->delete($c);
This would remove the corresponding row from the countries_users table.
My question is, what if I have no relevant Country() object to construct?
If countries and users are a one-to-many relationship, then certainly knowing the username attribute is enough to disassociate him with a country.
All the delete functions seem to require at least two objects... What is the best way to accomplish the deletion of this type of relation using the DataMapper ORM functions?
"All the delete functions seem to require at least two objects"
Not totally true, a delete() can be preformed on a single object without the need to explicitly delete the object's relationships, it is handled automatically.
From the user guide:
Note: When you delete an object, all its relations to other objects will also be deleted. Free house cleaning! :)
In addition, you may use a column in the users table for the country id instead of a separate countries_users table for relationships, assuming it is a one(country)-to-many(users) relationship.
My question is, what if I have no relevant Country() object to construct?
Then you don't have to worry about anything. If there are no relationships to delete, attempting to delete them will not cause any harm.
There is a relationship to delete! I want to pass the user_id into my Controller and disassociate him with a country in the countries_users table. To accomplish this using the documented functions, I would have to also pass in the country_id... Which IMO is irrelevant for this operation.
You don't have to look up the country id unless you specifically want to delete a particular relationship. In your case, you're working with a relationship where a user can only have one country, so you don't need to specify which related country to delete. Here are two options off the top of my head:
Assigning a new country (removes the previous one)
$c = new Country();
// Get all countries named "Wonderland"
// Usually we'll use an id instead, there could theoretically be more than one
$c->where('name', 'Wonderland')->get();
$user->save($c);
Just delete all related countries (there is only one of course)
$c = new Country();
// Get all countries
$c->get();
$user->delete($c); // You may need $c->all here
If we were working with a many to many relationship, you would of course have to know which ones to delete, but since there is only one - deleting them all is sufficient.
Believe it or not, I was unable to remove the relationship using the code Wesley has provided.
However, this seemed to work:
$u = new User();
$u->where('id', $id)->include_related('country', 'id', TRUE, TRUE)->get();
$c = new Country();
$c->where('id', $u->country->id)->get();
$c->delete($u);

Categories