Right now I have:
$products = Product::findAll([1,2,3,4]);
foreach ($products as $product){
$text = $product->part->type->texts;
}
This returns the related records from Texts table.
But I need to have only 1 record from it, and to do that I need to have one more condition in the last join type->texts, which is not defined in the model. It's dynamic session variable.
Is there any way to do this?
If you want modify the last relation query to have additional condition and return one record instead of many, simply change last relation call like so:
$text = $product->part->type->getTexts()->andWhere(...)->one();
Direct relation method call returns yii\db\ActiveQuery instance so you can modify conditions how you want.
If you want to use modified relation in more than just one place, create separate method for that:
/**
* #return yii\db\ActiveQuery
*/
public function getDynamicText()
{
// Insert link from texts relation similar as in hasMany() and additional condition in andWhere()
return $this->hasOne(...)->andWhere(...);
}
And then use it:
$text = $product->part->type->dynamicText;
In this case, scopes would be a handy solution, especially if you're going to use complicated conditions.
1. Start by creating a model that extends ActiveQuery with a method that will be used to add conditions to your query, for example active = 1:
namespace app\models;
use yii\db\ActiveQuery;
class TextQuery extends ActiveQuery
{
public function active($state = 1)
{
$this->andWhere(['active' => $state]); // or any other condition
return $this;
}
}
2. Override the find() method in your Text model:
public static function find()
{
return new \app\models\TextQuery(get_called_class());
}
3. Add a method in your Type model that retrieves your relational data via the newly made scope:
public function getActiveText()
{
return $this->hasMany(Text::className(), ['type_id' => 'id'])->active();
}
Finally, use it as follows:
$text = $product->part->type->activeText;
The docs are pretty clear on this, check 'em out.
Related
I need to change the queried table from a Laravel-model right before the query begins.
Normally you make a query like this:
ExampleModel::where('column_name', =, 'value')->get();
For one case I want to use a view-table which contains information from multiple tables combined in one view.
Therefore I need to switch the table of ExampleModel only for this one situation, e.g.:
ExampleModel::table('my_view')->where(...)->get();
It is not an option to use DB::table('my_view')->where(...)->get() because of several local scopes which need to be applied on ExampleModel.
As I could see, there are the following options:
somehow change the models table-name on the fly (as shown above)
Create a new Model used only in this use case which has the view defined as model-table
write all my scopes into a chained DB-command
Are there any other options?
The laravel way to Handle this would be to have a dedicated model for your view, with Scopes applying to each of the relevant models :
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class AgeScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('age', '>', 200);
}
}
Then in your ExampleModel AND you MyView Model
protected static function boot()
{
parent::boot();
static::addGlobalScope(new AgeScope);
}
So that when you want to edit a scope it will be reflected on each of your queries
BUT
You will allways know if your model is from your view_table or from you example_model table.
If you need to have some accessor or function used by both, I recomand you to put them in a Trait and use them on both Models
trait ExampleModelTrait
{
getTestAttribute(){
return strtolower($this->column_name);
}
}
and then
use ExampleModelTrait;
you can do this stuff by passing table name to setTable() method for example :
$user = new Users();
$user->setTable('customers');
$user->where(id,1)
...
I am using CakePHP 2.x and would like to apply pagination in my controller but I would like it to be available in one case with a contain key and the other case without the contain key. As an example consider this example:
class BlogController extends AppController {
public $name = "Blog";
public $paginate = array(
'Post'=>array(
'limit'=>30,
'conditions'=>array('publish'=>1),
),
);
function just_posts() {
// I want to paginate Post as it is
}
function posts_with_comments() {
// I want paginate Post with 'contain'=>'comments'
}
}
In my real life case the purpose in doing this is for performance, to reduce the query time. But I am at a loss how to implement this. The $this->paginate(...) will only accept an argument to filter records. Is there a way to make two paginators available for the same model in a controller?
You should be able to modify your controller's paginate property within the relevant action to include the contain before doing the paginate:-
$this->paginate['Post']['contain'] = 'Comment';
This would extend the controller's defaults for the specific action.
So I have a company based system and I am keeping all data within the one database and I separate the data by using a site_id field which is present in all tables and in the users table.
Now at the moment I am doing a condition on every single find('all'). Is there a more global way to do this. Maybe in the AppController? Even for saving data I am having to set the site_id every single save.
Something like
public function beforeFilter() {
parent::beforeFilter();
$this->set('site_id', $this->Auth->user('site_id'));
}
Any direction would only help. Thanks
TLDR:
Create a Behavior with a beforeFind() method that appends a site_id condition to all queries.
Example/Details:
Create a behavior something along the lines of the below. Note, I'm getting the siteId from a Configure variable, but feel free to get that however you want.
<?php
class SiteSpecificBehavior extends ModelBehavior {
public function setup(Model $Model, $settings = array()) {
$Model->siteId = Configure::read('Site.current.id');
}
public function beforeFind(Model $Model, $query = array()) {
$siteId = $Model->siteId;
$query['conditions'][$Model->alias . '.site_id'] = $siteId ;
return $query;
}
}
Then on any/all models where you want to make sure it's site-specific, add:
public $actsAs = array('SiteSpecific');
This can be tweaked and improved up certainly, but it should give you a good idea.
The following is declared in model 'GradingPeriod':
class GradingPeriod extends AppModel {
public $belongsTo = array('AcademicYear' => array('className' => 'AcademicYear', 'foreignKey' => 'academic_year_id'));
...
public function getEnrolledSections(){
$this->recursive = 1;
debug($this->findById(21)); // Does **not** return AcademicYear
// model data when function is called
// from a different model.
debug($this->findById(21)); // **Does** return AcademicYear
// model data when function is called
// from a different model.
die();
}
}
When called from a controller or inside the GradingPeriod model, this works fine. The first 'find' call does return the GradingPeriod model's associated data (AcademicYear).
When called from a different model, the first 'find' call does not return the GradingPeriod model's associated data (AcademicYear). The second 'find' call does return the GradingPeriod model's associated data (AcademicYear).
class ReportCard extends AppModel {
public function callToGradingPeriod(){
$objGradingPeriod = ClassRegistry::init('GradingPeriod');
$objGradingPeriod->getEnrolledSections();
}
}
I have tried this with CakePHP 2.1.2 and 2.2.3 with the same results.
I know calling one model from another may be considered bad form, but why is this code behaving as it does? Thank you in advance for any assistance you can provide.
This is not really an answer as far as why it's working (or not) the way it is, but a suggestion for you and any future users trying something similar:
Don't EVER use recursive to get associated data. Set public $recursive = -1; in your AppModel and never look back. When you want related data, use CakePHP's Containable Behavior.
It might seem like recursive is your friend - or that it's "just easier", but I promise it will cause issues further down the road - either when you want more data, or just when you get more data in your database (memory issues/errors among other things). It's bad practice, and I believe they're even going to remove recursive all together in CakePHP 3+.
Trust me on this one - ditch recursive, and use contain() instead.
<?php
class User extends AppModel {
var $name = 'User';
var $displayField = 'fname';
}
How can I only return users from this model that have a "standing" of "1"? I am not looking to do this from the controller but, from the model.
[Solution] In model
function beforeFind($queryData){
$queryData['conditions']['standing'] = 1;
return $queryData;
}
The easiest way to do this would be to put in some filtering conditions in your beforeFind callback. Modifying the $queryData variable and adding your restriction to the conditions key should do it.
From the manual entry - http://book.cakephp.org/1.3/en/view/1049/beforeFind
Called before any find-related operation. The $queryData passed to
this callback contains information about the current query:
conditions, fields, etc.
If you do not wish the find operation to begin (possibly based on a
decision relating to the $queryData options), return false. Otherwise,
return the possibly modified $queryData, or anything you want to get
passed to find and its counterparts.
You might use this callback to restrict find operations based on a
user’s role, or make caching decisions based on the current load.