I have a code for defaultScope:
public function defaultScope()
{
$currentdb = explode('=', Yii::app()->db->connectionString);
return array(
'condition'=> "tenant=:tenant",
'params' => array(":tenant"=>$currentdb[2]));
}
And this code for Beforefind:
public function beforeFind() {
$currentdb = explode('=', Yii::app()->db->connectionString);
$criteria = new CDbCriteria;
$criteria->condition = "tenant=:tenant";
$criteria->params = array(":tenant"=>$currentdb[2]);
$this->dbCriteria->mergeWith($criteria);
parent::beforeFind();
}
I am getting same result in both the functions. Which function is better and why?
I think that both can accomplish what you want, but for me the best usage is using scopes. In the yii guide we can find the following definition for scopes:
A named scope represents a named query criteria that can be combined
with other named scopes and applied to an active record query.
It's is what you want to do: apply some query criteria before executing the query. And since you want those criteria to be added on every query then defaultScope is the way to go!
I disagree. I'm having a database with records for multiple users and I'm trying to filter on those records that should be visible for the current user only. I got stuck today on trying to fixing that with defaultScope and I found out that beforeFind is the way to go in this case. The problem can be nailed down to the fact that beforeFind doesn't seem to be used on the relations while defaultScope is. This means you get stuck when you apply criteria in your defaultScope of an object with relations that are eagerly loaded with alike criteria because of the order in which the criteria are applied in the joins.
Let me try to explain this with Yii's blog guide: when we only want the posts of the current author, we could write the following defaultScope:
$c = new CDbCriteria();
$c->with('author');
$c->addInCondition('author.author_id', array(1,2,3));
return $c;
When using $post->author, we will find out that author.author_id is applied before author is defined as a join. This is not the best example, but you will find yourself having these problems when having more than two joins in your relations.
Therefore, I would suggest using beforeFind instead of defaultScope.
Related
i'm really new working with laravel 5.0, so I got this problem when I try to retrieve a result using a model. I have a users table, with a list of users who can be a manager or not, they can have assigned one or more companies, or none, a company table with companies which can have one or many managers, and a pivot table that I called companies_managers. I set up the relations in every model like this:
/***User model***/
public function companies()
{
return $this->belongsToMany('App\Company', 'companies_managers','id', 'manager_id');
}
and the same in Company model
public function managers()
{
return $this->belongsToMany('App\User', 'companies_managers', 'id', 'company_id');
}
I want to get the managers assigned to a company using a company id to get it, but it just gave me an huge object without the values I looking for (the names of the managers assigned to that company). This is the code that I tried:
$managers = Company::find($id)->managers();
I would appreciate any help you can give me
Using ->managers() (with the brackets) doesn't actually return the associated managers, but rather a Builder instance (the "huge object"), which you can then chain with additional parameters before finally retrieving them with ->get() (or another closure, like ->first(), ->paginate(), etc)
Using ->managers (without the brackets), will attempt to access the associated managers, and execute any additional logic to retrieve them.
So, you have 2 options:
$company = Company::with(["managers"])->findOrFail($id);
$managers = $company->managers;
Or
$company = Company::findOrFail($id);
$managers = $company->managers()->get();
Both of those will perform the necessary logic to pull the managers. ->with() and no brackets is slightly more efficient, doing it all in a single query, so bear that in mind.
You just need to split out your code;
// this will find the company based on the id, or if it cannot find
// it will fail so will abort the application
$company = Company::findOrFail($id);
// this uses the active company record and gets the managers based
// on the current company
$managers = $company->managers;
Thank you for your help guys, I solved the issue fixing the relations in the models to this:
return $this->belongsToMany('App\Company', 'companies_managers', 'manager_id', 'company_id');
and this
return $this->belongsToMany('App\User', 'companies_managers', 'company_id', 'manager_id');
The IDs that I had set were not the correct ones for belongsToMany function
And this
$managers = Company::find($id)->managers();
was a problem too, was a dumb mistake of my part. I solved the return of Builder instance using just return instead of dd(), in that way I got the values I looking for. Thanks everyone!
I am fairly new to laravel and I built a little "similar posts" section. So every post has a tag and I query all the id's from the current tag. And then I find all the posts with thoses id's. Now my problem is that the current post is always included. Is there an easy way to exclude the current id when querying?
I can't seem to find anything in the helper function on the laravel docs site
this is my function:
public function show($id)
{
$project = Project::findOrFail($id);
foreach ($project->tags as $tag){
$theTag = $tag->name;
}
$tag_ids = DB::table('tags')
->where('name', "=", $theTag)
->value('id');
$similarProjects = Tag::find($tag_ids)->projects;
return view('projects.show', ['project' => $project, 'similarProjects' => $similarProjects]);
}
An easy way to solve your issue would be to use the Relationship method directly instead of referring to it by property, which you can add additional filters just like any eloquent transaction.
In other words, you would need to replace this:
Tag::find($tag_ids)->projects
With this:
Tag::find($tag_ids)->projects()->where('id', '!=', $id)->get()
Where $id is the current project's id. The reason behind this is that by using the method projects(), you are referring your model's defined Relationship directly (most probably a BelongsToMany, judging by your code) which can be used as a Query Builder (just as any model instance extending laravel's own Eloquent\Model).
You can find more information about laravel relationships and how the Query Builder works here:
https://laravel.com/docs/5.1/eloquent-relationships
https://laravel.com/docs/5.1/queries
However, the way you are handling it might cause some issues along the way.
From your code i can assume that the relationship between Project and Tag is a many to many relationship, which can cause duplicate results for projects sharing more than 1 tag (just as stated by user Ohgodwhy).
In this type of cases is better to use laravel's whereHas() method, which lets you filter your results based on a condition from your model's relation directly (you can find more info on how it works on the link i provided for eloquent-relationships). You would have to do the following:
// Array containing the current post tags
$tagIds = [...];
// Fetch all Projects that have tags corresponding to the defined array
Project::whereHas('tags', function($query) use ($tagIds) {
$query->whereIn('id', $tagIds);
})->where('id', !=, $postId)->get();
That way you can exclude your current Project while avoiding any duplicates in your result.
I don't think that Tag::find($tag_ids)->projects is a good way to go about this. The reason being is that multiple tags may belong to a project and you will end up getting back tons of project queries that are duplicates, resulting in poor performance.
Instead, you should be finding all projects that are not the existing project. That's easy.
$related_projects = Project::whereNotIn('id', [$project->id])->with('tags')->get();
Also you could improve your code by using Dependency Injection and Route Model Binding to ensure that the Model is provided to you automagically, instead of querying for it yourself.
public function show(Project $project)
Then change your route to something like this (replacing your controller name with whatever your controller is:
Route::get('/projects/{project}', 'ProjectController#show');
Now your $project will always be available within the show function and you only need to include tags (which was performed in the "with" statement above)
I'm looking to a better way to write this function.
It's inside a Doctrine Entity Model
public function getCompanySubscriptions()
{
foreach ($this->subscriptions as $key => $value) {
if ($value->getPlan()->getType() == 'E' && $value->getActive()) {
return $value;
}
}
return null;
}
$this->subscriptions is a many-to-one collection and can have different "types" (but only one of them with type "E").
Problem is: If the Company has too many $subscriptions this function will be too slow to return that only one of type "E" which I need to check when building the view with TWIG. The solution would be to use a QueryBuilder, but I haven't found a way to use it directly from the entity model.
You cannot use a QueryBuilder inside your entity, but instead you can use doctrine Criteria for filtering collections (on SQL level). Check the documentation chapter 8.8. Filtering Collections for more details on Criteria.
If the collection has not been loaded from the database yet, the filtering API can work on the SQL level to make optimized access to large collections.
For example to only get active subscriptions:
$subscriptions = $this->getCompanySubscriptions();
$criteria = Criteria::create()
->where(Criteria::expr()->eq("active", true));
$subscriptions = $subscriptions ->matching($criteria);
Like that you can solve your performance issues, since the collection is loaded from the database using the conditions from the Criteria directly.
The problem in your case might be that you need to join on Plan, but joining is not possible in a Criteria. So if joining is really necessary then you should consider using a custom query where you do the join with conditions in your company EntityRepository (for example with a QueryBuilder).
Note.
The foreach in your question can be rewritten using the filter method from the ArrayCollection class. The filter takes a predicate and all elements satisfying the predicate will be returned.
Look also here in the doctrine 2 class documentation for more info.
Your predicate would look something like:
$predicate = function($subscription){
$subscription->getPlan()->getType() == 'E' && $subscription->getActive();
}
and then:
return $this->subscriptions->filter($predicate);
This may be a dupe but I've been trawling for some time looking for a proper answer to this and haven't found one yet.
So essentially all I want to do is join two tables and attach a where condition to the entire collection based on a field from the joined table.
So lets say I have two tables:
users:
-id
-name
-email
-password
-etc
user_addresses:
-address_line1
-address_line2
-town
-city
-etc
For the sake of argument (realising this may not be the best example) - lets assume a user can have multiple address entries. Now, laravel/eloquent gives us a nice way of wrapping up conditions on a collection in the form of scopes, so we'll use one of them to define the filter.
So, if I want to get all the users with an address in smallville, I may create a scope and relationships as follows:
Users.php (model)
class users extends Eloquent{
public function addresses(){
return $this->belongsToMany('Address');
}
public function scopeSmallvilleResidents($query){
return $query->join('user_addresses', function($join) {
$join->on('user.id', '=', 'user_addresses.user_id');
})->where('user_addresses.town', '=', 'Smallville');
}
}
This works but its a bit ugly and it messes up my eloquent objects, since I no longer have a nice dynamic attribute containing users addresses, everything is just crammed into the user object.
I have tried various other things to get this to work, for example using a closure on the relationship looked promising:
//this just filters at the point of attaching the relationship so will display all users but only pull in the address where it matches
User::with(array('Addresses' => function($query){
$query->where('town', '=', 'Smallville');
}));
//This doesnt work at all
User::with('Addresses')->where('user_addresses.town', '=', 'Smallville');
So is there an 'Eloquent' way of applying where clauses to relationships in a way that filters the main collection and keeps my eloquent objects in tact? Or have I like so many others been spoiled by the elegant syntax of Eloquent to the point where I'm asking too much?
Note: I am aware that you can usually get round this by defining relationships in the other direction (e.g. accessing the address table first) but this is not always ideal and not what i am asking.
Thanks in advance for any help.
At this point, there is no means by which you can filter primary model based on a constraint in the related models.
That means, you can't get only Users who have user_address.town = 'Smallwille' in one swipe.
Personally I hope that this will get implemented soon because I can see a lot of people asking for it (including myself here).
The current workaround is messy, but it works:
$products = array();
$categories = Category::where('type', 'fruit')->get();
foreach($categories as $category)
{
$products = array_merge($products, $category->products);
}
return $products;
As stated in the question there is a way to filter the adresses first and then use eager loading to load the related users object. As so:
$addressFilter = Addresses::with('Users')->where('town', $keyword)->first();
$users= $addressFilter->users;
of course bind with belongsTo in the model.
///* And in case anyone reading wants to also use pre-filtered Users data you can pass a closure to the 'with'
$usersFilter = Addresses::with(array('Users' => function($query) use ($keyword){
$query->where('somefield', $keyword);
}))->where('town', $keyword)->first();
$myUsers = $usersFilter->users;
OK lets say I want to select a number of columns from a database table, but I won't know what those columns are in the method. I could pass them in, but it could be more or less depending on the method calling the database method.
A quick fix would be SELECT *, but I understand that this is bad and can cause more data to be returned than is necessary, and I definitely don't need all the data from that table.
So I am using CodeIgniter and prepared statements to do this, and below is what I have currently (it works, just point that out).
function get_pages() {
$this->db->select('pages.id, pages.title, pages.on_nav, pages.date_added, admin.first_name, admin.last_name')
->from('pages, admin')
->where('pages.admin_id = admin.id')
->order_by('pages.id', 'ASC');
$query = $this->db->get();
return $query->result();
}
It's a simple function, but at the moment limited to getting only 'pages'. I want to convert this to work with getting from other tables too. What is the best way?
Many thanks in advance.
EDIT In CodeIgniter I have many Controllers. One for 'pages', one for 'products', one for 'news' and on and on. I don't want to create a single database query method in my model for each controller.
i think the desire to not have 4 methods is misguided. if you don't have the information in the method, you'll have to pass it in. so you could either pass in a string with the table you want and switch over that changing the query based on the table name, or pass in all of the necessary parts of the query. this would include table name, criteria column, criteria, and columns to select. and you'd need to pass that information in every time you called the function. neither of those two methods are really going to save you much code, and they're both less readable than a function for each purpose.
The entire idea with models to put your specific queries to the persistence layer in there. Using a generic catch-all method can be disastrous and hard to test. You should shape your model around the problem you're trying to solve.
This makes it much cleaner and easier to work with. At the same time you must also avoid the common trap of over-sizing models. Each model should follow the SRP. Try and separate concerns so that in your controller, you can easily see state changes.
Does that make sense or am I just rambling...?
In your model:
function get_pages($table_source) {
$this->db->select($table_source.".id"); // or $this->db->select('id');
// for instance, if one of your $table_source ="users" and there is no 'title' column you can write
if($table_source!='users') $this->db->select('title');
$this->db->select('on_nav');
$this->db->select('date_added');
$this->db->select('admin.first_name');
$this->db->select('admin.last_name');
$this->db->join('admin','admin.id = '.$table_source.'.admin_id')
$this->db->order_by('pages.id', 'ASC');
$query = $this->db->get($table_source);
return $query->result_array();
}
In your controller:
function all_tables_info() {
$tables = array("pages","users","customers");
$i=0;
foreach($tables as $table) {
$data[$i++]=$this->your_Model->get_pages($table);
}
//do somthing with $data
}