in D2.1, querybuilder, how do i use the day of week mysql function, i dont think its available in
doctrine but isnt there a way i could otherwise?
$qb->select('p')
->where('YEAR(p.postDate) = :year')
->andWhere('MONTH(p.postDate) = :month')
->andWhere('DAYOFWEEK(p.postDate) = :dayOfWeek');
You can write a doctrine extension to do the job:
Xyz\SomeBundle\DoctrineExtension\DayOfWeek.php
class DayOfWeek extends FunctionNode
{
public $date;
/**
* #override
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return "DAYOFWEEK(" . $sqlWalker->walkArithmeticPrimary($this->date) . ")";
}
/**
* #override
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->date = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
app/config/config.yml
doctrine:
orm:
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
default:
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
dql:
datetime_functions:
dayofweek: Xyz\SomeBundle\DoctrineExtension\DayOfWeek
More detail check here: DoctrineExtensions
It is not possible to use all of the SQL functions with the query builder. Some of them are defined (AVG, SUM, etc), but most aren't (including DAY/WEEK/MONTH).
You can still write native SQL:
// creating doctrines result set mapping obj.
$rsm = new Doctrine\ORM\Query\ResultSetMapping();
// mapping results to the message entity
$rsm->addEntityResult('AppBundle\Entity\Post', 'p');
$rsm->addFieldResult('p', 'id', 'id');
$rsm->addFieldResult('p', 'postDate', 'postDate');
$sql = "SELECT id, postDate
FROM your_table
WHERE YEAR(postDate) = ?
AND [...]";
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, $your_year_here);
$query->getResult();
Related
I'm trying the following: I have two models (Pub and Schedule) related by a 1xN relationship as follows:
Pub:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function pubSchedules()
{
return $this->hasMany(Schedule::class);
}
Schedule:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function pub()
{
return $this->belongsTo(Pub::class);
}
Table schedules has the following fields:
id | pub_id | week_day | opening_time | closing_time |
I use the following function to know if one pub is currently (or not) open:
/**
* #return bool
*/
public function isPubCurrentlyOpen()
{
$schedules = Schedule::where([
['pub_id', $this->id ],
['week_day', Carbon::now()->dayOfWeek],
])->get();
foreach ($schedules as $schedule){
$isOpen[] =
Carbon::now('Europe/Madrid')->between(
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->opening_time),
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->closing_time)
);
}
if(in_array(true, $isOpen)){
return true;
//return "Pub Opened";
}
return false;
//return "Pub Closed";
}
In my PubController I'd like, when the option "Filter by open pubs" is chosen if($request->openPubs == 1), to show only opened pubs isOpen ==true.
Knowing the relationships between models, how can I do it?
I'm looking for something like this:
if($request->openPubs == 1)
{
$pubs = $pubs->with('pubSchedules')->where('isOpen' == true);
}
Can you help me?
Thanks a lot!
You can do this using a "whereHas"
$openPubs = Pub::whereHas('schedule', function ($query) {
$query->where('week_day', Carbon::now()->dayOfWeek);
$query->whereRaw(
"'".Carbon::now('Europe/Madrid')->format("H:i:s")."' BETWEEN opening_time AND closing_time"
);
})->get();
This is assuming your opening time and closing time are the appropriate time format and not strings (though strings will work as well in a 24h format).
You might achieve something similar to what you are looking for by using a scope e.g.
public function scopeFilterBy($query, $filter = null) {
if ($filter == "isOpen") {
$query->whereHas('schedule', function ($query) {
$query->where('week_day', Carbon::now()->dayOfWeek);
$query->whereRaw(
"'".Carbon::now('Europe/Madrid')->format("H:i:s")."' BETWEEN opening_time AND closing_time"
);
});
}
return $query; //Not sure if this is needed
}
You could then do:
Pub::filterBy($request->openPubs ? "isOpen" : null)->get();
I don't fully understand how you are trying to accomplish this but it should be something like this
$pubs = Pub::with(['pubSchedules' => function ($query) {
$query->where('opening_time', '>' ,Carbon::now()) // make sure it's currently open
->where('closing_time', '<' ,Carbon::now()) // make sure that it's not finished already
->where('week_day', '==' ,Carbon::now()->dayOfWeek) // make sure it's today
}])->find($id);
// to get if pub is currently
if($pub->pubSchedules->count()){
//
}
you can put this code in the model (Pub) and make some changes
if you already have the object you can do this (Add it to model)
public function isPubOpen()
{
$this->load(['pubSchedules' =>
// same code in other method
]);
return (bool) $this->pubSchedules->count();
}
For small tables you could call the function isPubCurrentlyOpen for each element.
For this you would need to change your function to recieve the pub_id as a parameter:
public function isPubCurrentlyOpen($pub_id)
{
$schedules = Schedule::where([
['pub_id', $pub_id ],
['week_day', Carbon::now()->dayOfWeek],
])->get();
foreach ($schedules as $schedule){
$isOpen[] =
Carbon::now('Europe/Madrid')->between(
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->opening_time),
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->closing_time)
);
}
if(in_array(true, $isOpen)){
return true;
//return "Pub Opened";
}
return false;
//return "Pub Closed";
}
and to query the data do:
if($request->openPubs == 1)
{
// assuming $pubs is a collection instance
$pubs = $pubs->filter(function($a){
return $this->isPubCurrentlyOpen($a->id);
})
}
There's a feature in Eloquent called Eager Loading. The Eloquent ORM provides a simple syntax to query for all the Schedules that are related with this particular Pub as described below:
$pubIsOpen= $pub->schedules()
->where([
['week_day', Carbon::now()->dayOfWeek],
['opening_time' , '<' , Carbon::now('Europe/Madrid')],
['closing_time' , '>' , Carbon::now('Europe/Madrid')]
])
->count();
if($openPubCount > 0){
//PUB is open
}else{
//PUB is closed
}
If it helps to someone in the future I post my solution, thanks to #apokryfos:
Pub:
/**
* #param $pubs
* #return mixed
*/
public static function isPubCurrentlyOpen($pubs)
{
$pubs->whereHas( 'pubSchedules', function ($pubs) {
$pubs->where( 'week_day', Carbon::now()->dayOfWeek )
->whereRaw(
"'" . Carbon::now( 'Europe/Madrid' )->format( "H:i:s" ) . "' BETWEEN opening_time AND closing_time"
);
} );
return $pubs;
}
PubsController:
/**
* #param GetPubRequest $request
* #return ApiResponse
*/
public function getPubs(GetPubRequest $request)
{
$orderBy = 'id';
$order = 'asc';
$pubs = Pub::withDistance();
............
if($request->openPubs == 1)
{
$pubs = Pub::isPubCurrentlyOpen($pubs);
}
return $this->response(PubProfileResource::collection($pubs->orderBy($orderBy, $order)->paginate()));
}
I have a form, where users pick some options, and I would like to insert into DB an additional, concatenated field, based on user inputs. I need to do it on SCENARIO_CREATE (and update). E.g.:
type: A
size: 100
color: black
concatenated: A100black
I've tried it this way:
Model:
class Xyz extends BaseXyz {
const SCENARIO_CREATE = 'create';
public function rules() {
...
['concatenated', 'generateConcatenated', 'on' => self::SCENARIO_CREATE],
...
public function generateConcatenated() {
return $this->type . $this->size . $this->color;
}
Controller:
class XyzController extends base\XyzController {
public function actionCreate() {
$model = new Xyz;
$model->scenario = Xyz::SCENARIO_CREATE;
...
I've tried with 'filter' also in rules, but no success. Maybe my approach is totally wrong, and it can't be done in rules? Please point me in the right direction. Many thanks!
rules are for checking attribute validation but you can do it in rule too:
public function generateConcatenated($attribute,$param) {
$this->concatenated = $this->type . $this->size . $this->color;
}
I think best logistic way to achieve this is to remove attribute from rules and overriding beforeSave() in your model:
public function beforeSave($insert)
{
$this->concatenated = $this->type . $this->size . $this->color;
return parent::beforeSave($insert);
}
you should consider that yii have default insert scenario for new record and update for existing record.
I have a function named siblings which fetches all siblings of a user.
select siblings(id) as `siblings` from users where id = 1
I can access the function in Eloquent as
User::where('id', 1)->first([DB::raw(siblings(id) as `siblings`)]->siblings;
I want to make the siblings available via custom attribute.
I added siblings to $appends array
I also created getSiblingsAttribute method in my User model as
public function getSiblingsAttribute()
{
if (!$this->exists()) {
return [];
}
$siblings = User::where('idd', $this->id)
->first([DB::raw('siblings(id) AS `siblings`')])
->siblings;
return explode(',', $siblings);
}
But this is not working as $this->id returns null
My table schema is users(id, username,...), so clearly id is present.
Is there a way by which I can bind the siblings function while querying db and then returning something like $this->siblings from getSiblingsAttribute. If I can bind siblings(id) as siblings with query select globally as we do for scopes using global scope.
That way my code can be simply
public function getSiblingsAttribute()
{
return $this->siblings;
}
The simplest way is to create a view in your database and use that as a table:
protected $table = 'user_view';
Otherwise I need more information about your id == null problem.
If you can fix this by your own in the next step it is important that you use an other column name by selecting as in your accessor otherwise you run in an infinite loop.
public function getSiblingsAttribute()
{
if (!$this->exists()) {
return [];
}
$siblings = User::where('id', $this->id)
->first([DB::raw('siblings(id) AS `siblings_value`')])
->siblings_value;
return explode(',', $siblings);
}
EDIT
Sadly there is no simple way to archieve this.
But after a little bit tinkering I have found a (not very nice) solution.
Give it a try.
You have to add the following class and trait to your app.
app/Classes/AdditionalColumnsTrait.php (additional column trait)
namespace App\Classes;
trait AdditionalColumnsTrait {
public function newEloquentBuilder($query) {
$builder = new EloquentBuilder($query);
$builder->additionalColumns = $this->getAdditionalColumns();
return $builder;
}
protected function getAdditionalColumns() {
return [];
}
}
app/Classes/EloquentBuilder.php (extended EloquentBuilder)
namespace App\Classes;
use Illuminate\Database\Eloquent\Builder;
class EloquentBuilder extends Builder {
public $additionalColumns = [];
public function getModels($columns = ['*']) {
$oldColumns = is_null($this->query->columns) ? [] : $this->query->columns;
$withTablePrefix = $this->getModel()->getTable() . '.*';
if (in_array('*', $columns) && !in_array($withTablePrefix, $oldColumns)) {
$this->query->addSelect(array_merge($columns, array_values($this->additionalColumns)));
} elseif (in_array($withTablePrefix, $oldColumns)) {
$this->query->addSelect(array_values($this->additionalColumns));
} else {
foreach ($this->additionalColumns as $name => $additionalColumn) {
if (!is_string($name)) {
$name = $additionalColumn;
}
if (in_array($name, $columns)) {
if (($key = array_search($name, $columns)) !== false) {
unset($columns[$key]);
}
$this->query->addSelect($additionalColumn);
}
}
if (is_null($oldColumns)) {
$this->query->addSelect($columns);
}
}
return parent::getModels($columns);
}
}
after that you can edit your model like this:
class User extends Model {
...
use App\Classes\AdditionalColumnsTrait;
protected function getAdditionalColumns() {
return [
'siblings' => DB::raw(siblings(id) as siblings)),
];
}
...
}
now your siblings column will be selected by default.
Also you have the option to select only specific columns.
If you don't want to select the additional columns you can use: User::find(['users.*']).
Perhaps it is a solution for you.
Is it possible and what would be the best way to define a relation with a parameter in Yii2.
Situation is simple. I have table texts and texts_regional. texts_regional of course has foreign keys text_id and lang_id.
Gii generated a method to get all regional texts but I dont need that on the frontend. I just need in the current language.
Generated method is:
public function getTextsRegionals()
{
return $this->hasMany(TextRegional::className(), ['text_id' => 'id']);
}
Tried this but it's probably not right:
public function getReg($langId=null)
{
if($langId === null && Yii::$app->session->has('langId')) {
$langId = Yii::$app->session->get('langId');
}
return $this->hasOne(TextRegional::className(), ['text_id' => 'id', 'lang_id'=>$langId]);
}
I need data from both tables so I'd like to eager load this.
Is it just better to use separate method and manually construct the query?
Read in documentation that it's possible to do ->onCondition so wrote a method like this:
public function getReg($langId=1)
{
if(Yii::$app->session->has('langId')) {
$langId = Yii::$app->session->get('langId');
}
return $this->hasOne(TextRegional::className(), ['text_id' => 'id'])->onCondition(['lang_id' => $langId]);
}
$langId is set in main controller.
But I ended up using TextRegional model and joined with Text model to set condition.
Made a TextRegionalQuery class and added a new method:
public function byCode($code)
{
if(Yii::$app->session->has('langId')) {
$langId = Yii::$app->session->get('langId');
} else {
$langId = 1;
}
$this->joinWith('text0')
->andWhere("lang_id = '".$langId."'")
->andWhere("texts.code = '".$code."'");
return $this;
}
Using it like this:
$ft = TextRegional::find()->byCode("footer_text")->one();
Or
$news = TextRegional::find()->byType(2)->visible()->all();
/**
* relation with current LangContractTemplate
*/
public function getCurLangContractTemplate()
{
if(isset(Yii::$app->user->identity->u_lang) && !empty(Yii::$app->user->identity->u_lang))
$langId = Yii::$app->user->identity->u_lang;
else
$langId = \Yii::$app->language;
return $this->hasOne(LangContractTemplate::className(), ['lcont_cont_id' => 'cont_id'])->onCondition(['lcont_lang_id' => $langId]);
}
//------------------OR------------------
/**
* relation with language table
*/
public function getContractByLang()
{
return $this->hasOne(LangContractTemplate::className(), ['lcont_cont_id' => 'cont_id']);
}
/* and Get data */
$contract_content = ContractTemplate::find()
->joinWith(['contractByLang' => function($query) use ($lang) {
return $query->where(['lcont_lang_id' => $lang]);
}])
->one();
Why this ain't working?
class Condition extends Eloquent{
protected $connection = 'another-database-connection';
}
But it turns out it connects to the default database.
Any thoughts?
Edit:
The problem is in:
protected function fetchColumns($is = null)
{
if(!empty($this->table)){
$columns = DB::select('DESCRIBE '.$this->getTable());
foreach($columns as $column){
if(!in_array($column, array('id', 'created_at', 'updated_at', 'deleted_at'))){
$this->tableColumns[] = $column->Field;
}
}
}
}
It seems that Laravel mixes databases when trying to get the table columns, that's why I get Table doesn't exist error. The connection is fine.
change:
$columns = DB::select('DESCRIBE '.$this->getTable());
to:
$columns = $this->getConnection()->select('DESCRIBE '.$this->getTable());