To improve my php experiences I have written a small movie databse with the TMDB API.
Now I have data from the API and data from own codings. I have a database movieSeen where people can save movies they have already seen.
global $pdo;
$stmt = $pdo->prepare("SELECT * FROM movieSeen WHERE user_id = '" . $_SESSION['id'] . "'");
$stmt->execute();
foreach ($stmt as $row ) {
echo $row['movie_id'] . ', ';
} // outputs the IDs of the movies already seen. here: 157, 189, 298, 456
Now I have a API Call where I load all movies from a specific director.
$url = "https://api.themoviedb.org/3/person/" . $personID . "/movie_credits?api_key=" . $apiKey . "&" . $language . "";// path to your JSON file
$data = file_get_contents($url); // put the contents of the file into a variable
$personCareer = json_decode($data); // decode the JSON feed
foreach ($personCareer->crew as $showCrewDetails) { //output of the movies of a specific director
echo '<tr>';
echo '<td>' . $showCrewDetails->title . ' ' . $showCrewDetails->id . ' (' . substr($showCrewDetails->release_date, 0, 4) . ')</td>';
echo '<td><span class="badge badge-danger">Movie Seen</span></td>';
echo '<td>' . $showCrewDetails->job . '</td>';
echo '</tr>';
}
Every movie which has alredy be seen should be checked as seen in this list. So I have to check if the IDs from my database are included in the API call.
I think I have to use something like in_array or array_search to see if $showCrewDetails->id is equal to $row['movie_id'].
But I don't know how to exactly do it. I am thankful for every hint.
I would do something like this:
/**
* #param int $user_id DB user ID
* #param PDO $pdo
* #return int[] User movie IDs
*/
function getUserSeenMovieIds(int $user_id, PDO $pdo) : array {
$stmt = $pdo->prepare('SELECT movie_id FROM movieSeen WHERE user_id = :user_id');
$stmt->execute([':user_id' => $user_id]);
return $stmt->fetchAll(PDO::FETCH_COLUMN, 0) ?: [];
}
/**
* #return array Response from api.themoviedb.org/3/person/{int}/movie_credits
*/
function getMovieCredits(int $personID, string $apiKey, string $language) : array {
$query = http_build_query([
'api_key' => $apiKey,
'language' => $language,
]);
$url = 'https://api.themoviedb.org/3/person/' . $personID . '/movie_credits?' . $query;
$data = file_get_contents($url);
$credits = json_decode($data, true);
if (!is_array($credits)) {
throw new \RuntimeException("Failed decoding response $data from URL $url");
}
return $credits;
}
/**
* #param array $credits Response from api.themoviedb.org/3/person/{int}/movie_credits
* #param int[] $movie_ids
* #return array Crew details from the response filtered by the movie IDs
*/
function getMovieCrewDetails(array $credits, array $movie_ids) : array
{
$reference = array_flip($movie_ids);
$credits = [];
foreach ($credits['crew'] as $crew) {
$id = $crew['id'];
if (isset($reference[$id])) {
$credits[] = $crew;
}
}
return $credits;
}
////////////////////////////////////////////////////////////////////////////////
$movie_ids = getUserSeenMovieIds($user_id, $pdo);
$credits = getMovieCredits($personID, $apiKey, $language);
foreach (getMovieCrewDetails($credits, $movie_ids) as $crew) {
// TODO: Print $crew
}
The code above avoids nested loop (which would result in O(n*m) complexity) in getMovieCrewDetails by using a hash table $reference. Hash tables are O(1) average case complexity, so using them for moderate data sets is generally a good idea.
There is a number of other things to note:
do use PDO placeholders, since they free you from the need to escape the input;
do not use global, since you can easily avoid pollution of the global name space by using functions, classes and techniques like dependency injection;
always check for the values functions return; otherwise, you are risking to catch an unexpected runtime error or bug (I've put some error checking into the code, but you should really check every function call.)
if you have $showCrewDetails->id is multiple value.
use foreach
foreach ($showCrewDetails as $key => $val) {
$id_fromapi = $val->id;
$stmt = $pdo->prepare("SELECT *FROM movieSeen WHERE user_id ='".$id_fromapi ."'");
$stmt->execute();
}
Related
I have a some data coming from the db via a model
public static function loadPermissions($user_role)
{
$db = \Config\Database::connect();
$builder = $db->table('role_has_perm');
$builder->select('*');
$builder->join('perms', 'perms.id_perm = role_has_perm.id_perm', 'left');
$builder->join('roles', 'roles.id_role = role_has_perm.id_role');
$builder->where('role_has_perm.id_role', $user_role);
$query = $builder->get();
return $query->getResultObject();
}
Now if I loop trough it with a foreach loop I can spit out the data.
foreach ($query as $row) {
echo "id_role = " . $row->id_role . " " . "id_perm = " . $row->id_perm . " " . "role_name = " . $row->role_name;
$role = $row->role_name;
}
However I don't always want to loop through the whole object. I want to access specific properties.
I want to access only the role_name for example.
$role = $query->role_name;
I get this error: Trying to get property 'role_name' of non-object
Am I handling the object wrong ?
Any input is welcomed :)
Alex
Apparently, the $query variable contains an array. So you just need to access an element of the array by index. The following snippet would give you the first element of the array.
$role = $query[0]->role_name;
As an alternative you can fetch a specific single raw.
I can get the not-bind query on with this way :
\DB::enableQueryLog();
$items = OrderItem::where('name', '=', 'test')->get();
$log = \DB::getQueryLog();
print_r($log);
Output is :
(
[0] => Array
(
[query] => select * from "order_items" where "order_items"."name" = ? and "order_items"."deleted_at" is null
[bindings] => Array
(
[0] => test
)
[time] => 0.07
)
)
But what I really need is bind query like this :
select * from "order_items" where "order_items"."name" = 'test' and "order_items"."deleted_at" is null
I know I can do this with raw PHP but is there any solution in laravel core?
Actually I've created one function within helpers.php for same. You can also use same function within your helpers.php file
if (! function_exists('ql'))
{
/**
* Get Query Log
*
* #return array of queries
*/
function ql()
{
$log = \DB::getQueryLog();
$pdo = \DB::connection()->getPdo();
foreach ($log as &$l)
{
$bindings = $l['bindings'];
if (!empty($bindings))
{
foreach ($bindings as $key => $binding)
{
// This regex matches placeholders only, not the question marks,
// nested in quotes, while we iterate through the bindings
// and substitute placeholders by suitable values.
$regex = is_numeric($key)
? "/\?(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/"
: "/:{$key}(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/";
$l['query'] = preg_replace($regex, $pdo->quote($binding), $l['query'], 1);
}
}
}
return $log;
}
}
if (! function_exists('qldd'))
{
/**
* Get Query Log then Dump and Die
*
* #return array of queries
*/
function qldd()
{
dd(ql());
}
}
if (! function_exists('qld'))
{
/**
* Get Query Log then Dump
*
* #return array of queries
*/
function qld()
{
dump(ql());
}
}
Simply place these three functions within your helpers.php file and you can use same as follows:
$items = OrderItem::where('name', '=', 'test')->get();
qldd(); //for dump and die
or you can use
qld(); // for dump only
Here I extended the answer of #blaz
In app\Providers\AppServiceProvider.php
Add this on boot() method
if (env('APP_DEBUG')) {
DB::listen(function($query) {
File::append(
storage_path('/logs/query.log'),
self::queryLog($query->sql, $query->bindings) . "\n\n"
);
});
}
and also added a private method
private function queryLog($sql, $binds)
{
$result = "";
$sql_chunks = explode('?', $sql);
foreach ($sql_chunks as $key => $sql_chunk) {
if (isset($binds[$key])) {
$result .= $sql_chunk . '"' . $binds[$key] . '"';
}
}
$result .= $sql_chunks[count($sql_chunks) -1];
return $result;
}
Yeah, you're right :/
This is a highly requested feature, and i have no idea why its not a part of the framework yet...
This is not the most elegant solution, but you can do something like this:
function getPureSql($sql, $binds) {
$result = "";
$sql_chunks = explode('?', $sql);
foreach ($sql_chunks as $key => $sql_chunk) {
if (isset($binds[$key])) {
$result .= $sql_chunk . '"' . $binds[$key] . '"';
}
}
return $result;
}
$query = OrderItem::where('name', '=', 'test');
$pure_sql_query = getPureSql($query->toSql(), $query->getBindings());
// Or like this:
$data = OrderItem::where('name', '=', 'test')->get();
$log = DB::getQueryLog();
$log = end($log);
$pure_sql_query = getPureSql($log['query'], $log['bindings']);
You can do that with:
OrderItem::where('name', '=', 'test')->toSql();
I have a configuration table in a database, using CI3.
Each row has a unique ID, as well as a type_name and type_desc.
So as each row has a unique ID, how can I update the entire table using one update_batch query?
I obviously don't want to run a query on each row, so I'm guessing i need to build an array with the values, but how can do this with the where clause somehow within the array?! Confused.
I basically need to do this:
UPDATE check_types
SET type_name = POSTED_TYPE_NAME, type_desc = POSTED_TYPE_DESC
WHERE check_type_id = ???? POSTED_ID?????
The only way I know of to do this is to extend the db driver.
In the core folder create file with name MY_DB_mysqli_driver.php. file name assumes that the subclass_prefix defined in config.php is MY_
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_DB_mysqli_driver extends CI_DB_mysqli_driver
{
final public function __construct($params)
{
parent::__construct($params);
}
/**
* Insert_On_Duplicate_Key_Update_Batch
*
* Compiles batch insert strings and runs the queries
*
* #param string $table Table to insert into
* #param array $set An associative array of insert values
* #param bool $escape Whether to escape values and identifiers
* #return int Number of rows inserted or FALSE on failure
*/
public function insert_on_duplicate_update_batch($table = '', $set = NULL, $escape = NULL)
{
if ($set !== NULL)
{
$this->set_insert_batch($set, '', $escape);
}
if (count($this->qb_set) === 0)
{
// No valid data array. Folds in cases where keys and values did not match up
return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE;
}
if ($table === '')
{
if (!isset($this->qb_from[0]))
{
return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE;
}
$table = $this->qb_from[0];
}
// Batch this baby
$affected_rows = 0;
for ($i = 0, $total = count($this->qb_set); $i < $total; $i += 100)
{
$this->query($this->_insert_on_duplicate_key_update_batch($this->protect_identifiers($table, TRUE, $escape, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, 100)));
$affected_rows += $this->affected_rows();
}
$this->_reset_write();
return $affected_rows;
}
/**
* Insert on duplicate key update batch statement
*
* Generates a platform-specific insert string from the supplied data
*
* #param string $table Table name
* #param array $keys INSERT keys
* #param array $values INSERT values
* #return string
*/
private function _insert_on_duplicate_key_update_batch($table, $keys, $values)
{
foreach ($keys as $num => $key)
{
$update_fields[] = $key . '= VALUES(' . $key . ')';
}
return "INSERT INTO " . $table . " (" . implode(', ', $keys) . ") VALUES " . implode(', ', $values) . " ON DUPLICATE KEY UPDATE " . implode(', ', $update_fields);
}
}
You would use it like this:
$sql_array = array();
foreach ($data as $row)
{
$arr = array(
'check_type_id' => $row['check_type_id'],
'type_name' => $row['type_name'],
'type_desc' => $row['type_desc']
);
$sql_array[] = $arr;
}
$this->db->insert_on_duplicate_update_batch('check_types', $sql_array);
One day I'm on this problem and I cant find the solution.
My goal is to return a list of links from an ID (the father). So I want all his childs.
But in the view, I only have on result (the 1st one...).
I have in my controller :
$data['list_link'] = $this->menuManager->list_link();
In my Model :
function list_link($fatherid=0){
$r = array();
$sSQL = 'SELECT * FROM categories WHERE fatherid = ' . $fatherid . ' AND siteid = ' . MY_SITEID_DEFAULT . ' ORDER BY name';
$query = $this->db->query($sSQL);
// stock results in array
$r[] = $query->result_array();
foreach ($query->result() as $row) {
// let's find the childs
$this->list_link($row->id,$loop);
}
return $r;
}
If I "for each" the $r here, all looks good.
So, $data['list_link'] shoud now have all the rows.
In my view :
foreach ($list_link as $link){
foreach ($link as $row){
echo $row['name'];
}
}
But, I only have the first links (first childs), not the other one. Any help would be greatly appreciated as I'm on that problem for days...
You're not storing any values in the recursive calls (though I'm still not sure you'd get what you expect). You'd need to populate $r with each function call:
$r[] = $this->list_link($row->id,$loop);
However, either I missed something or you're overcomplicating things, but I think you could simply return the result array and use it:
function list_link($fatherid=0,$loop=0){
$sSQL = 'SELECT * FROM categories WHERE fatherid = ' . $fatherid . ' AND siteid = ' . MY_SITEID_DEFAULT . ' ORDER BY name';
$query = $this->db->query($sSQL);
return $query->result_array();
}
UPDATE
Your latest version still doesn't collect the data from recursive calls, here is the full function, see if it works:
function list_link($fatherid=0){
$r = array();
$sSQL = 'SELECT * FROM categories WHERE fatherid = ' . $fatherid . ' AND siteid = ' . MY_SITEID_DEFAULT . ' ORDER BY name';
$query = $this->db->query($sSQL);
$result = $query->result_array();
// stock results in array
$r[$fatherid] = $result;
foreach ($result as $row) {
// let's find the children
$r[$fatherid][$row['id']] = $this->list_link($row['id']);
}
return $r;
}
Note that I've added $r[$fatherid][$row['id']] so the end result should be an array with a branching structure. If you don't want that, just do $r[] instead.
I´m having trouble using selects, I tried reading the documentation but it's not too clear, and the forums that talk about it are few and inactive.
I want to do a simple select, so I tried:
$applications = $NOTORM->user_types()
->select('id, group_title')
->where('id', 1);
return $applications;
That however gives me back a NotORM object where I don't see the resulting rows that I get when I do a normal: SELECT id, group_title FROM user_types WHERE id = 1
I tried using the fetch() but not really sure how to use it. Any insights?
Actually, recommended by the author of NotORM is to use:
<?php
array_map('iterator_to_array', iterator_to_array($result));
?>
Good luck!
I had the same problem till I realized that you have to loop over result.
Try
foreach($applications as $application) {
echo $application["id"] . ": " . $application["group_title"]
}
Alternatively as you mentioned you can use fetch() which will fetch you one row at a time.
$row=$applications->fetch();
echo $row["id"];
EDIT:
To get all the row data as plain associative array instead of NotORM Object I have come across 2 techniques:
foreach($row as $key => $value) {
$data[$key]=$value;
}
$data=iterator_to_array($row); - I haven't fount a NotOrm function that does this but I found that Notorm uses this technique internally(somewhere).
To actually get only the data array of the row, you have to access NotORM_Row->row param, but it is 'protected' by default. So to use it like this:
$row = $NOTORM->user_types()
->select('id, group_title')
->where('id', 1)
->fetch()->row; //here is the magic :)
You first need to 'hack' core NotORM_Row class in 'NotORM/Row.php',
by replacing
protected $row, $result;
to
public $row, $result;
Note: You have to call fetch() on NotORM results, because it will return the NotORM_Row object where the row data is placed.
Just add this code somewhere inside the NotORM_Result class:
function result() { return (Object)$this->result_array(); }
function result_array() {
foreach($this as $row) { $ret[] = iterator_to_array($row); }
return $ret;
}
And use it like:
$applications = $NOTORM->user_types()
->select('id, group_title')
->where('id', 1);
return $applications->result(); //To return it as an Plain Object
//or
return $applications->result_array(); //To return it as a Assoc Array
Try to define this PHP function:
function getArray($obj){
$arr = array();
foreach ($obj as $objSingle) {
$arrRow = array();
foreach ($objSingle as $key => $value) {
$arrRow[$key] = $value;
}
$arr[] = $arrRow;
}
return $arr;
}
And use it by calling:
$arr = getArray($applications);
NotOrm added a function that return raw row data than names jsonSerialize. You can get row data array by this function.
Example:
$row=$db->MyTable->where('X','93054660084')->fetch();
var_dump($row->jsonSerialize());
Output:
array (size=5)
'X' => string '93054660084' (length=9)
'Idc' => string '1132' (length=4)
'IdH' => string '1' (length=1)
'Mab' => string '0' (length=1)
'Tar' => string 'xsderf' (length=10)
For multi record data you need to use foreach and apply it to all records.
It can be done like this.
function comboBuilder2($tbl, $name, $lable, $value, $value2, $value3, $selected = NULL, $cond, $sort = NULL){
global $db;
$html = '';
$sort1 = (!empty($sort)) ? "order by sort asc" : '';
$sql = "select * from " . $tbl . " " . $cond . " " . $sort1 . "";
//echo $sql;
$sth = $db->query($sql);
$rs = $sth->fetchAll();
//print_r($rs);
if ($rs[0] > 0) {
foreach ($rs as $row) {
if ($selected == $row[$value])
$sel = 'selected = "selected" ';
else
$sel = '';
echo $row[$lable];
//$html .= '<option value="' . $row[$value] . '" data-min="' . $row[$value2] . '" data-max="' . $row[$value3] . '" ' . $sel . '>' . $row[$lable] . '</option>';
}
$html .= '';
}
return $html;
}