I have 2 models, store and dvd with many to many relationship
dvd model:
public function stores() {
return $this->belongsToMany('App\store');
}
store model:
public function dvds ( ) {
return $this->belongsToMany('App\dvd');
}
Then, in controller, when I need fetch data from both models, I use code like this:
$res_store = store::orderBy("id","desc")->get();
foreach( $res_store as $row ) {
$dvds = $row->dvds()->get();
foreach ($dvds as $dvd) {
// echo columns here
}
}
This works, but my question is, I'm doing this in correct way? I'm asking because it seems 2 loops for this simple relation is somehow inefficient.
No, this is not the right way.
When looping over a $store, you can access the associated $dvd records via $store->dvds; there is no need to call $row->dvds()->get(), as that is executing a new query with the same result of $store->dvds. Full code should simply be:
$stores = Store::with("dvds")->orderBy("id", "DESC")->get();
foreach($stores AS $store){
foreach($store->dvds AS $dvd){
... // Do something with `$dvd`
}
}
The ::with("dvds") clause is known as "Eager loading", and prevents $store->dvds from needing execute another query behind the scenes.
Also, please name your models correctly. Classes in PHP are StudlyCase, so Store and DVD, not store and dvd.
Related
I get a data from model and i need to do something with it, i'm getting it like this $data = Book::paginate(10) ; and then i'm trying to use that data like this
$first = $data['data'][0] ; return $first; but i cant, it just doesn't work. is there the right way to use data?
If you just want the first book, you could do this:
return Book::first();
This would return the first book in your database. If you want the JSON version of your book object:
return Book::first()->toJson();
If you wanted to be able to do things with multiple book objects, you could do:
$books = Book::paginate(10);
foreach($books as $book) {
$book_json = $book->toJson();
// do something with the data here...
}
First code:
$result=ResPlaces::model()->findall($criteria);
/*foreach($result as $value)
{
$model_name=ResRegistration::model()->findByAttributes(array('id'=>$value->user_id));
$model_image=ResImages::model()->findByAttributes(array('place_id'=>$value->id),array('limit'=>'1'));
}*/
}
echo CJSON::encode($result);
?>
i need to add model_name->company_name & $model_image->image
to my echo json array
Try to load the relations within the findAll so you don't need the foreach.
ResPlaces::model()->with('ResRegistration', 'ResImages')->together()->findAll($criteria);
For ResRegistration and ResImages use the relation names as defined in your model.
If you don't need all fields of these relations, you can specify a select in your $criteria.
See the guide for more info.
edit: I am not quite sure, why you cannot use relation. However you will need a loop, like you already have. Here is how you do it without relations:
First add the fields you want to use to your model. In this case
class ResPlaces extends CActiveRecord
{
public $name; // check that these do not collide with your models db fields
public $image;
[...]
}
In your controller do the loop like you did before:
foreach($result as $key => $value)
{
$result[$key]->name = ResRegistration::model()->findByAttributes(array('id' => $value->user_id));
// findByAttributes returns only one record, so you don't need the limit here
// if you want multiple records, you have to use findAllByAttributes
$result[$key]->image = ResImages::model()->findByAttributes(array('place_id' => $value->id), array('limit' => '1'));
}
That should do it. However I wouldn't recommend this way, because you have lots of additional database requests. If your $result is populated with say 100 records, you have in sum 200 additional queries which are not nessassary with a relation.
Note also that if you need these two other fields more often, it may be better to put these two queries which are now in the controller in your model. The afterFind() would be the right place.
I hope the title was descriptive enough, i wasn't sure how to name it.
Let's say i have the following code:
Class Movie_model {
public method getMoviesByDate($date) {
// Connects to db
// Gets movie IDs from a specific date
// Loop through movie IDs
// On each ID, call getMovieById() and store the result in an array
// When all IDs has looped, return array with movies returned from getMovieById().
}
public function getMovieById($id) {
// Get movie by specified ID
// Also get movie genres from another method
// Oh, and it gets movie from another method as well.
}
}
I always want to get the same result when getting a movie (I always want the result from getMovieById().
I hope you get my point. I will have many other functions like getMoviesByDate(), i will also have getMoviesByGenre() for example, and i want that to return the same movie info as getMovieById() as well.
It it "ok" to do it this way? I know this puts more load on the server and increases load time, but is there any other, better way that i don't know of?
EDIT: I clarified the code in getMoviesByDate() a bit. Also, getMovieByDate() is just an example. As i said, i will be calling methods like getMoviesByGenre() also.
EDIT: I'm currently running 48 database queries on the frontpage of my project, and the frontpage is still far from finished, so that number would at least triple when i'm done. Almost all queries take around 0.0002, but as the database keeps growing that number will rise dramatically i'm guessing. I need to change something.
I don't think it's good to work like this in this particular case. The function getMoviesByDate would return an amount of "n" movies (or movie ids) from a single query. For each id in this query you would have a separate query to get the movie by the specified ID.
This would mean if the first function would return 200 movies, you would run the getMovieById() function (and the query inside it) 200 times. A better practice (IMO) would be to just get all the info you require in the getMoviesByDate() function and return it as a collection.
It doesn't seem very logical to have getMoviesByDate() and getMoviesById() methods on a Movie class.
An alternative would be to have some sort of MovieManager class that does all of the retrieving, and returns Movie objects.
class MovieManager {
public function getMoviesByDate($date) {
// get movies by date, build an array of Movie objects and return
}
public function getMoviesByGenre($genre) {
// get movies by genre, build an array of Movie objects and return
}
public function getMovieById($id) {
// get movie by id, return Movie object
}
}
Your Movie class would just have properties and methods specific to a single movie:
class Movie {
public id;
public name;
public releaseDate;
}
It's OK to have separate methods for getting by date, genre etc etc, but you must ensure that you are not calling for the same records multiple times - in that case you will want a single query that could join the various tables you need.
Edit - after you have clarified your question:
The idea of getting movie IDs by date, then running them all through getMovieById() is bad! The movie data should be pulled when getting by date, so you don't have to hit the database again.
You can modified your getMovieById function. You can pass date as a parameter, the function should return the movies by their id and filtered by date.
To keep track which records you've already loaded into RAM previously you can use a base class for your models which saves the id's of the records already loaded and a reference to object the model object in the RAM.
class ModelBase {
/* contains the id of the current record, null if new record */
protected $id;
// keep track of records already loaded
static $loaded_records = Array();
public function __construct(Array $attr_values) {
// assign $attr_values to this classes attributes
// save this instance in class variable to reuse this object
if($attr_values['id'] != null) {
self::$loaded_records[get_called_class()][$attr_values['id']] = $this;
}
}
public static function getConcurrentInstance(Array $attr_values) {
$called_class = get_called_class();
if(isset(self::$loaded_records[$called_class][$attr_values['id']])) {
// this record was already loaded into RAM
$record = self::$loaded_records[$called_class][$attr_values['id']];
// you may need to update certain fields of $record
// from the data in $attr_values, because the data in the RAM may
// be old data.
} else {
// create the model with the given values
$record = new $called_class($attr_values);
}
return $record;
}
// provides basic methods to update records in ram to database etc.
public function save() {
// create query to save this record to database ...
}
}
Your movie model could look something like this.
Class MovieModel extends ModelBase {
// additional attributes
protected $title;
protected $date;
// more attributes ...
public static function getMoviesByDate($date) {
// fetches records from database
// calls getConcurrentInstance() to return an instance of MovieModel() for every record
}
public static function getMovieById($id) {
// fetches record from database
// calls getConcurrentInstance() to return an instance of MovieModel()
}
}
Other things you could do do decrease the load on the DB:
Only connect once to the database per request. There are also possibilities to share a connection to a database between multiple requests.
Index thefields in your database which get searched often.
only fetch the records you need
Prevent to load the same record twice (if it didn't change)
Some quick background info: I'm coding up a site which matches books to the classes they're required for.
I have two pieces of data that I need to represent in my code-- which books go with which classes, and the data (titles, authors, pricing, etc.) on these books.
Currently I represent this all with two arrays: $classArray, and $Books_data.
The advantage of this approach over a one-variable approach is that I don't repeat myself-- if a Book is required multiple times for different classes, only the ISBN needs to be stored in the $classArray and I can store the data in the $Books_array. This advantage is especially poignant because I have to query the pricing data from API's on the fly. If I only had a $classBooksArray, I'd have to loop the query responses into a big array, repeating myself (seemingly) unnecessarily.
The disadvantage of this approach is that these variables follow each other almost everywhere like Siamese twins. Nearly every function that needs one, needs the other. And my coding spidey sense tells me it might be unnecessary.
So, what would be the best way to store this data? Two arrays, one array, or some other approach I haven't mentioned (e.g. passing by reference).
Why not an associative which has two keys - one pointing to an array of classes, one to store Books
data?
$allData = array("classes" => &$classArray, "books" => &$Books_data);
That way you're only passing around 1 variable (less clutter) but retain all the benefits of separate data stores for books and classes.
Though, to be honest, if it's just TWO sets of data, so IMHO your spidey sense is wrong - passing both as separate parameters is perfectly fine. Once you get into a set of siamise sextuplet variables, then the above approach starts to actually bring benefits.
A multidimensional array.
$collection = array(
'classes' => array( /* Contents of $classArray */),
'books' => array( /* Contents of $Books_data */)
);
function some_function($collection) {
// looping over books
foreach ($collection['books'] as $book) {
// yadda yadda
}
}
Or better yet a class:
/* Define */
class Collection {
private $books;
private $classes;
public function __construct($classes = array(), $books = array()) {
$this->books = $books;
$this->classes = $classes;
}
public function addBook($book) {
$this->books[] = $book;
}
public function addClass($class) {
$this->classes[] = $class;
}
public function get_classes() {
return $this->classes;
}
public function get_books() {
return $this->books;
}
}
function some_function(Collection $col) {
// looping over books
foreach ($col->get_books as $book) {
// yadda yadda
}
}
/* Usage */
$collection = new Collection(); // you also could pass classes and books in the
// constructor.
$collection->addBook($book);
somefunction($collection);
If your datas were a database, your current proposal being a normal form would be canonical. The two variables would just become tables and ISBN a foreign key to books table (with a third table as a class has several books). I would probably stick with the current implementation as it will be very easy to transform to database when that will be necessary (and it usually happens faster than expected).
EDIT: a comment, say it is already in a database... what I do not understand is why you would want to store a full database in memory instead of just keeping what is necessary for the current task.
Let's be OO and put the arrays into an object. Define a class with those properties, load it up, and call its methods. Or, if you must have other functions operating with the object's data, pass the instance around. Disallow direct access to the data, but provide methods for extracting the salient info.
class book_class_association {
protected $books_to_classes = array();
protected $classes_to_books = array();
function __construct() {
$this->books_to_classes = array(
'mathbook1' => array('math'),
'mathbook2' => array('math'),
);
$this->classes_to_books = array(
'math' => array('mathbook1', 'mathbook2'),
);
}
function classes_for_book( $class_name ) {
return $this->books_to_classes[$class_name];
}
function books_for_class( $class_name ) {
return $this->classes_to_books[$class_name];
}
}
Is there a reason you are not storing this data in a database and then querying the database? It is a many to many relationship, and you would need 3 tables - class , book, and class_book_intersection.
So for example, you ui could have "select class" from a list, where the list is derived from the rows in class.
Then if the class id selected is 123. The query would then be something like:
Select book.title, book.cost
from book
inner join class_book_intersection
on
class_book_intersection.classid = 123 and
class_book_intersection.bookid = book.bookid
I’m attempting to use get_where to grab a list of all database records where the owner is equal to the logged in user.
This is my function in my controller;
function files()
{
$owner = $this->auth->get_user();
$this->db->get_where('files', array('owner =' => '$owner'))->result();
}
And in my view I have the following;
<?php foreach($query->result() as $row): ?>
<span><?=$row->name?></span>
<?php endforeach; ?>
When I try accessing the view, I get the error :
Fatal error: Call to a member function result() on a non-object in /views/account/files.php on line 1.
Wondered if anyone had any ideas of what might be up with this?
Thanks
CodeIgniter is a framework based on MVC principles. As a result, you would usually separate application logic, data abstraction and "output" into their respective areas for CodeIgniter use. In this case: controllers, models and views.
Just for reference, you should usually have you "data" code as a model function, in this case the get_where functionality. I highly suggest you read through the provided User Guide to get to grips with CodeIgniter, it should hold your hand through most steps. See: Table of Contents (top right).
TL;DR
To solve your problem you need to make sure that you pass controller variables through to your view:
function files()
{
$owner = $this->auth->get_user();
$data['files'] = $this->db->get_where('files', array('owner =' => '$owner'))->result();
$this->load->view('name_of_my_view', $data);
}
And then make sure to use the correct variable in your view:
<?php foreach($files as $row): ?>
<span><?=$row['name']; ?></span>
<?php endforeach; ?>
<?php foreach($query->result() as $row): ?>
<span><?=$row->name?></span>
<?php endforeach; ?>
Remove the result function like so.
<?php foreach($query as $row): ?>
<span><?=$row->name?></span>
<?php endforeach; ?>
Btw. It's a much better idea to test the query for a result before you return it.
function files()
{
$owner = $this->auth->get_user();
$query = $this->db->get_where('files', array('owner =' => $owner))->result();
if ($query->num_rows() > 0)
{
return $query->result();
}
return FALSE;
}
public function get_records(){
return $this->db->get_where('table_name', array('column_name' => value))->result();
}
This is how you can return data from database using get_where() method.
All querying should be performed in the Model.
Processing logic in the View should be kept to an absolute minimum. If you need to use some basic looping or conditionals, okay, but nearly all data preparation should be done before the View.
By single quoting your $owner variable, you convert it to a literal string -- in other words, it is rendered as a dollar sign followed by five letters which is certainly not what you want.
The default comparison of codeigniter's where methods is =, so you don't need to declare the equals sign.
I don't know which Auth library you are using, so I'll go out on a limb and assume that get_user() returns an object -- of which you wish to access the id of the current user. This will require ->id chained to the end of the method call to access the id property.
Now, let's re-script your MVC architecture.
The story starts in the controller. You aren't passing any data in, so its duties are:
Load the model (if it isn't already loaded)
Call the model method and pass the owner id as a parameter.
Load the view and pass the model's returned result set as a parameter.
*Notice that there is no querying and no displaying of content.
Controller: (no single-use variables)
public function files() {
$this->load->model('Files_model');
$this->load->view(
'user_files',
['files' => $this->Files_model->Files($this->auth->get_user()->id)]
);
}
Alternatively, you can write your controller with single-use variables if you prefer the declarative benefits / readability.
public function files() {
$this->load->model('Files_model');
$userId = $this->auth->get_user()->id;
$data['files'] = $this->Files_model->Files($userId);
$this->load->view('user_files', $data);
}
Model: (parameters are passed-in, result sets are returned)
public function Files($userId) {
return $this->db->get_where('files', ['owner' => $userId])->result();
}
In the above snippet, the generated query will be:
SELECT * FROM files WHERE owner = $userId
The result set (assuming the query suits the db table schema) will be an empty array if no qualifying results or an indexed array of objects. Either way, the return value will be an array.
In the final step, the view will receive the populated result set as $files (the variable is named by the associative first-level key that was declared in the view loading method).
View:
<?php
foreach ($files as $file) {
echo "<span>{$file->name}</span>";
}
The { and } are not essential, I just prefer it for readability in my IDE.
To sum it all up, the data flows like this:
Controller -> Model -> Controller -> View
Only the model does database interactions.
Only the view prints to screen.