eloquent querybuilder pass column to nested query - database

I would like to query the db to retrieve all events that do not have a comment created after the start date. Following structure for EVENT and COMMENT
EVENT
id
start
created_at
updated_at
COMMENT
id
comment
event_id
created_at
updated_at
Create a query ( $query = Event::query()->with('comments'); ) which gives me the following construct.
[{
"id": 1,
"start": "2018-06-18 12:00:00",
"created_at": "2018-06-18 11:50:07",
"updated_at": "2018-07-02 12:13:15",
"comments": [
{
"id": 1,
"created_at": "2018-06-18 12:44:35",
"updated_at": "2018-06-18 12:44:35",
"comment": "cascsac"
}
]
}]
I want to retrieve all entries where comment.created_at is after the start date of the event. I tried with the following:
$query->orWhere(function ($query) {
$value = $query->where(function ($query) use ('start') {
$query->whereHas('comments', function ($query) use ('start') {
$query->where('created_at','<=','start');
//->whereColumn('created_at', '<=', 'start');
});
});
});
But its does not like my attempts to pass start to a nested query and am not sure how to pass it for whereColumn query.
How do I pass values from columns to a nested where.
Or how else do I query a with the value of a separate column.
Edit1
On request the whole query
$query
Event::query()->with('comments');
$query->orWhere(function ($query) {
$value = $query->where(function ($query) use ('start') {
$query->whereHas('comments', function ($query) use ('start') {
$query->where('created_at','>=','start');
//->whereColumn('created_at', '>=', 'start');
});
});
});
$events = $query->get();
Passing variable $start would work too but I cannot convert column value of start to variable $start to pass the nested query

Answered on https://laracasts.com/discuss/channels/eloquent/querybuilder-pass-column-to-nested-query
$events = Event::with('comments')->whereHas('comments',function ($query) {
$query->whereColumn('comments.created_at','<','events.start');
})->get();
whereColumn is function to compare columns in query.

Wrong code is 'start'> It must be $start
$start = Carbon::now()->subYears(100); // for examplee
$query->orWhere(function ($query) {
$value = $query->where(function ($query) use ($start) {
$query->whereHas('comments', function ($query) use ($start) {
$query->where('created_at','<=', $start);
//->whereColumn('created_at', '<=', $start);
});
});
})

Related

Powershell Specific Json Value

I have a json that I wanted to generate using Powershell
{
"CreatedBy": "div",
"Label": "jss key",
"Scopes": ["content-#everything#", "audience-delivery"]
}
I am using the below to generate the json but it is not successful
$EdgeClientID = 'fsfsdfSDFsda'
$scope = '[ "content-#everything#", "audience-delivery" ]'
$body = #{
'CreatedBy' = $EdgeClientID
'Label' = 'jss key'
'Scopes' = $scope
} | ConvertTo-Json
the result I am getting is
{
"CreatedBy": "fsfsdfSDFsda",
"Label": "jss key",
"Scopes": "[\"content-#everything#\", \"audience-delivery\"]"
}
The value of scope is not coming right. Can someone please help?
The reason why you're not getting the desired output from it is because the following is an array only in the context of JSON and not in the context of PowerShell and ConvertTo-Json interprets it a simple string:
$scope = '[ "content-#everything#", "audience-delivery" ]'
The following should fix the problem:
$EdgeClientID = 'fsfsdfSDFsda'
$scope = "content-#everything#", "audience-delivery"
$body = #{
'CreatedBy' = $EdgeClientID
'Label' = 'jss key'
'Scopes' = $scope
} | ConvertTo-Json
You could also create a blue print class for your JSON:
class JSONBluePrint {
[string]$CreatedBy
[string]$Label
[object[]]$Scopes
JSONBluePrint () { }
JSONBluePrint([string]$CreatedBy, [string]$Label, [object[]]$Scopes)
{
$this.CreatedBy = $CreatedBy
$this.Label = $Label
$this.Scopes = $Scopes
}
[string] ToJson () {
return $this | ConvertTo-Json -Depth 100
}
}
[JSONBluePrint]::new(
'div',
'jss key',
#('content-#everything#', 'audience-delivery')
).ToJson()
$json = [JSONBluePrint]::new()
$json.CreatedBy = 'div'
$json.Label = 'jss key'
$json.Scopes = 'content-#everything#', 'audience-delivery'
$json.ToJson()

laravel update json query

I have mysql db with column that called "info", this is json column.
I have there this json:
{
"pizza":{
"sugar":"yes",
"calorie":"100",
"protein":"no"
},
"hamburger":{
"sugar":"no",
"calorie":"120",
"protein":"yes"
}
}
when I want to update for example the calorie of the pizza there is no problem:
DB::table('food')->where('id', '=', '1')
->update(array('info->pizza->calorie' => '90'));
then in the db i have:
{
"pizza":{
"sugar":"yes",
"calorie":"90",
"protein":"no"
},
"hamburger":{
"sugar":"no",
"calorie":"120",
"protein":"yes"
}
}
but when i want to add some food, for example chocolate:
DB::table('food')->where('id', '=', '1')
->update(array('info->chocolate->calorie' => '10'));
nothing happened.
In which way I can do that? thanks!
You can't update non-existing keys for json columns in MySQL table. Look at this post to better understand the reason why.
To solve this problem, your best bet is to retrieve the column json data, decode it, insert new object entry, encode it again and finally update the table with the new json data.
$food = DB::table('food')->where('id', '=', '1')->first();
$info = json_decode($food->info);
$info->chocolate = (object)["calorie"=>"10"];
DB::table('food')->where('id', '=', '1')
->update(['info' => json_encode($info)]);
Which version of Laravel are you using? I'm able to set new keys in JSON cast columns using both 5.7 and 5.8:
User Model:
protected $casts = [
'phone_numbers' => 'json',
];
Updating:
User::first()->phone_numbers;
[
'mobile' => '0000000000',
]
User::first()->update(['phone_numbers->office'] => '9999999999']);
User::first()->phone_numbers;
[
'mobile' => '0000000000',
'office' => '9999999999',
]
Updating nested values:
User::first()->update(['phone_numbers->office->ext'] => '100']);
User::first()->phone_numbers;
[
'mobile' => '0000000000',
'office' => [
'ext' => '100',
],
]
Edit: You don't happen to have $fillable set for the info column, do you? If so, I believe you'll have to specify each individual property for mass-assignment:
protected $fillable = [
'info->chocolate',
];
You can test it quickly by removing it from $fillable and setting $guarded to an empty array (temporarily):
protected $guarded = [];
I know this is the exact answer, as 2 queries have to be performed on DB. But this is a workaround.
$food = DB::table('food')->where('id', '=', '1')->first();
$info = json_decode($food->info, true);
$info['chocolate']['calorie] = 10;
$food->info = json_encode(json_encode($info), JSON_FORCE_OBJECT);
$food->save();

CakePHP OR condition not properly working

I have the following problem: I have a table for customers, with a prename and a name. Now I have variable keywords (in this example hardcoded 2) in an array and I want to check whether one of the keywords match with either the name OR the prename. So I figured to use the OR condition in the following codesnippet. Unfortunately it doesnt provide the wanted output:
$searchValueArr = ["keyword1","keyword2"];
$customers = $customersTable->find()
->where(function (QueryExpression $exp) {
$orConditions = $exp->or_(function ($or) {
foreach($searchValueArr as $searchValue) {
$or = $or
->eq('prename LIKE', "%".$searchValue."%")
->eq('name LIKE', "%".$searchValue."%");
}
return $or;
});
return $orConditions;
})
->all();
You need to list $searchValueArr in the use part of each anonymous function, otherwise it's not in context, ex:
$searchValueArr = ["keyword1","keyword2"];
$customers = $customersTable->find()
->where(function (QueryExpression $exp) use ($searchValueArr){
$orConditions = $exp->or_(function ($or) use ($searchValueArr){
foreach($searchValueArr as $searchValue) {
$or = $or
->eq('prename LIKE', "%".$searchValue."%")
->eq('name LIKE', "%".$searchValue."%");
}
return $or;
});
return $orConditions;
})
->all();
Also this is personal preference really, but you technically can still use array formatting for a query like this (a nested set of OR's), for example:
$searchValueArr = ["keyword1","keyword2"];
$searchTerms = [];
foreach($searchValueArr as $searchValue) {
$searchTerms[] = [
'OR' => [
'prename LIKE' => "%".$searchValue."%",
'name LIKE' => "%".$searchValue."%"
]
];
}
$customers = $customersTable->find()
->where([
'OR' => $searchTerms
])
->all();

Invalid argument supplied for foreach() unpacking Facebook array in Laravel 4

I am trying to store the FBIDs of a Facebook user's friends in the column of a mysql database. I've tried looking up other answer on this issue, and I have tried to implement it (in Laravel 4). Here is what I have done:
In the Facebook.php file, one of the providers:
'friends' => 'https://graph.facebook.com/me/friends?access_token='.$token->access_token
In my Oauth2 Controller:
$friends_list = $user['friends'];
$friends_list_array = json_decode($friends_list,true);
$arr= $friends_list_array['data'];
$friend_ids_arr = array();
foreach($arr as $friend) {
$friend_ids_arr[] = $friend['id'];
}
$friend_ids = implode("," , $friend_ids_arr);
And then I want to store the $friend_ids object in a "text" column in my database. However, when running this, I keep getting the error: Invalid argument supplied for foreach()
But it is very clearly being supplied an array as it should. Is there something I'm not seeing? Thank you for your help.
Actually the returned result is a json, the returned object should look something like this
{
"id": "xxxxxxx",
"name": "Sheikh Heera",
"friends": {
"data": [
{ "name": "RaseL KhaN", "id": "xxx" },
{ "name": "Yizel Herrera", "id": "xxx" }
],
"paging": {
"next": "https://graph.facebook.com/xxx/friends?limit=..."
}
}
}
After you json_decode
$user = json_decode($user, true);
It should look something like
Array
(
[id] => xxxxxxx
[name] => Sheikh Heera
[friends] => Array
(
[data] => Array
(
[0] => Array
(
[name] => RaseL KhaN
[id] => xxx
)
[1] => Array
(
[name] => Yizel Herrera
[id] => xxx
)
)
[paging] => Array
(
[next] => https://graph.facebook.com/xxx/friends?limit=...
)
)
)
So, now you can
$friends_list = $user['friends'];
$data = $friends_list['data'];
Make sure your $data array is not empty and then loop
if(count($data)) {
$friend_ids_arr = array();
foreach($data as $friend) {
$friend_ids_arr[] = $friend['id'];
}
}
So, the foreach will run only when $data has items in it.
Update: It may help you
$url = "https://graph.facebook.com/me?fields=id,name,friends&access_token=YOUR_ACCESS_TOKEN";
$contents = json_decode(file_get_contents($url), true);
$friends = $contents['friends'];
$friend_ids_arr[]
foreach($friends['data'] as $friend)
{
$friend_ids_arr[] = $friend['id'];
}

Using Test::MockDBI multiple times with different results

I'm trying to test some code in different situations (for different result sets). I've got the first test running well, but the next one is trying to reuse the first "table".
My result sets:
my $usernames_many = [
{ username => '1234567' },
{ username => '2345678' },
];
my $usernames_empty = [
];
but now when I try these calls:
$mock_dbi->set_retval_scalar(MOCKDBI_WILDCARD, "SELECT username FROM location", $usernames_many);
is_deeply(find_multiple_registrations($mock_db, 15), [ '1234567', '2345678' ], "many entries");
$mock_dbi->set_retval_scalar(MOCKDBI_WILDCARD, "SELECT username FROM location", $usernames_empty);
is_deeply(find_multiple_registrations($mock_db, 15), [ ], "no entries");
The first test passes, but the second one results in:
not ok 3 - no entries
# Failed test 'no entries'
# at ./report_many_registrations_test.pl line 28.
# Structures begin differing at:
# $got->[0] = '1234567'
# $expected->[0] = Does not exist
Which seems to indicate the first resultset was used again instead. How can I clean a resultset? Or reset the state in some other way?
The implementation of set_retval_scalar may at first appear discouraging:
sub set_retval_scalar {
my $self = shift; # my blessed self
my $type = shift; # type number from --dbitest=TYPE
my $sql = shift; # SQL pattern for badness
push #{ $scalar_retval{$type} },
{ "SQL" => $sql, "retval" => $_[0] };
}
The reason the first resultset appeared to be used again is successive calls to set_retval_scalar are cumulative. After the second call to set_retval_scalar, just before the second test, the internal bookkeeping for Test::MockDBI resembles
[ # first resultset
{ SQL => "SELECT username ...",
retval => [{ username => '1234567' }, ...]
},
# second resultset
{ SQL => "SELECT username ...",
retval => []
}
]
Under the hood when your second test queries SELECT username ..., _force_retval_scalar in Test::MockDBI searches this data structure for the currently executing query and stops on the first hit it finds. Both resultsets are associated with the same query, so the second doesn't have a chance to match.
But there's hope! Notice that set_retval_scalar copies only the outermost reference—a reference to an array that you control!
Modify your test slightly:
my #usernames_many = (
{ username => '1234567' },
{ username => '2345678' },
);
my #usernames_empty = ();
my $usernames = [];
$mock_dbi->set_retval_scalar(
MOCKDBI_WILDCARD,
"SELECT username FROM location",
$usernames);
With this fixture, you need only change the contents of #$usernames (that is, the array referred to by $usernames) to change the canned result of the query:
#$usernames = #usernames_many;
is_deeply(find_multiple_registrations($mock_db, 15),
[ '1234567', '2345678' ],
"many entries");
#$usernames = #usernames_empty;
is_deeply(find_multiple_registrations($mock_db, 15),
[ ],
"no entries");
With these modifications, both tests pass.
IMPORTANT: Always assign to #$usernames! You may be tempted to save a few keystrokes by writing
$usernames = []; # empty usernames
is_deeply(find_multiple_registrations($mock_db, 15),
[ ],
"no entries");
but this will cause your test to fail for nearly the same reason as the test from your question: the fixture will continue to have the same reference that you gave it in the call to set_retval_scalar. Doing it this way would be both incorrect and misleading, a nasty combination.
For completeness, a full working example is below.
#! /usr/bin/perl
use warnings;
use strict;
BEGIN { push #ARGV, "--dbitest" }
use Test::MockDBI qw/ :all /;
use Test::More tests => 2;
my #usernames_many = (
{ username => '1234567' },
{ username => '2345678' },
);
my #usernames_empty = ();
my $usernames = [];
my $mock_dbi = get_instance Test::MockDBI;
my $mock_db = DBI->connect("dbi:SQLite:dbname=:memory:", "", "");
$mock_db->{RaiseError} = 1;
$mock_db->do(q{CREATE TABLE location (username char(10))});
sub find_multiple_registrations {
my($dbh,$limit) = #_;
my $sth = $dbh->prepare("SELECT username FROM location");
$sth->execute;
[ map $_->{username} => #{ $sth->fetchall_arrayref } ];
}
$mock_dbi->set_retval_scalar(
MOCKDBI_WILDCARD,
"SELECT username FROM location",
$usernames);
#$usernames = #usernames_many;
is_deeply(find_multiple_registrations($mock_db, 15),
[ '1234567', '2345678' ],
"many entries");
#$usernames = ();
is_deeply(find_multiple_registrations($mock_db, 15),
[ ],
"no entries");
Output:
1..2
connect() 'CONNECT TO dbi:SQLite:dbname=:memory: AS WITH '
do() 'CREATE TABLE location (username char(10))'
prepare() 'SELECT username FROM location'
execute()
fetchall_arrayref()
ok 1 - many entries
prepare() 'SELECT username FROM location'
execute()
fetchall_arrayref()
ok 2 - no entries
If you (are able to) change the second test to something like:
$mock_dbi->set_retval_scalar(
MOCKDBI_WILDCARD,
"Get me username stuff", # <= something different
$usernames_empty
);
then you may find that the test now works.
This is because Test::MockDBI only uses the SQL text provided has a placeholder for which it returns the DBI object after a matching dbi->prepare( 'Get me username stuff' );
Update - Here is a workaround that doesn't require changing the SQL:
BEGIN { push #ARGV, "--dbitest=1"; }
use 5.012;
use warnings;
use Test::More;
use Test::MockDBI ':all';
my $mock_dbi = Test::MockDBI::get_instance;
my $dbh = DBI->connect(q{}, q{}, q{});
my $sql = 'SELECT username FROM location';
my $Data = [
{ username => '1234567' },
{ username => '2345678' },
];
$mock_dbi->set_retval_scalar( MOCKDBI_WILDCARD, $sql, sub { $Data } );
is_deeply( get_mock_user($dbh, $sql), [1234567,2345678], 'many entries' );
$Data = []; # change the data!
is_deeply( get_mock_user($dbh, $sql), [], 'no entries' );
done_testing;
sub get_mock_user {
my ($dbh, $sql) = #_;
$dbh->prepare( $sql );
[ map { $_->{username} } #{ $dbh->fetchrow_arrayref } ];
}
/I3az/

Resources