Update fields from an array with CakePHP - php

I have an array that is sent to my controller, such as this:
$array = array(
[0]=>array(
[id]=>5,
[position]=>6
),
[1]=>array(
[id]=>8,
[position]=>2
)
);
And I need to save the position of each item using its id. What is the best way to do this in cakePHP? I can only imagine looping an update function or pulling the entire database, changing the correct values, then saving the database. Both ideas seem very bodged.

Ha, Cake magic to the rescue again. You don't have to tell Cake to save it by id. There's a big long amazing way Cake does this, but the short and skinny is -
if your array contains an 'id' key, Cake presumes it is the table primary key and generates an UPDATE statement instead of an INSERT. Looks like this:
UPDATE table as Table SET Table.position = $position WHERE Table.id = $id;
And, Cake knows to iterate for you if you use saveAll() instead of save():
$this->Model->saveAll($array);
If you have any save callbacks in your model, such as beforeSave(), you have to call them manually before calling saveAll() - they only autofire on save(), not saveAll() or updateAll().
You'll want to top your array with the name of your model ($array['Model'][0], $array['Model'][1], etc). If you need to magically saveAll() with multiple models, you top your array with indexed keys, then model names - like $array[0]['Model1'], $array[0]['Model2']) and Cake knows to save / update the associated data for all the models in each index batch.
Cake does ALL the legwork for you:
http://book.cakephp.org/view/1031/Saving-Your-Data - especially the saveAll() entry.
Edit - and topping your array with your model name? Cake makes that ridiculously easy, too. Check out Cake's built-in Set library for all your array / object manipulation needs.
http://book.cakephp.org/view/1487/Set

Have you tried CakePHP's saveAll() ? Details here.

Related

Laravel session variable is not updating

I have a complex record stored in session. I want to update parts of this record in session then update the DB row with the session data. So far I have managed to overwrite the entire structure and not just the part of it I intend to.
Example:
base <-- whole record
base.field1 <-- single field
base.field2 <-- single field
base.field3 <-- single field
base.users <-- array of objects (users), stored as JSON column
base.details <-- single dimensional array of fields
base.cards <-- array of objects (cards), stored as JSON column
base.regions <-- array of objects (regions), stored as JSON column
This whole structure is stored as a row in a progress table.
I load and store this data in session like this:
session()->put('base', Progress::find($id));
I then update some of the fields in the cards array:
$cards = session()->get('base.cards');
$cards[$index]['points'] = 100;
I then try (unsuccessfully) to update the session variable, having tried both below:
session()->put('base.cards', $cards);
session()->push('base.cards', $cards);
session('base.cards', $cards);
Then lastly I want to store this record in the progress table by updating its instance, like this:
Progress::find($id)->update(session()->get('base'));
How do I manipulate then update in session just one of the JSON/array fields?
UPDATE: I added:
session()->put('base.cards', $cards);
session()->save();
But still, when I dd(session()->get('base')) I get the $cards array?!
I would suggest your problem is caused by you using Session in a way that was not intended. You seem to be storing an Eloquent model called Progress in the Session with the key 'base' and then expecting the get, put and push methods of Session to understand how to interact with your Model's parameters.
How about you make life simple and just store the ID in the Session...
$progress = Progress::find($id);
session()->put('progress_id', $progress->id );
session()->save();
And then when you want to pull it out to manipulate, save in database etc do this...
$progress_id = session()->get('progress_id');
$progress = Progress::find($progress_id);
$progress->cards = $cards->asJSON(); // Give your cards model a method that serialises itself
$progress->save();
Overall your code is now easier to read and everything's being used in a very 'normal' way. The session is just holding an ID, the model exists as a proper Eloquent model being accessed in the documented way.
Future you will be grateful when he comes to review this code :-)
Oh, and if you were storing the model in the session because you're worried about speed, pulling a single record from a properly indexed database is probably no slower than pulling the data from a session. We're talking thousands of a second but please do run tests to put your mind at ease.
Complex structures supported by session storage is limited to arrays.
To benefit from . syntax in key names, you need to convert the model to array, e.g.:
session()->put('base', Progress::find($id)->toArray());
So you can do later
session()->put('base.cards', $cards);

Swapping array values with Eloquent model IDs

I need to make an import method that takes the CSV file and imports everything in the database.
I've done the parsing with one of Laravel's CSV addons and it works perfectly giving me a big array of values set as:
[
'col1_name' => 'col1 value',
'col2_name' => 'col2 value',
'col3_name' => 'col3 value,
'...' => '...'
]
This is also perfect since all the column names fit my model which makes the database inserts a breeze.
However - a lot of column values are strings that i'd like to set as separate tables/relations. For example, one column contains the name of the item manufacturer, and i have the manufacturer table set in my database.
My question is - what's the easy way to go through the imported CSV and swap the strings with the corresponding ID from the relationship table, making it compatible with my database design?
Something that would make the imported line:
[
'manufacturer' => 'Dell',
]
into:
[
'manufacturer' => '32',
]
I know i could just do a foreach loop comparing the needed values with values from the relationship models but I'm sure there's an easier and more clean way of doing it.
I don't think theres any "nice" way to do this - you'll need to look up each value for "manufacturer" - the question is, how many queries will you run to do so?
A consideration you need to make here is how many rows you will be importing from your CSV file.
You have a couple of options.
1) Querying 1 by 1
I'm assuming you're going to be looping through every line of the CSV file anyway, and then making a new model? In which case, you can add an extra database call in here;
$model->manufacturer_id = Manufacturer::whereName($colXValue)->first()->id;
(You'd obviously need to put in your own checks etc. here to make sure manufacturers exist)
This method is fine relatively small datsets, however, if you're importing lots and lots of rows, it might end up sluggish with alot of arguably unnecessary database calls.
2) Mapping ALL your Manufacturers
Another option would be to create a local map of all your Manufacturers before you loop through your CSV lines;
$mappedManufacturers = Manufacturer::all()->pluck('id', 'name');
This will make $mappedManufacturers an array of manufacturers that has name as a key, id as a value. This way, when you're building your model, you can do;
$model->manufacturer_id = $mappedManufacturers[$colXValue];
This method is also fine, unless you have tens of thousands of Manufacturers!
3) Where in - then re-looping
Another option would be to build up a list of manufacturer names when looping through your CSV lines, going to the database with 1 whereIn query and then re-looping through your models to populate the manufacturer ID.
So in your initial loop through your CSV, you can temporarily set a property to store the name of the manufacturer, whilst adding it to another array;
$models = collect();
$model->..... = ....;
$model->manufacturer = $colXValue;
$models->push($colXValue);
Then you'll end up with a collection of models. You then query the database for ONLY manufacturers which have appeared:
$manufacturers = Manufacturer::whereIn('name', $models->lists('manufacturer'))->get()->keyBy('name')->toArray();
This will give you array of manufacturers, keyed by their name.
You then loop through your $models collection again, assigning the correct manufacturer id using the map;
$model->manufacturer_id = $manufacturers[$model->manufacturer];
Hopefully this will give you some ideas of how you can achieve this. I'd say the solution mostly depends on your use case - if this was going to be a heavy duty ask - I'd definitely Queue it and be tempted to use Option 1! :P

Array_unique on a laravel eloquent collection

Not sure if this is possible but im trying to run array_unique over a collection of items i have, to remove duplicates. Although i cannot get it working.
my controller logic:
// init models
$jobs = Job::search();
$countries = $jobs->get()->map(function( $job ) {
return $job->country;
});
$countries = array_unique( $countries->toArray() );
although this gets a "Array to string conversion" error
You could try the Unique method of the Collection class:
$countries = $countries->unique();
The Collection class has several wonderful methods. You could read about this in the Laravel API documentation.
I agree that sometimes it is more efficient to "query" on an existing Collection in memory (in stead of doing another query on the database using the Querybuilder class), like for example you first want to count and then filter. In .NET LINQ you can query almost equally on an IEnumerable (in-memory collection) as on a database, something I really enjoy.
I had similar issue and although time have passed since it may be useful to someone today.
The Problem I had was that when I called unique method on collection of items it didn't worked, that is probably the reason the first answer got accepted. So if you have models and you want to remove duplicates based on a specific field you can pass parameter to your unique method, in this case it would be:
$countries->unique('code');
that way you'll only have countries with unique codes. You may notice that only the first value stays, so if you develop a shopping cart application and want for some reason merge carts and only want to have recent items you can just reverse the collection and call unique and reverse it back:
$countries->reverse()->unique('code')->reverse(); // it doesn't really make sense in this example though
it is probably not the best option and it is better to do filtering on the database side but it is nice to have options.
You can have unique values in your DB results using distinct or group by in your select clause. But, if you really need to have unique values over an array of object you can do the following:
$uniques = array();
foreach ($countries as $c) {
$uniques[$c->code] = $c; // Get unique country by code.
}
dd($uniques);
You could try the filter method on eloquent collections if that's exactly what you want to do
http://laravel.com/docs/eloquent#collections

PHP & MySQL Tickers..Is this standard practice?

I currently have about 4 different database tables which output to html tables. Each of these tables uses a count query to calculate data from a 5th table.
That's no problem, but what about when I want to sort and order the data, and paginate etc (like with zend). If it were a one page table, I could probably sort an array.
My thought was, to use a ticker. But that would require a new column in all 4 tables and seems like overkill or like there could be a better way.
Sadly, I can't find much info on it (likely because I don't know what to search for).
Advice?
..and please take it easy, I'm new and learning.
Assuming youre using Zend_Db_Table_Row and that you dont need to persist any modifications you might make to these rowsets then you can just append the virtual columns to the row object and have them be accessible via array notation. So if youre doing it all in one query now just use that same query, and the column should be there.
OTOH, if youre using a Data Mapper pattern then simply adjust your hydration to look for this "virtual column" and hydrate it if it exists in the result data. Then in your getter for this property have it see if the property is null or some other negative specification, and if it is, to execute a calculation query on that single object or return the already calculated result.

OOPHP - How do I create multiple objects from an array of database rows?

I'm new to "Object Oriented" PHP, but have managed to figure most things out so far. I'm building a website where users can register an account and then add, let's say, hobbies onto their profile.
I've managed to figure out creating the class, object, storing a single user in the database, and retrieving a single user from the database - creating a new object from the associative array that is returned from the SQL "select" query.
Where I am now getting stuck is when I need it to return multiple rows (records) from the database. For example, a user may have 7 hobbies; the SQL query returns all 7 rows into an associative array (with fields e.g. hobby_id, hobby_name, hobby_created), but how then do I make each one of those rows/hobby records into its own object?
I have tried searching all sorts of terms but I don't know if I'm just missing the buzz word that I need to search for. If anyone could please let me know the best way to go about this I would be eternally greatful.
Many thanks,
Steve
You can either loop through the result and create hobbies from the data or if you're using PDO you can use:
$stmt->fetchAll( PDO::FETCH_CLASS, 'Hobby' );
This will create a Hobby class for each row and populate properties with the columns from the query.
From http://www.php.net/manual/en/pdostatement.fetch.php
PDO::FETCH_CLASS: returns a new
instance of the requested class,
mapping the columns of the result set
to named properties in the class. If
fetch_style includes
PDO::FETCH_CLASSTYPE (e.g.
PDO::FETCH_CLASS |
PDO::FETCH_CLASSTYPE) then the name of
the class is determined from a value
of the first column.
Note: if you're using fetch() and not fetchAll() you have to use setFetchMode() before calling fetch()
$stmt->setFetchMode( PDO::FETCH_CLASS, 'Hobby' );
When you have an array of hobbies, create the hobby objects in a foreach.
$hobby = Array();
foreach ($query->results as $row)
$hobby = new Hobby($row['person_id'], $row['hobby']...
or perhaps a hash of objects.
First you'd have to actually create an object to handle those hobbies. You can't just sprinkle Acme OOP sauce on some data and have it magically turn into an object. An object is both data and functions (methods, actually) to work with that data. So, first figure out what you want your code to do with those hobbie data bits and work from there.
Is there any reason the hobbies needs to be its own object? It seems like it functions fine as a hash/array. If you really want to create an object, though, then you can create a hobby class that stores the hobby name/created date and has getters for them. Is there any other data to associate with the hobby, or calculations that need to be done? If so, you can create methods of the Hobby class to do this for you.
You can easily typecast an associative array to object with properties, like:
// $obj is instance of stdClass
$obj = (object)array('hobby_name'=>'name');
Of course the question wasn't that clear for what you need objects, or if they need to be of any explicit class (#Galean's answer).

Categories