How can I minimize the amount of calls to the database? - php

I have lots of images into mysql database, which are painted so: <img src="/PrintImage.php?$id&width&height&quality" />.
The problem is that for each image, I am doing a call to the database through a class. To explain it better, here is the code:
edited: posted entirely class code
HTML View (example):
<img src="/PrintImage.php?id=10&width=120&height=120&quality=100" />
<img src="/PrintImage.php?id=20&width=120&height=120&quality=100" />
PrintImage.php
<?php
include_once($_SERVER['DOCUMENT_ROOT'].'/php/classes/Galerias.php');
$a = new \Galerias();
$i = $_GET['i'];
$w = $_GET['w'];
$h = $_GET['h'];
$q = $_GET['q'];
$a->Pintar($i, $w, $h, $q);
unset($a);
Gallery.php:
<?php
namespace Galerias;
include_once 'Database.php';
include_once 'Utils.php';
use \Database;
use \Utils;
class Galerias {
private $img_max_width = 1024;
private $img_max_height = 768;
private $img_quality = 85;
function __construct(){
$this->Database = new Database();
$this->Utils = new Utils();
}
public function Pintar($id, $width, $height, $quality){
$query = "select (select titulo from imagenes where id=$id) as titulo, (select imagen from imagenes where id=$id) as imagen";
$data = $this->Database->GetData($query);
$titulo = $data[0]['titulo'];
$tmp = $data[0]['imagen'];
$dataimg = $this->Utils->formatImage("string", $tmp, $width, $height, false);
$mime = $dataimg[1];
header("Content-type: $mime");
header("Content-Disposition: inline; filename=$titulo");
imagejpeg($dataimg[0], null, $quality);
}
}
Database.php:
<?php
namespace Database;
include_once 'Utils.php';
use \Utils;
class Database {
private $host = "localhost";
private $user = "user";
private $pass = "pass";
private $daba = "database";
private $link;
public $Utils;
function __construct(){
$this->Open();
$this->Utils = new Utils();
}
function __destruct(){
error_log('Database destruct');
}
private function Open(){
$this->link = mysql_connect($this->host, $this->user, $this->pass);
error_log('open succeeded');
mysql_select_db($this->daba, $this->link) or $this->Utils->newEx("Class Database->Open(): ".mysql_error());
}
private function Close(){
mysql_close($this->link) or $this->Utils->newEx("Class Database->Close(): ".mysql_error());
error_log('close succeeded');
}
public function GetData($query){
$data = array();
$query = mysql_query($query) or $this->Utils->newEx("Class Database->GetData(): ".mysql_error(), true);
while( $result = mysql_fetch_array($query) ){
$data[] = $result;
}
$this->FreeResults($query);
return $data;
}
public function InsertData($query){
if( !mysql_query($query) ){
$this->Utils->newEx("Class Database->InsertData(): ". mysql_error(), true);
return false;
}
return true;
}
public function DeleteData($query){
if( !mysql_query($query) ){
$this->Utils->newEx("Class Database->DeleteData(): ". mysql_error(), true);
return false;
}
return true;
}
public function FreeResults($res = null){
if( !is_null($res) ) mysql_free_result($res);
}
}
I am calling database too many times, because I have to load ~50/100 images per gallery.
The question is, how can I do this task efficiently? Thanks in advance

Your real question is: How can i minimize the amount of calls to the database.
The data is stored in the database so will have to do a call to retrieve the data. However what do you need to ask yourself, do i really need to ask the question every time?
In your current object model, you retrieve the unique data for every picture only when the object is created. This will add one call every time you want to read the info. This is a very common mistake to do and is something that (as you already realized) doesn't scale at all.
What you need is to make the object already have the data before the call. There is numerous ways to do this. Object Factories, Cached object, memcache, redis the list can be as long as there is active developers out there.
I'm inviting you to try to think outside the box and find a solution. Because if you understand the problem and solve it you will get a better grasp of object models and the pitfalls.
Lets take it again, are you sure you need to make the database call in the construction of the object? If you know the subset of the data you need, you should ask for in bulk. That will remove a lot of queries to the database.
Perhaps the Galerias could have function where you grab a lot of Pintar in one go?
I know this isn't a "do this" answer and probably will get down voted. But at least try :).
Also: Never use the query parameters $_GET $_POST directly without sanitize them first!!

Finally, I got the logical system to reduce -considerably- the amount of calls to the database. Before, I used a PHP file as image src to print images (with its headers, etc) directly from the database. This is a terrible fail. The problems to solve were:
I need to use a PHP file as image src, passing an ID, width, height and quality. And, of course, it has to be a friendly uri. So I can't use base64 encoding to print images. It has to be a PHP file (other file, other process, it isn't connected to the first one).
I use a shared hosting, the thinking of loading -nice- extensions like memcache is not viable.
Problem 2 tells me that I can't save images (or other data) in any place to use along the site. (It's wrong).
What I did (after thinking and searching and thinking...) is to use $_SESSION to store, previous serializing, all images. So, I solved all the problems. Now, have to look for a valid logic to populate the code. The result looks like this:
// Session Cache Class (static)
namespace SessionCache;
ini_set('memory_limit', '256M'); // this is the best issue of this system
session_start();
class SessionCache {
// todo: add the possible to expire the session
private static $SessionName = 'SessionCache';
public static function Check($name){
if( empty($_SESSION[self::$SessionName]) ) return false;
if( empty($_SESSION[self::$SessionName][$name]) ) return false;
else return true;
}
public static function Set($name, $data){
if( self::Check($name) ) return;
$data = serialize($data);
$_SESSION[self::$SessionName][$name] = $data;
}
public static function Get($name){
if( self::Check($name) ){
$data = unserialize($_SESSION[self::$SessionName][$name]);
return $data;
}
else return null;
}
public static function Flush($name){
if( self::Check($name) ) unset($_SESSION[self::$SessionName][$name]);
}
public static function Destroy(){
session_destroy();
}
}
Now, the (current) logic:
// Images Class. Here I use some extra stuff. Feel free to read the comments
namespace AdminPanel;
include_once 'SessionCache.php';
include_once 'Database.php';
include_once 'Utils.php';
use \AdminPanel\Database;
use \AdminPanel\Utils;
use SessionCache\SessionCache;
use stdClass;
class Patrocinios {
private $cache_name = 'Patrocinadores';
private $img_width = 225;
private $img_height = 70;
private $img_quality = 100;
public function __construct(){
$this->Database = new \AdminPanel\Database();
$this->Utils = new \AdminPanel\Utils();
}
private function CreateImageCache(){
if( SessionCache::Check($this->cache_name) ) return null;
$query = "select * from patrocinadores";
$this->Database->Open();
$data = $this->Database->GetData($query);
$this->Database->Close();
$patros = new stdClass();
if( count($data) > 0 ){
for( $i=0; $i<count($data); $i++ ){
$id = $data[$i]['id'];
$nombre = $data[$i]['nombre'];
$uri = $data[$i]['web'];
$mimetype = $data[$i]['tipo'];
$imagedata = $data[$i]['imagen'];
$patros->patro[$id] = new stdClass();
$patros->patro[$id]->id = $id;
$patros->patro[$id]->nombre = $nombre;
$patros->patro[$id]->uri = $uri;
$patros->patro[$id]->mimetype = $mimetype;
$patros->patro[$id]->data = $imagedata; // the image BLOB
}
}
SessionCache::Set($this->cache_name, $patros);
}
public function GetPatrocinadores(){ // this method is the only one called from the main view
$this->CreateImageCache();
$patros = SessionCache::Get($this->cache_name);
return $patros;
}
public function Pintar($id, $width, $height, $quality){ // this method is called from the PHP file used to print the images
if( !SessionCache::Check($this->cache_name) ) $patros = $this->GetPatrocinadores();
else $patros = SessionCache::Get($this->cache_name);
$dataimg = $this->Utils->formatImage("string", $patros->patro[$id]->data, $width, $height); // creates new image with desired measures and quality
header('Content-Type: '.$patros->patro[$id]->mimetype);
header('Content-Length: '.strlen($patros->patro[$id]->data));
imagejpeg($dataimg[0], null, $quality);
}
}
I just have a variable with the name of the session object ($cache_name). First I check if exists (from previous call). If not, I populate a stdClass() object with the information from the database and store it in the session.
<ul id="ulPatrocinadores">
<?php
include_once $_SERVER['DOCUMENT_ROOT'].'/php/classes/Patrocinios.php';
$p = new \AdminPanel\Patrocinios();
$patros = $p->GetPatrocinadores();
$str = '';
foreach( $patros as $value ){
foreach( $value as $patro ){
$id = $patro->id;
$nombre = str_replace(" ", "_", $patro->nombre);
$web = $patro->uri;
$str .= '<li>';
$str .= '<img src="/'.$nombre.'_pat-i='.$id.'&w=225&h=70&q=85" alt="'.$nombre.'" />';
}
}
echo $str;
?>
</ul>
Above is the main view which images are printed. Note that anchors and image sources goes to the PHP file that calls to the method Pintar(). I use a RewriteRule to redirect it.
<?php
include_once '../scripts/PrintPartner.php';
$a = new \AdminPanel\Patrocinios();
$i = $_GET['i'];
$w = $_GET['w'];
$h = $_GET['h'];
$q = $_GET['q'];
$a->Pintar($i, $w, $h, $q);
unset($a);
And so, I finally achieve it. I don't know if this is a good system because $_SESSION is relational to php memory_limit, but after two days of thinking about it, I couldn't get something better.
What I achieve:
Before: one query for each image to the database. Now: one query for all images for one session (or the time that I could need).
Keep of the friendly uris with creating of the image file on the fly as needed, still using a PHP file as image src.
A good system to reduce calls to the database, also using a shared hosting.
Hope this experience helps someone.

Related

PHP: Script failing after PDO query

During my create user process I make a few queries to various database's to get the new user setup. This script has been working fine for about a year and a half, but now something is off.
So the first thing I do is I check to see if a user exists with the credentials being submitted. I've thoroughly tested the check and I'm confident my issue isn't there.
If that check comes back false then the script continues to create the user.
public function registerUser() {
parse_str($_SERVER['QUERY_STRING'], $data);
$data = (object) $data;
$check = json_decode($this->checkUserExists($data->email));
if ($check->res) {
$res = new \stdClass();
$res->res = false;
$res->user_status = $check->user_status;
$res->msg = 'User exists.';
echo json_encode($res);
}
if (!$check->res) {
$this->createUser($data);
}
}
The problem arises after all the queries have been completed, the script does not seem to want to run the if statement at the bottom. I marked it with comment characters so it's easier to find, but I included the entire function for clarity, maybe I'm doing something that is causing the issue.
I tried invoking an error manually at various points during the script. And I am able to trigger an error all the way down to the bottom of the script.
private function createUser($data) {
$Crypt = new CryptController();
$AuthSelect = new AuthController();
$Time = new TimeController();
$remote_address = new RemoteAddressController();
$Session = new SessionController();
$AuthInsert = new AuthModel_Insert();
$hashed_password = $Crypt->create_hash($data->password);
$data->password = '';
$AuthData = json_decode($AuthSelect->getAuth());
$system_auth_id = $AuthData->system_auth_id;
$user_id = $Crypt->get_uuid();
$user_auth_id = $Crypt->get_uuid();
$user_createddate = $Time->time();
$user_updateddate = $Time->time();
$user_lastupdateddate = $Time->time();
$agent_ip = $remote_address->getIpAddress();
$userData = $this->createUserObject(
$user_id,
$user_auth_id,
$system_auth_id,
$hashed_password,
$user_createddate,
$user_updateddate,
$user_lastupdateddate,
$data
);
$agentData = $this->createAgentObject(
$user_id,
$agent_ip,
$data
);
//////////////////////////////////////////
$create_user = $AuthInsert->createNewUser(
$userData
);
$create_user_agent = $this->setUserAgent(
$agentData
);
$sessionKeyData = new \stdClass();
$sessionKeyData->user_id = $user_id;
$sessionKeyData->user_auth_id = $user_auth_id;
$sessionKeyData->system_auth_id = $system_auth_id;
$sessionKeyData->agent_id = $create_user_agent->agent->agent_id;
$set_session_key = $Session->setSessionKey(
$sessionKeyData
);
$send_activation_email = $this->createUserActivation(
$userData
);
if (
$create_user &&
$create_user_agent->res &&
$set_session_key->res &&
$send_activation_email->res) {
$res = new \stdClass();
$res->res = true;
$res->msg = 'New user successfully created.';
echo json_encode($res);
} else {
$res = new \stdClass();
$res->res = false;
$res->msg = 'Error: User creation process incomplete.';
echo json_encode($res);
}
//////////////////////////////////////////
trigger_error("Invoked Error: ",E_USER_ERROR);
}
The queries themselves go through just fine, all the tables are populated just fine. The issue is that after that happens the script doesn't finish. It seems to end the createUser() function and return to the registerUser() function at which point the user will exist so it will return false and echo that back to the client.
In my testing it seems my issue might be at the bottom with that if statement. But I've tested each of those queries individually and they do return the desired booleans to get the true condition. But, even the false condition doesn't go through which should return 'Error: User creation process incomplete.'. That doesn't happen either.
I'm hoping someone sees something I'm missing because I've been stuck on this problem for too long. I appreciate any guidance that might lead me to an answer. Thanks in advance.
Just for clarification the message I'm getting back is $res->msg = 'User exists.'; which comes from registeruser(). The message I'm expecting back is $res->msg = 'New user successfully created.'; which should come from createUser().

How could I check if there is code outside a class in php?

Let's say that you have a class in php with functions and all that. How could you check if there is no code outside the class?
I tried to code this checker with PHP and did it with regex and tokens but nothing worked for me :/
An exmple
<?php
class example {
var $name;
var $password;
function __construct($name, $password) {
$this->name = $name;
$this->password = $password;
}
----Allowed code----
}
----Not allowed code----
?>
EDIT: (SOLVED)
Thanks #user3163495 for all the information
Here what I did:
1ยบ I tried to get the class name inside the file with this two functions:
function getClass($tokens) {
$clases = array();
for($i = 0; $i < count($tokens); $i++) {
//Tipo del token actual
$tokenName = getTokenName($tokens, $i);
if($tokenName === "T_CLASS") {
//Searchs the name that it has in the file.
return getClassName($tokens, $i);
}
}
return "";
}
function getClassName($tokens, $i) {
$index = $i + 1;
//Line in which is the class inside the file
$lineaClase = getTokenLine($tokens, $i);
//Line to be updated while searching
$lineaTemp = getTokenLine($tokens, $index);
//Type of token to be updated while searching
$tokenName = getTokenName($tokens, $index);
//Searchs in the parsed array for the first class token
while($index < count($tokens) &&
$lineaClase === $lineaTemp &&
($tokenName === "UNKOWN" || $tokenName === "T_WHITESPACE")) {
$index++;
$tokenName = getTokenName($tokens, $index);
$lineaTemp = getTokenLine($tokens, $index);
}
//Returns the name of the class
return getTokenContent($tokens, $index);
}
Then, I injected PHP code in the end of the file that I tried to check if it's only a class. Also I saved this new content in a new file called temp.php and finally I shell-executed this to get the echo of the injected code, that will correspond to beginning_of_class:end_of_class. Here is where I used what #user3163495 told me, thank you again.
function codeInjection($clase, $contenido) {
//PHP code to inject, thanks to #user3163495
$codigoPHP = "<?php \$class = new ReflectionClass(\"{$clase}\"); \$comienzo = \$class->getStartLine(); \$fin = \$class->getEndLine(); echo \$comienzo . \":\" . \$fin; ?>";
$contenido .= $codigoPHP;
//Creating temp file
file_put_contents("temp.php", $contenido);
//Returning result of execution
return shell_exec("php temp.php");
}
Further, I removed from the token parsed array those tokens which line where between the beginning and the end of the class. Last I go through the array searching for something that is different than a comment, white space, etc..
(Variables are in spanish, if you don't understand the meaning of some feel free to ask)
If you are wanting to "scan" the questionable file from another script to see if there is any code outside the class, then you could use the ReflectionClass in PHP.
Step 1: get the file name that your class is defined in
$class = new ReflectionClass("example");
$fileName = $class->getFileName();
Step 2: get the starting and ending lines of code that the class definition occupies in the file
$startLine = $class->getStartLine();
$endLine = $class->getEndLine();
$numLines = $endLine - $startLine;
Step 3: use file_get_contents() on the file name you obtained in Step 1, and see if there is any forbidden code before the start line or after the end line. You'll have to test and play around with what you get as I don't know exactly where getStartLine() and getEndLine() consider "start" and "end", respectively.
I hope you get the idea.
Some code lifted from this answer: https://stackoverflow.com/a/7909101/3163495

create new worksheet PHPExcel

I'm trying to create another worksheet and everything works fine. But what I need now is to create 1 depending on a variable. For instance :
I have two options one for validation and one for results.
Everything is conditioned by a boolean variable called $resultado.
I have my component in CakePHP
function ExcelCargaMasivaComponent() {
$this->xls = new PHPExcel();
$this->sheet = $this->xls->getActiveSheet();
$this->sheet->setTitle("Worksheet");
$this->sheet->getDefaultStyle()->getFont()->setName('Verdana');
$this->xls->createSheet();
$this->xls->setActiveSheetIndex(1);
$this->validations = $this->xls->getActiveSheet();
$this->validations->setTitle('Validations');
}
Where this-> validations is the second worksheet. Now, I need this worksheet has a different name, and therefore I want other data encapsulated in a function. So my function generate wanted condition this way:
function ExcelCargaMasivaComponent() {
$this->xls = new PHPExcel();
$this->sheet = $this->xls->getActiveSheet();
$this->sheet->setTitle("Worksheet");
$this->sheet->getDefaultStyle()->getFont()->setName('Verdana');
$this->xls->createSheet();
$this->xls->setActiveSheetIndex(1);
}
function generate($title = 'Report', $headers = array(), $data = array(), $uid = false, $resultados = false){
if($resultados){
$this->validations = $this->xls->getActiveSheet();
$this->validations->setTitle('Resultados');
}else{
$this->validations = $this->xls->getActiveSheet();
$this->validations->setTitle('Validations');
}
}
I do this so that the second sheet has a different name and different data depending on the variable, but I could not get it to work. I only generates 1 sheet with the title depending on the variable, it's not what I want.
Create new worksheet PHPExcel
Hi I really don't know if my answer may really do the magic to your question. However it seems fascinating to me.
Answer:
Just try doing the following to your generate method as I have provided the following code snippet:
function ExcelCargaMasivaComponent() {
$this->xls = new PHPExcel();
$this->sheet = $this->xls->getActiveSheet();
$this->sheet->setTitle("Worksheet");
$this->sheet->getDefaultStyle()->getFont()->setName('Verdana');
// $this->xls->createSheet(); // comment out this lines as we keep
// $this->xls->setActiveSheetIndex(1); // them in our generate method
}
function generate($title = 'Report', $headers = array(), $data = array(), $uid = false, $resultados = false) {
if ($resultados) {
$this->xls->createSheet(0);
$this->xls->setActiveSheetIndex(0); // This is the first required line
$this->validations = $this->xls->getActiveSheet();
$this->validations->setTitle('Resultados');
} else {
$this->xls->createSheet(1);
$this->xls->setActiveSheetIndex(1); // This is the second required line
$this->validations = $this->xls->getActiveSheet();
$this->validations->setTitle('Validations');
}
}
For your further reference please see the following SO Q&A Thread too.

PHP Session Not Saving Upon Refresh

So, I have a PHP class that has a method which updates a session variable called $_SESSION['location']. But the problem is, each time the method is called, it doesn't find the saved session variable, and tells me it isn't set. It's supposed to store a location ID, and the method pulls the next location from a MySQL database based on the session variable, then storing the new ID. But the place in the SQL code, that's supposed to include the variable, is empty.
I do have session_start() at the beginning of the page. I've tried manually setting the variable, and it doesn't do anything either. Also tried to reach that variable from another PHP page, and no luck either. Please help.
Small sample of my code:
class location {
#session_start();
function compass($dir) {
$select = $_SESSION['location'];
if($dir == "north") {
$currentlat = mysql_result(mysql_query("SELECT `lat` FROM `locationdb` WHERE id=".$select), 0, "lat");
$currentlon = mysql_result(mysql_query("SELECT `lon` FROM `locationdb` WHERE id=".$select), 0, "lon");
$sql = "[THE SQL CODE THAT GETS THE NEXT LOCATION]";
$id = mysql_result(mysql_query($sql), 0, "id");
$_SESSION['location'] = $id;
$return['loc'] = $this->display_location($id);
$return['lat'] = $this->display_lat($id);
$return['long'] = $this->display_long($id);
$return['id'] = $id;
}
return $return;
}
}
I have tested your code
**Dont use session_start() in this file.
For simple testing first add this inside your compass() function.
$_SESSION['location'] .= 'World';
Then create a php script with these codes.
<?php
session_start();
$_SESSION['location'] = 'Hello';
include_once('*your name of class file*');
$obj = new location();
$obj -> compass('north');
echo $_SESSION['location'];
?>
Run this script
If the output is "HelloWorld" then your $_SESSION['location'] is working.
Check your phpinfo(), to see if the session save path is defined. If not, define a directory to store the sessions. In your code:
session_save_path('/DIRECTORY IN YOUR SERVER');
Then try again.
This is closer to what your method should look like. There are some settings that will help reduce errors being thrown when running the function. With this function, and other suggestions, you should be able to remove the error your are getting.
class location
{
public function compass($dir = '')
{
// Set the $select by $_SESSION or by your function
$select = (isset($_SESSION['location']))? $_SESSION['location']: $this->myFunctionToSetDefault();
// I set $dir to empty so not to throw error
if($dir == "north") {
$currentlat = mysql_result(mysql_query("SELECT `lat` FROM `locationdb` WHERE id=".$select), 0, "lat");
$currentlon = mysql_result(mysql_query("SELECT `lon` FROM `locationdb` WHERE id=".$select), 0, "lon");
$sql = "[THE SQL CODE THAT GETS THE NEXT LOCATION]";
$id = mysql_result(mysql_query($sql), 0, "id");
$_SESSION['location'] = $id;
$return['loc'] = $this->display_location($id);
$return['lat'] = $this->display_lat($id);
$return['long'] = $this->display_long($id);
$return['id'] = $id;
}
// This will return empty otherwise may throw error if $return is not set
return (isset($return))? $return:'';
}
}

Multiple save on upload plus save on database errors

I have set of questions have you ever encounter some issues when multiple save on database?my problem is this, i have a database called "album" where fields are 'album_id,album_title,album_user'.
The html output would be there is a login section where there is a input type file where you want to add more and once you have uploaded it. and array of set of names will be stored and that array will be our que to save the file on the multiple format the problem is that. it says and error on the database whichs is sql specified twice.Do you have an idea on how to save in multiple using php?
code will be like this.
<?php
class Album extends olib{
function __construct(){
parent::olib();
}
function upload_submit() {
$allow = array("jpg","png","gif");
$directory = "upload";
$pictures = array();
$counter = 0;
$error = '';
if($this->jpost('upload')) {
for($getupload = 0;$getupload<count($_FILES['uploadpictures']['name']);$getupload++){
$extension = end(explode(".",$_FILES['uploadpictures']['name'][$getupload]));
if(in_array(strtolower($extension),$allow)){
if(move_uploaded_file($_FILES['uploadpictures']['tmp_name'][$getupload],$directory."/".$_FILES['uploadpictures']['name'][$getupload])){
$pictures[$getupload] = $_FILES['uploadpictures']['name'][$getupload];
$counter++;
// $this->save_user_album($_FILES['uploadpictures']['name'][$getupload],$this->setSession('user_id'));
}else{
$error[$getupload] = "Sorry seems some of the data invalid";
}
}else{
$error = '1';
}
}
print_r($pictures);
print_r($error);
foreach($pictures as $urpics){
$this->save_user_album($urpics,$this->setSession('user_id'));
}
}
}
function save_user_album($albumtitle,$session){
$_firewall = ($this->setSession('user_id') !=="") ? $this->setSession('user_id') : "";
$this->jfields('album_pics_title',$albumtitle);
// $this->jfields('album_pics_user',$session);
return $this->jSave('album_pics');
}
}
any response will greatly appreciated!!
Hi I'm sorry seems i have a solution for this i have unset after it has been save..
unset($this->jfields); problem has been solved

Categories