Symfony - adding additional field manually - php

I wrote a query to return all data from db.. Now, I need to add one additional field. The api call should receive one additional field - "term". When term is passed, results are filtered to match that term.
My Service.
/**
* Return all schools from city
*
* #param int $limit
* #param int $offset
* #return array
*/
public function getCityPaginated($limit = 20, $offset = 0)
{
$query = $this->getCityRepository()
->createQueryBuilder('c')
->getQuery();
return $this->container->get('paginator')->paginate($query, $offset, $limit);
}
My Controller..
/**
* #Route("/cities", name="city_list")
* #throws \Doctrine\Common\Annotations\AnnotationException
*/
public function getCityPaginatedAction()
{
if(isset($this->data['offset'])){
$offset = $this->data['offset'];
}else {
$offset = 0;
}
if(isset($this->data['limit'])){
$limit = $this->data['limit'];
}else{
$limit = 20;
}
$city = $this->get('city')->getCityPaginated($limit, $offset);
return $this->success($city, ['city_data']);
}

If I understand it correctly you need to get a POST parameter?
public function getCityPaginatedAction(Request $request)
{
$term = $request->request->get('term');

Change your controller so that you read the term parameter from request and pass it to the service method:
/**
* #Route("/cities", name="city_list")
* #param Request $request
* #throws \Doctrine\Common\Annotations\AnnotationException
*/
public function getCityPaginatedAction(Request $request)
{
if(isset($this->data['offset'])){
$offset = $this->data['offset'];
}else {
$offset = 0;
}
if(isset($this->data['limit'])){
$limit = $this->data['limit'];
}else{
$limit = 20;
}
$term = $request->query->get('term');
$city = $this->get('city')->getCityPaginated($limit, $offset, $term);
return $this->success($city, ['city_data']);
}
Then, change your service to use this parameter in the query
/**
* Return all schools from city
*
* #param int $limit
* #param int $offset
* #param string $text
* #return array
*/
public function getCityPaginated($limit = 20, $offset = 0, $term = null)
{
$queryBuilder = $this->getCityRepository()
->createQueryBuilder('c');
if ($term) {
$queryBuilder->andWhere($queryBuilder->expr()->like('c.name', :term)
->setParameter('term', "%" . $term . "%");
}
$query = $queryBuilder->getQuery();
return $this->container->get('paginator')->paginate($query, $offset, $limit);
}
You may want to change the query builder and search on other fields as well.

Related

How to get unique slug to same post-title for other time too?

I have tried the codes as follows.
$post->title = $request->title;
$post->body = $request->body;
$post->slug = str_slug($post->title,'%');
The code was running great but now I have the post with same title so its throwing error as it is set to unique in db.
Is there any way I can get another slug?
If you are facing a slug colision, in this case best way to go would be adding an extra integer at the end example :
mysite.dev/my-post
mysite.dev/my-post-1
For this you could use a package to generate a slug
Of if you want to do it yourself then in the model add slugattribute
public function setSlugAttribute($value) {
if (static::whereSlug($slug = str_slug($value))->exists()) {
$slug = $this->incrementSlug($slug);
}
$this->attributes['slug'] = $slug;
}
So the setSlugAtrribute will always check if the slug for given model exists if so then it will increment the slug as I meantioned above, by simply calling the below method.
public function incrementSlug($slug) {
$original = $slug;
$count = 2;
while (static::whereSlug($slug)->exists()) {
$slug = "{$original}-" . $count++;
}
return $slug;
}
Basically we are always checking if the slug exists in the database, if yes then we use an accessor to change the slug by adding an integer at the end this way you will never end having the slug duplication issue.
Create our own function that generate unique slug from any title. Look at the code and that is pretty much clear.
Reference code from this link.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Product;
class ProductController extends Controller
{
public function store(Request $request)
{
$product = new Product;
$product->title = $request->title;
$product->slug = $this->createSlug($request->title);
$product->save();
}
public function createSlug($title, $id = 0)
{
$slug = str_slug($title);
$allSlugs = $this->getRelatedSlugs($slug, $id);
if (! $allSlugs->contains('slug', $slug)){
return $slug;
}
$i = 1;
$is_contain = true;
do {
$newSlug = $slug . '-' . $i;
if (!$allSlugs->contains('slug', $newSlug)) {
$is_contain = false;
return $newSlug;
}
$i++;
} while ($is_contain);
}
protected function getRelatedSlugs($slug, $id = 0)
{
return Product::select('slug')->where('slug', 'like', $slug.'%')
->where('id', '<>', $id)
->get();
}
}
Finally. We've created an Unique slug in Laravel.
localhost:8000/kwee-dev
localhost:8000/kwee-dev-1
I had the same problem, that's my solution:
I would check if there is the same title in the database.
If yes return rows with the same title
Increment return value by 1
If there are no posts/listing with the same title than if check will not be executed
/**
* Create a slug from title
* #param string $title
* #return string $slug
*/
protected function createSlug(string $title): string
{
$slugsFound = $this->getSlugs($title);
$counter = 0;
$counter += $slugsFound;
$slug = str_slug($title, $separator = "-", app()->getLocale());
if ($counter) {
$slug = $slug . '-' . $counter;
}
return $slug;
}
/**
* Find same listing with same title
* #param string $title
* #return int $total
*/
protected function getSlugs($title): int
{
return Listing::select()->where('title', 'like', $title)->count();
}
You can easily solve this common issue with few lines of code using one of these two packages:
Laravel Sluggable
https://github.com/spatie/laravel-sluggable
Just install the package running:
composer require spatie/laravel-sluggable
Then add the code below to your model:
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
class Post extends Model
{
use HasSlug;
public function getSlugOptions() : SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('title')
->saveSlugsTo('slug');
}
or as alternative:
Eloquent sluggable
https://github.com/cviebrock/eloquent-sluggable
You can install it typing:
composer require cviebrock/eloquent-sluggable
And then adding to your model:
use Cviebrock\EloquentSluggable\Sluggable;
class Post extends Model
{
use Sluggable;
/**
* Return the sluggable configuration array for this model.
*
* #return array
*/
public function sluggable()
{
return [
'slug' => [
'source' => 'title'
]
];
}
}
This is the one that I am using to get SEO friendly unique slug.
Model
# verify and return custom slug string
public function slugify($text)
{
$slug = strtolower($text);
$slug = str_replace(array('[\', \']'), '', $slug);
$slug = preg_replace('/\[.*\]/U', '', $slug);
$slug = preg_replace('/&(amp;)?#?[a-z0-9]+;/i', '-', $slug);
$slug = htmlentities($slug, ENT_COMPAT, 'utf-8');
$slug = preg_replace('/&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);/i', '\\1', $slug );
$slug = preg_replace(array('/[^a-z0-9]/i', '/[-]+/') , '-', $slug);
# slug repeat check
$latest = $this->whereRaw("slug REGEXP '^{$slug}(-[0-9]+)?$'")
->latest('id')
->value('slug');
if($latest){
$pieces = explode('-', $latest);
$number = intval(end($pieces));
$slug .= '-' . ($number + 1);
}
return $slug;
}
Controller
$post = new POST();
$post->title = $request->title;
$post->slug = $school->slugify($request->title);
$post->save();
return redirect()->back()->with('success', 'Successfully created new record');
Several months ago I've searched convenient solution for automation slug creating and found interesting decision with Laravel's Mutator by Taner Fejzulovski.
But, when I started to check it and debug, I've understand that code not completely done.
So, I've updated and refactored it based on my needs and understanding of checks.
I have used there recursive method for a complete check of previously created slugs in the database. Maybe it will be also useful for you and others too!
/**
* Set the proper slug attribute.
*
* #param string $value
* #return mixed
*/
public function setSlugAttribute($value)
{
if(static::whereSlug($slug = Str::slug($value))->exists())
{
if(static::whereSlug($slug)->get('id')->first()->id !== $this->id){
$slug = $this->incrementSlug($slug);
if(static::whereSlug($slug)->exists()){
return $this->setSlugAttribute($slug);
}
}
}
$this->attributes['slug'] = $slug;
}
/**
* Increment slug
*
* #param string $slug
* #return string
**/
public function incrementSlug($slug)
{
// Get the slug of the created post earlier
$max = static::whereSlug($slug)->latest('id')->value('slug');
if (is_numeric($max[-1])) {
return preg_replace_callback('/(\d+)$/', function ($matches) {
return $matches[1] + 1;
}, $max);
}
return "{$slug}-2";
}
try it
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174");
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174", 51, true);
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174", 51, false);
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174", 51, true, true, Product::class, 'sku');
// If you used Laravel Eloquent Model's SoftDeletes (https://laravel.com/docs/8.x/eloquent#soft-deleting)
// then pass true as a one more addition parameter which named **`includeTrashed`**
// after the `attribute` parameter` in `createSlug` function. otherwise pass false.
// default value of includeTrashed is also false. see below
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174", 51, true, true, Product::class, 'sku', true);
Comman Helper Class Function created for Slug which named SlugHelper class
<?php
namespace Modules\CoreProduct\Utilities;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Modules\CoreProduct\Entities\Product;
use Illuminate\Support\Str;
class SlugHelper
{
/**
* model Obj, mandatory if wants to unique slug
*
* #var Model
*/
public static $modelObj;
/**
* The attribute is a database field name for check uniqueness of slugs. mandatory if you pass $uniqueSlug as true.
* for ex: I have "sku" named DB field then I have to pass $attribute = "sku"
*
* #var string
*/
public static $attribute;
/**
* if you wants to Enforce uniqueness of slugs with Soft Deleted Rows also
*
* #var boolean
*/
public static $includeTrashed;
/**
* If you are setting a maximum length on your slugs, you may not want the
* truncated string to split a word in half.
*
* e.g. with a maxLength of 12:
* if you pass true, "my source string" -> "my-source" .
* if you pass false, "my source string" -> "my-source-st"
*
* #var bool
*/
public static $maxLengthKeepWords;
/**
* The first suffix to add to a slug to make it unique
*
* default value is 2. for adding incremental integers, we start counting
* at 2, so the list of slugs would be,
*
* e.g.:
* - my-post
* - my-post-2
* - my-post-3
*
* #var integer
*/
public static $firstSuffix = 2;
/**
* Separator to use when generating slugs. Defaults to a hyphen.
*
* #var string
*/
public static $separator = '-';
/**
* Generate a URL friendly "slug" from a given string
*
* #param string $title
* #param integer|null $maxLength // maximum length of a generated slug. pass it as a positive integer if you want to make sure your slugs aren't too long.
* #param boolean $maxLengthKeepWords // pass bool true if you may not want the truncated string to split a word in half. e.g. with a maxLength of 12: "my source string" -> "my-source" . if you pass false then output will like this "my source string" -> "my-source-st"
* #param boolean $uniqueSlug // pass bool true if you wants to Enforce uniqueness of slugs
* #param Model $modelObj // pass Model. mandatory if you pass $uniqueSlug as true.
* #param string $attribute // database field name for check uniqueness of slugs. mandatory if you pass $uniqueSlug as true. for ex: I have "sku" named DB field then I have to pass $attribute = "sku"
* #param boolean $includeTrashed // if you wants to Enforce uniqueness of slugs with Soft Deleted Rows also
* #return string
*/
public static function createSlug(string $title, ?int $maxLength = null, bool $maxLengthKeepWords = true, bool $uniqueSlug = false, $modelObj = null, string $attribute = null, bool $includeTrashed = false)
{
self::$modelObj = $modelObj;
self::$attribute = $attribute;
self::$maxLengthKeepWords = $maxLengthKeepWords;
self::$includeTrashed = $includeTrashed;
echo $slug = Str::slug($title);
$slug = self::truncateOverLimitSlug($slug, $maxLength, self::$separator);
$slug = self::makeSlugUnique($slug, self::$separator, $uniqueSlug, $maxLength);
echo "<h3>". $slug . "</h3>";
return $slug;
}
/**
* Checks if the slug should be unique, and makes it so if needed.
*
* #param string $slug
* #param string $separator
* #param boolean $uniqueSlug
* #param integer|null $maxLength
* #return string
*/
private function makeSlugUnique(string $slug, string $separator, bool $uniqueSlug, ?int $maxLength = null)
{
if(!$uniqueSlug){
// $slug = self::truncateOverLimitSlug($slug, $maxLength, $separator);
return $slug;
}
$original = $slug;
$suffix = self::$firstSuffix;
while(self::getExistingSlugs($slug, self::$includeTrashed)){
if($maxLength){
$newMaxLength = $maxLength - strlen($separator . $suffix);
}
$slug = self::truncateOverLimitSlug($original, $newMaxLength, self::$separator) . "{$separator}" . $suffix;
// $slug = "{$original}". "{$separator}" . $suffix;
$suffix++;
}
return $slug;
/* if(!$uniqueSlug){
$slug = self::truncateOverLimitSlug($slug, $maxLength, $separator);
return $slug;
}
$list = self::getExistingSlugs($slug, true);
if($list->count() === 0 || $list->contains($slug) === false){
return $slug;
}
$suffix = self::generateSuffix($slug, $separator, $list, self::$firstSuffix);
if($maxLength){
$maxLength = $maxLength - strlen($separator . $suffix);
}
$slug = self::truncateOverLimitSlug($slug, $maxLength, $separator);
return $slug . $separator . $suffix; */
}
/**
* Truncate the slug using maxLength parameter
*
* #param string $slug
* #param integer|null $maxLength
* #param string $separator
* #return string
*/
private function truncateOverLimitSlug(string $slug, ?int $maxLength = null, string $separator)
{
$len = mb_strlen($slug);
if (is_string($slug) && $maxLength && $len > $maxLength) {
$reverseOffset = $maxLength - $len;
$lastSeparatorPos = mb_strrpos($slug, $separator, $reverseOffset);
if (self::$maxLengthKeepWords && $lastSeparatorPos !== false) {
$slug = mb_substr($slug, 0, $lastSeparatorPos);
} else {
$slug = trim(mb_substr($slug, 0, $maxLength), $separator);
}
}
return $slug;
}
// /**
// * Generate a unique suffix for the given slug (and list of existing, "similar" slugs.
// *
// * #param string $slug
// * #param string $separator
// * #param Collection $list
// * #param integer $firstSuffix
// * #return mixed
// */
// private function generateSuffix(string $slug, string $separator, Collection $list, int $firstSuffix)
// {
// $len = strlen($slug . $separator);
// // If the slug already exists, but belongs to
// // our model, return the current suffix.
// if ($list->search($slug) === 'sku') {
// $suffix = explode($separator, $slug);
// return end($suffix);
// }
// $list->transform(function($value, $key) use ($len) {
// return (int) substr($value, $len);
// });
// $max = $list->max();
// // return one more than the largest value,
// // or return the first suffix the first time
// return (string) ($max === 0 ? $firstSuffix : $max + 1);
// }
/**
* Get all existing slugs that are similar to the given slug.
*
* #param string $slug
* #param boolean $includeTrashed
* #return bool
*/
private function getExistingSlugs(string $slug, bool $includeTrashed)
{
if(is_string(self::$modelObj)){
$modelObj = new self::$modelObj(); // initialize the Model Class, if user pass Product::class. because of string type.
}else{
$modelObj = self::$modelObj; // instance of Model Class, means user passed object of equivalent model class
}
$query = $modelObj;
if($includeTrashed === true){
$query = $modelObj->withTrashed();
}
$query = $modelObj->where(self::$attribute, $slug);
return $query->exists();
// var_dump(self::$modelObj);
/* $results = $modelObj->where(self::$attribute, $slug)->select(self::$attribute)->get();
return $results->pluck(self::$attribute); */
}
}
I hope this one helps to anyone.
Note: I used Laravel version 8+
it's work for me
public function generateSlug($name)
{
$slug=Str::slug($name);
// dd($slug,"show");
if (Business::where('profile_link',Str::slug($name))->exists()) {
$max = Business::where('name','LIKE',$name)->latest()->value('profile_link');
if(is_numeric($max[-1])) {
// dd($max);
return preg_replace_callback('/(\d+)$/', function($mathces) {
// dd($mathces[1] + 1);
return $mathces[1] + 1;
}, $max);
}
// dd("{$slug}-2");
return "{$slug}-2";
}
// dd($slug);
return $slug;
}

Getting random results with Doctrine

I'm new to Doctrine and had a hard time deciding how to get random rows within it without creating custom DQL functions or using a native query.
The solution I have come up with is below, this is within a custom repository for an Entity. I'm hoping for some constructive criticism in regards to it, however it currently is working.
class MyRepository extends EntityRepository
{
/**
* #param int $numberOfResults
* #return array
*/
public function getRandomResults($numberOfResults = 1)
{
$maxID = $this->getMax();
$count = 0;
$randomNumberCounter = 0;
$randomNumbers = array();
while ($randomNumberCounter < $numberOfResults) {
$randomNumberCounter++;
$randomNumbers[] = $this->getRandom(0, $maxID);
}
$qb = $this->createQueryBuilder('r');
while ($count < $numberOfResults) {
$qb
->andWhere(
$qb->expr()->orX(
// the greater than is to account for holes within the primary key
$qb->expr()->gte("r.id", "?".$count),
/* the less than is in case we have a beginning database with a large disparity
between starting and ending ID */
$qb->expr()->lte("r.id", "?".$count)
)
);
$count++;
}
$result = $qb
->setParameters($randomNumbers)
->setMaxResults($numberOfResults)
// ensure we have no duplicates
->groupBy('r.id')
->getQuery();
return $result->getResult();
}
/**
* get the maximum ID that the table has
* #return mixed
* TODO: Create a Cron job to set this every 6 hours
*/
public function getMax()
{
$maxID = $this->createQueryBuilder('r')
->select('(MAX(r.id))')
->getQuery()
->getSingleScalarResult();
return $maxID;
}
/**
* #param $min
* #param $max
* #return int
*/
public function getRandom($min, $max)
{
return mt_rand($min, $max);
}
}

includes/MysqliDb.php on line 823 error - url shortener ads 1.3_2

* #author Josh Campbell
* #author Alexander V. Butenko
* #copyright Copyright (c) 2010
* #license http://opensource.org/licenses/gpl-3.0.html GNU Public License
* #version 2.0
/
class MysqliDb
{
/
* Static instance of self
*
* #var MysqliDb
*/
protected static $_instance;
/**
* Table prefix
*
* #var string
*/
protected static $_prefix;
/**
* MySQLi instance
*
* #var mysqli
*/
protected $_mysqli;
/**
* The SQL query to be prepared and executed
*
* #var string
*/
protected $_query;
/**
* The previously executed SQL query
*
* #var string
*/
protected $_lastQuery;
/**
* An array that holds where joins
*
* #var array
*/
protected $_join = array();
/**
* An array that holds where conditions 'fieldname' => 'value'
*
* #var array
*/
protected $_where = array();
/**
* Dynamic type list for order by condition value
*/
protected $_orderBy = array();
/**
* Dynamic type list for group by condition value
*/
protected $_groupBy = array();
/**
* Dynamic array that holds a combination of where condition/table data value types and parameter referances
*
* #var array
*/
protected $_bindParams = array(''); // Create the empty 0 index
/**
* Variable which holds an amount of returned rows during get/getOne/select queries
*
* #var string
*/
public $count = 0;
/**
* Variable which holds last statement error
*
* #var string
*/
protected $_stmtError;
/**
* Database credentials
*
* #var string
*/
protected $host;
protected $username;
protected $password;
protected $db;
protected $port;
/**
* Is Subquery object
*
*/
protected $isSubQuery = false;
/**
* #param string $host
* #param string $username
* #param string $password
* #param string $db
* #param int $port
*/
public function __construct($host = NULL, $username = NULL, $password = NULL, $db = NULL, $port = NULL)
{
$this->host = $host;
$this->username = $username;
$this->password = $password;
$this->db = $db;
if($port == NULL)
$this->port = ini_get ('mysqli.default_port');
else
$this->port = $port;
if ($host == null && $username == null && $db == null) {
$this->isSubQuery = true;
return;
}
// for subqueries we do not need database connection and redefine root instance
$this->connect();
$this->setPrefix();
self::$_instance = $this;
}
/**
* A method to connect to the database
*
*/
public function connect()
{
if ($this->isSubQuery)
return;
$this->_mysqli = new mysqli ($this->host, $this->username, $this->password, $this->db, $this->port)
or die('There was a problem connecting to the database');
$this->_mysqli->set_charset ('utf8');
}
/**
* A method of returning the static instance to allow access to the
* instantiated object from within another class.
* Inheriting this class would require reloading connection info.
*
* #uses $db = MySqliDb::getInstance();
*
* #return object Returns the current instance.
*/
public static function getInstance()
{
return self::$_instance;
}
/**
* Reset states after an execution
*
* #return object Returns the current instance.
*/
protected function reset()
{
$this->_where = array();
$this->_join = array();
$this->_orderBy = array();
$this->_groupBy = array();
$this->_bindParams = array(''); // Create the empty 0 index
$this->_query = null;
$this->count = 0;
}
/**
* Method to set a prefix
*
* #param string $prefix Contains a tableprefix
*/
public function setPrefix($prefix = '')
{
self::$_prefix = $prefix;
return $this;
}
/**
* Pass in a raw query and an array containing the parameters to bind to the prepaird statement.
*
* #param string $query Contains a user-provided query.
* #param array $bindParams All variables to bind to the SQL statment.
* #param bool $sanitize If query should be filtered before execution
*
* #return array Contains the returned rows from the query.
*/
public function rawQuery ($query, $bindParams = null, $sanitize = true)
{
$this->_query = $query;
if ($sanitize)
$this->_query = filter_var ($query, FILTER_SANITIZE_STRING,
FILTER_FLAG_NO_ENCODE_QUOTES);
$stmt = $this->_prepareQuery();
if (is_array($bindParams) === true) {
$params = array(''); // Create the empty 0 index
foreach ($bindParams as $prop => $val) {
$params[0] .= $this->_determineType($val);
array_push($params, $bindParams[$prop]);
}
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($params));
}
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
return $this->_dynamicBindResults($stmt);
}
/**
*
* #param string $query Contains a user-provided select query.
* #param int $numRows The number of rows total to return.
*
* #return array Contains the returned rows from the query.
*/
public function query($query, $numRows = null)
{
$this->_query = filter_var($query, FILTER_SANITIZE_STRING);
$stmt = $this->_buildQuery($numRows);
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
return $this->_dynamicBindResults($stmt);
}
/**
* A convenient SELECT * function.
*
* #param string $tableName The name of the database table to work with.
* #param integer $numRows The number of rows total to return.
*
* #return array Contains the returned rows from the select query.
*/
public function get($tableName, $numRows = null, $columns = '*')
{
if (empty ($columns))
$columns = '*';
$column = is_array($columns) ? implode(', ', $columns) : $columns;
$this->_query = "SELECT $column FROM " .self::$_prefix . $tableName;
$stmt = $this->_buildQuery($numRows);
if ($this->isSubQuery)
return $this;
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
return $this->_dynamicBindResults($stmt);
}
/**
* A convenient SELECT * function to get one record.
*
* #param string $tableName The name of the database table to work with.
*
* #return array Contains the returned rows from the select query.
*/
public function getOne($tableName, $columns = '*')
{
$res = $this->get ($tableName, 1, $columns);
if (is_object($res))
return $res;
if (isset($res[0]))
return $res[0];
return null;
}
/**
*
* #param <string $tableName The name of the table.
* #param array $insertData Data containing information for inserting into the DB.
*
* #return boolean Boolean indicating whether the insert query was completed succesfully.
*/
public function insert($tableName, $insertData)
{
if ($this->isSubQuery)
return;
$this->_query = "INSERT into " .self::$_prefix . $tableName;
$stmt = $this->_buildQuery(null, $insertData);
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
return ($stmt->affected_rows > 0 ? $stmt->insert_id : false);
}
/**
* Update query. Be sure to first call the "where" method.
*
* #param string $tableName The name of the database table to work with.
* #param array $tableData Array of data to update the desired row.
*
* #return boolean
*/
public function update($tableName, $tableData)
{
if ($this->isSubQuery)
return;
$this->_query = "UPDATE " . self::$_prefix . $tableName ." SET ";
$stmt = $this->_buildQuery (null, $tableData);
$status = $stmt->execute();
$this->reset();
$this->_stmtError = $stmt->error;
$this->count = $stmt->affected_rows;
return $status;
}
/**
* Delete query. Call the "where" method first.
*
* #param string $tableName The name of the database table to work with.
* #param integer $numRows The number of rows to delete.
*
* #return boolean Indicates success. 0 or 1.
*/
public function delete($tableName, $numRows = null)
{
if ($this->isSubQuery)
return;
$this->_query = "DELETE FROM " . self::$_prefix . $tableName;
$stmt = $this->_buildQuery($numRows);
$stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset();
return ($stmt->affected_rows > 0);
}
/**
* This method allows you to specify multiple (method chaining optional) AND WHERE statements for SQL queries.
*
* #uses $MySqliDb->where('id', 7)->where('title', 'MyTitle');
*
* #param string $whereProp The name of the database field.
* #param mixed $whereValue The value of the database field.
*
* #return MysqliDb
*/
public function where($whereProp, $whereValue = null, $operator = null)
{
if ($operator)
$whereValue = Array ($operator => $whereValue);
$this->_where[] = Array ("AND", $whereValue, $whereProp);
return $this;
}
/**
* This method allows you to specify multiple (method chaining optional) OR WHERE statements for SQL queries.
*
* #uses $MySqliDb->orWhere('id', 7)->orWhere('title', 'MyTitle');
*
* #param string $whereProp The name of the database field.
* #param mixed $whereValue The value of the database field.
*
* #return MysqliDb
*/
public function orWhere($whereProp, $whereValue = null, $operator = null)
{
if ($operator)
$whereValue = Array ($operator => $whereValue);
$this->_where[] = Array ("OR", $whereValue, $whereProp);
return $this;
}
/**
* This method allows you to concatenate joins for the final SQL statement.
*
* #uses $MySqliDb->join('table1', 'field1 <> field2', 'LEFT')
*
* #param string $joinTable The name of the table.
* #param string $joinCondition the condition.
* #param string $joinType 'LEFT', 'INNER' etc.
*
* #return MysqliDb
*/
public function join($joinTable, $joinCondition, $joinType = '')
{
$allowedTypes = array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER');
$joinType = strtoupper (trim ($joinType));
$joinTable = filter_var($joinTable, FILTER_SANITIZE_STRING);
if ($joinType && !in_array ($joinType, $allowedTypes))
die ('Wrong JOIN type: '.$joinType);
$this->_join[$joinType . " JOIN " . self::$_prefix . $joinTable] = $joinCondition;
return $this;
}
/**
* This method allows you to specify multiple (method chaining optional) ORDER BY statements for SQL queries.
*
* #uses $MySqliDb->orderBy('id', 'desc')->orderBy('name', 'desc');
*
* #param string $orderByField The name of the database field.
* #param string $orderByDirection Order direction.
*
* #return MysqliDb
*/
public function orderBy($orderByField, $orderbyDirection = "DESC")
{
$allowedDirection = Array ("ASC", "DESC");
$orderbyDirection = strtoupper (trim ($orderbyDirection));
$orderByField = preg_replace ("/[^-a-z0-9\.\(\),_]+/i",'', $orderByField);
if (empty($orderbyDirection) || !in_array ($orderbyDirection, $allowedDirection))
die ('Wrong order direction: '.$orderbyDirection);
$this->_orderBy[$orderByField] = $orderbyDirection;
return $this;
}
/**
* This method allows you to specify multiple (method chaining optional) GROUP BY statements for SQL queries.
*
* #uses $MySqliDb->groupBy('name');
*
* #param string $groupByField The name of the database field.
*
* #return MysqliDb
*/
public function groupBy($groupByField)
{
$groupByField = preg_replace ("/[^-a-z0-9\.\(\),_]+/i",'', $groupByField);
$this->_groupBy[] = $groupByField;
return $this;
}
/**
* This methods returns the ID of the last inserted item
*
* #return integer The last inserted item ID.
*/
public function getInsertId()
{
return $this->_mysqli->insert_id;
}
/**
* Escape harmful characters which might affect a query.
*
* #param string $str The string to escape.
*
* #return string The escaped string.
*/
public function escape($str)
{
return $this->_mysqli->real_escape_string($str);
}
/**
* Method to call mysqli->ping() to keep unused connections open on
* long-running scripts, or to reconnect timed out connections (if php.ini has
* global mysqli.reconnect set to true). Can't do this directly using object
* since _mysqli is protected.
*
* #return bool True if connection is up
*/
public function ping() {
return $this->_mysqli->ping();
}
/**
* This method is needed for prepared statements. They require
* the data type of the field to be bound with "i" s", etc.
* This function takes the input, determines what type it is,
* and then updates the param_type.
*
* #param mixed $item Input to determine the type.
*
* #return string The joined parameter types.
*/
protected function _determineType($item)
{
switch (gettype($item)) {
case 'NULL':
case 'string':
return 's';
break;
case 'boolean':
case 'integer':
return 'i';
break;
case 'blob':
return 'b';
break;
case 'double':
return 'd';
break;
}
return '';
}
/**
* Helper function to add variables into bind parameters array
*
* #param string Variable value
*/
protected function _bindParam($value) {
$this->_bindParams[0] .= $this->_determineType ($value);
array_push ($this->_bindParams, $value);
}
/**
* Helper function to add variables into bind parameters array in bulk
*
* #param Array Variable with values
*/
protected function _bindParams ($values) {
foreach ($values as $value)
$this->_bindParam ($value);
}
/**
* Helper function to add variables into bind parameters array and will return
* its SQL part of the query according to operator in ' $operator ?' or
* ' $operator ($subquery) ' formats
*
* #param Array Variable with values
*/
protected function _buildPair ($operator, $value) {
if (!is_object($value)) {
$this->_bindParam ($value);
return ' ' . $operator. ' ? ';
}
$subQuery = $value->getSubQuery ();
$this->_bindParams ($subQuery['params']);
return " " . $operator . " (" . $subQuery['query'] . ")";
}
/**
* Abstraction method that will compile the WHERE statement,
* any passed update data, and the desired rows.
* It then builds the SQL query.
*
* #param int $numRows The number of rows total to return.
* #param array $tableData Should contain an array of data for updating the database.
*
* #return mysqli_stmt Returns the $stmt object.
*/
protected function _buildQuery($numRows = null, $tableData = null)
{
$this->_buildJoin();
$this->_buildTableData ($tableData);
$this->_buildWhere();
$this->_buildGroupBy();
$this->_buildOrderBy();
$this->_buildLimit ($numRows);
$this->_lastQuery = $this->replacePlaceHolders ($this->_query, $this->_bindParams);
if ($this->isSubQuery)
return;
// Prepare query
$stmt = $this->_prepareQuery();
// Bind parameters to statement if any
if (count ($this->_bindParams) > 1)
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
return $stmt;
}
/**
* This helper method takes care of prepared statements' "bind_result method
* , when the number of variables to pass is unknown.
*
* #param mysqli_stmt $stmt Equal to the prepared statement object.
*
* #return array The results of the SQL fetch.
*/
protected function _dynamicBindResults(mysqli_stmt $stmt)
{
$parameters = array();
$results = array();
$meta = $stmt->result_metadata();
// if $meta is false yet sqlstate is true, there's no sql error but the query is
// most likely an update/insert/delete which doesn't produce any results
if(!$meta && $stmt->sqlstate) {
return array();
}
$row = array();
while ($field = $meta->fetch_field()) {
$row[$field->name] = null;
$parameters[] = & $row[$field->name];
}
// avoid out of memory bug in php 5.2 and 5.3
// https://github.com/joshcam/PHP-MySQLi-Database-Class/pull/119
if (version_compare (phpversion(), '5.4', '<'))
$stmt->store_result();
call_user_func_array(array($stmt, 'bind_result'), $parameters);
while ($stmt->fetch()) {
$x = array();
foreach ($row as $key => $val) {
$x[$key] = $val;
}
$this->count++;
array_push($results, $x);
}
return $results;
}
/**
* Abstraction method that will build an JOIN part of the query
*/
protected function _buildJoin () {
if (empty ($this->_join))
return;
foreach ($this->_join as $prop => $value)
$this->_query .= " " . $prop . " on " . $value;
}
/**
* Abstraction method that will build an INSERT or UPDATE part of the query
*/
protected function _buildTableData ($tableData) {
if (!is_array ($tableData))
return;
$isInsert = strpos ($this->_query, 'INSERT');
$isUpdate = strpos ($this->_query, 'UPDATE');
if ($isInsert !== false) {
$this->_query .= '(`' . implode(array_keys($tableData), '`, `') . '`)';
$this->_query .= ' VALUES(';
}
foreach ($tableData as $column => $value) {
if ($isUpdate !== false)
$this->_query .= "`" . $column . "` = ";
// Subquery value
if (is_object ($value)) {
$this->_query .= $this->_buildPair ("", $value) . ", ";
continue;
}
// Simple value
if (!is_array ($value)) {
$this->_bindParam ($value);
$this->_query .= '?, ';
continue;
}
// Function value
$key = key ($value);
$val = $value[$key];
switch ($key) {
case '[I]':
$this->_query .= $column . $val . ", ";
break;
case '[F]':
$this->_query .= $val[0] . ", ";
if (!empty ($val[1]))
$this->_bindParams ($val[1]);
break;
case '[N]':
if ($val == null)
$this->_query .= "!" . $column . ", ";
else
$this->_query .= "!" . $val . ", ";
break;
default:
die ("Wrong operation");
}
}
$this->_query = rtrim($this->_query, ', ');
if ($isInsert !== false)
$this->_query .= ')';
}
/**
* Abstraction method that will build the part of the WHERE conditions
*/
protected function _buildWhere () {
if (empty ($this->_where))
return;
//Prepair the where portion of the query
$this->_query .= ' WHERE ';
// Remove first AND/OR concatenator
$this->_where[0][0] = '';
foreach ($this->_where as $cond) {
list ($concat, $wValue, $wKey) = $cond;
$this->_query .= " " . $concat ." " . $wKey;
// Empty value (raw where condition in wKey)
if ($wValue === null)
continue;
// Simple = comparison
if (!is_array ($wValue))
$wValue = Array ('=' => $wValue);
$key = key ($wValue);
$val = $wValue[$key];
switch (strtolower ($key)) {
case '0':
$this->_bindParams ($wValue);
break;
case 'not in':
case 'in':
$comparison = ' ' . $key . ' (';
if (is_object ($val)) {
$comparison .= $this->_buildPair ("", $val);
} else {
foreach ($val as $v) {
$comparison .= ' ?,';
$this->_bindParam ($v);
}
}
$this->_query .= rtrim($comparison, ',').' ) ';
break;
case 'not between':
case 'between':
$this->_query .= " $key ? AND ? ";
$this->_bindParams ($val);
break;
default:
$this->_query .= $this->_buildPair ($key, $val);
}
}
}
/**
* Abstraction method that will build the GROUP BY part of the WHERE statement
*
*/
protected function _buildGroupBy () {
if (empty ($this->_groupBy))
return;
$this->_query .= " GROUP BY ";
foreach ($this->_groupBy as $key => $value)
$this->_query .= $value . ", ";
$this->_query = rtrim($this->_query, ', ') . " ";
}
/**
* Abstraction method that will build the LIMIT part of the WHERE statement
*
* #param int $numRows The number of rows total to return.
*/
protected function _buildOrderBy () {
if (empty ($this->_orderBy))
return;
$this->_query .= " ORDER BY ";
foreach ($this->_orderBy as $prop => $value)
$this->_query .= $prop . " " . $value . ", ";
$this->_query = rtrim ($this->_query, ', ') . " ";
}
/**
* Abstraction method that will build the LIMIT part of the WHERE statement
*
* #param int $numRows The number of rows total to return.
*/
protected function _buildLimit ($numRows) {
if (!isset ($numRows))
return;
if (is_array ($numRows))
$this->_query .= ' LIMIT ' . (int)$numRows[0] . ', ' . (int)$numRows[1];
else
$this->_query .= ' LIMIT ' . (int)$numRows;
}
/**
* Method attempts to prepare the SQL query
* and throws an error if there was a problem.
*
* #return mysqli_stmt
*/
protected function _prepareQuery()
{
if (!$stmt = $this->_mysqli->prepare($this->_query)) {
trigger_error("Problem preparing query ($this->_query) " . $this->_mysqli->error, E_USER_ERROR);
}
return $stmt;
}
/**
* Close connection
*/
public function __destruct()
{
if (!$this->isSubQuery)
return;
if ($this->_mysqli)
$this->_mysqli->close();
}
/**
* #param array $arr
*
* #return array
*/
protected function refValues($arr)
{
//Reference is required for PHP 5.3+
if (strnatcmp(phpversion(), '5.3') >= 0) {
$refs = array();
foreach ($arr as $key => $value) {
$refs[$key] = & $arr[$key];
}
return $refs;
}
return $arr;
}
/**
* Function to replace ? with variables from bind variable
* #param string $str
* #param Array $vals
*
* #return string
*/
protected function replacePlaceHolders ($str, $vals) {
$i = 1;
$newStr = "";
while ($pos = strpos ($str, "?")) {
$val = $vals[$i++];
if (is_object ($val))
$val = '[object]';
$newStr .= substr ($str, 0, $pos) . $val;
$str = substr ($str, $pos + 1);
}
return $newStr;
}
/**
* Method returns last executed query
*
* #return string
*/
public function getLastQuery () {
return $this->_lastQuery;
}
/**
* Method returns mysql error
*
* #return string
*/
public function getLastError () {
return $this->_stmtError . " " . $this->_mysqli->error;
}
/**
* Mostly internal method to get query and its params out of subquery object
* after get() and getAll()
*
* #return array
*/
public function getSubQuery () {
if (!$this->isSubQuery)
return null;
array_shift ($this->_bindParams);
$val = Array ('query' => $this->_query,
'params' => $this->_bindParams
);
$this->reset();
return $val;
}
/* Helper functions */
/**
* Method returns generated interval function as a string
*
* #param string interval in the formats:
* "1", "-1d" or "- 1 day" -- For interval - 1 day
* Supported intervals [s]econd, [m]inute, [h]hour, [d]day, [M]onth, [Y]ear
* Default null;
* #param string Initial date
*
* #return string
*/
public function interval ($diff, $func = "NOW()") {
$types = Array ("s" => "second", "m" => "minute", "h" => "hour", "d" => "day", "M" => "month", "Y" => "year");
$incr = '+';
$items = '';
$type = 'd';
if ($diff && preg_match('/([+-]?) ?([0-9]+) ?([a-zA-Z]?)/',$diff, $matches)) {
if (!empty ($matches[1])) $incr = $matches[1];
if (!empty ($matches[2])) $items = $matches[2];
if (!empty ($matches[3])) $type = $matches[3];
if (!in_array($type, array_keys($types)))
trigger_error ("invalid interval type in '{$diff}'");
$func .= " ".$incr ." interval ". $items ." ".$types[$type] . " ";
}
return $func;
}
/**
* Method returns generated interval function as an insert/update function
*
* #param string interval in the formats:
* "1", "-1d" or "- 1 day" -- For interval - 1 day
* Supported intervals [s]econd, [m]inute, [h]hour, [d]day,
[M]onth, [Y]ear
* Default null;
* #param string Initial date
*
* #return array
*/
public function now ($diff = null, $func = "NOW()") {
return Array ("[F]" => Array($this->interval($diff, $func)));
}
/**
* Method generates incremental function call
* #param int increment amount. 1 by default
*/
public function inc($num = 1) {
return Array ("[I]" => "+" . (int)$num);
}
/**
* Method generates decrimental function call
* #param int increment amount. 1 by default
*/
public function dec ($num = 1) {
return Array ("[I]" => "-" . (int)$num);
}
/**
* Method generates change boolean function call
* #param string column name. null by default
*/
public function not ($col = null) {
return Array ("[N]" => (string)$col);
}
/**
* Method generates user defined function call
* #param string user function body
*/
public function func ($expr, $bindParams = null) {
return Array ("[F]" => Array($expr, $bindParams));
}
/**
* Method creates new mysqlidb object for a subquery generation
*/
public static function subQuery()
{
return new MysqliDb();
}
/**
* Method returns a copy of a mysqlidb subquery object
*
* #param object new mysqlidb object
*/
public function copy ()
{
return clone $this;
}
/**
* Begin a transaction
*
* #uses mysqli->autocommit(false)
* #uses register_shutdown_function(array($this, "_transaction_shutdown_check"))
*/
public function startTransaction () {
$this->_mysqli->autocommit (false);
$this->_transaction_in_progress = true;
register_shutdown_function (array ($this, "_transaction_status_check"));
}
/**
* Transaction commit
*
* #uses mysqli->commit();
* #uses mysqli->autocommit(true);
*/
public function commit () {
$this->_mysqli->commit ();
$this->_transaction_in_progress = false;
$this->_mysqli->autocommit (true);
}
/**
* Transaction rollback function
*
* #uses mysqli->rollback();
* #uses mysqli->autocommit(true);
*/
public function rollback () {
$this->_mysqli->rollback ();
$this->_transaction_in_progress = false;
$this->_mysqli->autocommit (true);
}
/**
* Shutdown handler to rollback uncommited operations in order to keep
* atomic operations sane.
*
* #uses mysqli->rollback();
*/
public function _transaction_status_check () {
if (!$this->_transaction_in_progress)
return;
$this->rollback ();
}
} // END class

Extend MySQLi Class PHP

I have downloaded the SafeMySQL class which I will post below. I would like to extend this database class to all of my other classes throughout the site that call queries. Currently, I have the main db connector set as a global variable, but I have to call it inside each class constructor and all of the class's methods. Surely there has to be an easier way?
Here is the DB class:
class SafeMySQL
{
private $conn;
private $stats;
private $emode;
private $exname;
private $defaults = array(
'host' => 'localhost',
'user' => '',
'pass' => '',
'db' => '',
'port' => NULL,
'socket' => NULL,
'pconnect' => FALSE,
'charset' => 'utf8',
'errmode' => 'error', //or exception
'exception' => 'Exception', //Exception class name
);
const RESULT_ASSOC = MYSQLI_ASSOC;
const RESULT_NUM = MYSQLI_NUM;
public function __construct($opt = array())
{
$opt = array_merge($this->defaults,$opt);
$this->emode = $opt['errmode'];
$this->exname = $opt['exception'];
if ($opt['pconnect'])
{
$opt['host'] = "p:".$opt['host'];
}
#$this->conn = mysqli_connect($opt['host'], $opt['user'], $opt['pass'], $opt['db'], $opt['port'], $opt['socket']);
if ( !$this->conn )
{
$this->error(mysqli_connect_errno()." ".mysqli_connect_error());
}
mysqli_set_charset($this->conn, $opt['charset']) or $this->error(mysqli_error($this->conn));
unset($opt); // I am paranoid
}
/**
* Conventional function to run a query with placeholders. A mysqli_query wrapper with placeholders support
*
* Examples:
* $db->query("DELETE FROM table WHERE id=?i", $id);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return resource|FALSE whatever mysqli_query returns
*/
public function query()
{
return $this->rawQuery($this->prepareQuery(func_get_args()));
}
/**
* Conventional function to fetch single row.
*
* #param resource $result - myqli result
* #param int $mode - optional fetch mode, RESULT_ASSOC|RESULT_NUM, default RESULT_ASSOC
* #return array|FALSE whatever mysqli_fetch_array returns
*/
public function fetch($result,$mode=self::RESULT_ASSOC)
{
return mysqli_fetch_array($result, $mode);
}
/**
* Conventional function to get number of affected rows.
*
* #return int whatever mysqli_affected_rows returns
*/
public function affectedRows()
{
return mysqli_affected_rows ($this->conn);
}
/**
* Conventional function to get last insert id.
*
* #return int whatever mysqli_insert_id returns
*/
public function insertId()
{
return mysqli_insert_id($this->conn);
}
/**
* Conventional function to get number of rows in the resultset.
*
* #param resource $result - myqli result
* #return int whatever mysqli_num_rows returns
*/
public function numRows($result)
{
return mysqli_num_rows($result);
}
/**
* Conventional function to free the resultset.
*/
public function free($result)
{
mysqli_free_result($result);
}
/**
* Helper function to get scalar value right out of query and optional arguments
*
* Examples:
* $name = $db->getOne("SELECT name FROM table WHERE id=1");
* $name = $db->getOne("SELECT name FROM table WHERE id=?i", $id);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return string|FALSE either first column of the first row of resultset or FALSE if none found
*/
public function getOne()
{
$query = $this->prepareQuery(func_get_args());
if ($res = $this->rawQuery($query))
{
$row = $this->fetch($res);
if (is_array($row)) {
return reset($row);
}
$this->free($res);
}
return FALSE;
}
/**
* Helper function to get single row right out of query and optional arguments
*
* Examples:
* $data = $db->getRow("SELECT * FROM table WHERE id=1");
* $data = $db->getOne("SELECT * FROM table WHERE id=?i", $id);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return array|FALSE either associative array contains first row of resultset or FALSE if none found
*/
public function getRow()
{
$query = $this->prepareQuery(func_get_args());
if ($res = $this->rawQuery($query)) {
$ret = $this->fetch($res);
$this->free($res);
return $ret;
}
return FALSE;
}
/**
* Helper function to get single column right out of query and optional arguments
*
* Examples:
* $ids = $db->getCol("SELECT id FROM table WHERE cat=1");
* $ids = $db->getCol("SELECT id FROM tags WHERE tagname = ?s", $tag);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return array|FALSE either enumerated array of first fields of all rows of resultset or FALSE if none found
*/
public function getCol()
{
$ret = array();
$query = $this->prepareQuery(func_get_args());
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
$ret[] = reset($row);
}
$this->free($res);
}
return $ret;
}
/**
* Helper function to get all the rows of resultset right out of query and optional arguments
*
* Examples:
* $data = $db->getAll("SELECT * FROM table");
* $data = $db->getAll("SELECT * FROM table LIMIT ?i,?i", $start, $rows);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return array enumerated 2d array contains the resultset. Empty if no rows found.
*/
public function getAll()
{
$ret = array();
$query = $this->prepareQuery(func_get_args());
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
$ret[] = $row;
}
$this->free($res);
}
return $ret;
}
/**
* Helper function to get all the rows of resultset into indexed array right out of query and optional arguments
*
* Examples:
* $data = $db->getInd("id", "SELECT * FROM table");
* $data = $db->getInd("id", "SELECT * FROM table LIMIT ?i,?i", $start, $rows);
*
* #param string $index - name of the field which value is used to index resulting array
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return array - associative 2d array contains the resultset. Empty if no rows found.
*/
public function getInd()
{
$args = func_get_args();
$index = array_shift($args);
$query = $this->prepareQuery($args);
$ret = array();
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
$ret[$row[$index]] = $row;
}
$this->free($res);
}
return $ret;
}
/**
* Helper function to get a dictionary-style array right out of query and optional arguments
*
* Examples:
* $data = $db->getIndCol("name", "SELECT name, id FROM cities");
*
* #param string $index - name of the field which value is used to index resulting array
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return array - associative array contains key=value pairs out of resultset. Empty if no rows found.
*/
public function getIndCol()
{
$args = func_get_args();
$index = array_shift($args);
$query = $this->prepareQuery($args);
$ret = array();
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
$key = $row[$index];
unset($row[$index]);
$ret[$key] = reset($row);
}
$this->free($res);
}
return $ret;
}
/**
* Function to parse placeholders either in the full query or a query part
* unlike native prepared statements, allows ANY query part to be parsed
*
* useful for debug
* and EXTREMELY useful for conditional query building
* like adding various query parts using loops, conditions, etc.
* already parsed parts have to be added via ?p placeholder
*
* Examples:
* $query = $db->parse("SELECT * FROM table WHERE foo=?s AND bar=?s", $foo, $bar);
* echo $query;
*
* if ($foo) {
* $qpart = $db->parse(" AND foo=?s", $foo);
* }
* $data = $db->getAll("SELECT * FROM table WHERE bar=?s ?p", $bar, $qpart);
*
* #param string $query - whatever expression contains placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the expression
* #return string - initial expression with placeholders substituted with data.
*/
public function parse()
{
return $this->prepareQuery(func_get_args());
}
/**
* function to implement whitelisting feature
* sometimes we can't allow a non-validated user-supplied data to the query even through placeholder
* especially if it comes down to SQL OPERATORS
*
* Example:
*
* $order = $db->whiteList($_GET['order'], array('name','price'));
* $dir = $db->whiteList($_GET['dir'], array('ASC','DESC'));
* if (!$order || !dir) {
* throw new http404(); //non-expected values should cause 404 or similar response
* }
* $sql = "SELECT * FROM table ORDER BY ?p ?p LIMIT ?i,?i"
* $data = $db->getArr($sql, $order, $dir, $start, $per_page);
*
* #param string $iinput - field name to test
* #param array $allowed - an array with allowed variants
* #param string $default - optional variable to set if no match found. Default to false.
* #return string|FALSE - either sanitized value or FALSE
*/
public function whiteList($input,$allowed,$default=FALSE)
{
$found = array_search($input,$allowed);
return ($found === FALSE) ? $default : $allowed[$found];
}
/**
* function to filter out arrays, for the whitelisting purposes
* useful to pass entire superglobal to the INSERT or UPDATE query
* OUGHT to be used for this purpose,
* as there could be fields to which user should have no access to.
*
* Example:
* $allowed = array('title','url','body','rating','term','type');
* $data = $db->filterArray($_POST,$allowed);
* $sql = "INSERT INTO ?n SET ?u";
* $db->query($sql,$table,$data);
*
* #param array $input - source array
* #param array $allowed - an array with allowed field names
* #return array filtered out source array
*/
public function filterArray($input,$allowed)
{
foreach(array_keys($input) as $key )
{
if ( !in_array($key,$allowed) )
{
unset($input[$key]);
}
}
return $input;
}
/**
* Function to get last executed query.
*
* #return string|NULL either last executed query or NULL if were none
*/
public function lastQuery()
{
$last = end($this->stats);
return $last['query'];
}
/**
* Function to get all query statistics.
*
* #return array contains all executed queries with timings and errors
*/
public function getStats()
{
return $this->stats;
}
/**
* private function which actually runs a query against Mysql server.
* also logs some stats like profiling info and error message
*
* #param string $query - a regular SQL query
* #return mysqli result resource or FALSE on error
*/
private function rawQuery($query)
{
$start = microtime(TRUE);
$res = mysqli_query($this->conn, $query);
$timer = microtime(TRUE) - $start;
$this->stats[] = array(
'query' => $query,
'start' => $start,
'timer' => $timer,
);
if (!$res)
{
$error = mysqli_error($this->conn);
end($this->stats);
$key = key($this->stats);
$this->stats[$key]['error'] = $error;
$this->cutStats();
$this->error("$error. Full query: [$query]");
}
$this->cutStats();
return $res;
}
private function prepareQuery($args)
{
$query = '';
$raw = array_shift($args);
$array = preg_split('~(\?[nsiuap])~u',$raw,null,PREG_SPLIT_DELIM_CAPTURE);
$anum = count($args);
$pnum = floor(count($array) / 2);
if ( $pnum != $anum )
{
$this->error("Number of args ($anum) doesn't match number of placeholders ($pnum) in [$raw]");
}
foreach ($array as $i => $part)
{
if ( ($i % 2) == 0 )
{
$query .= $part;
continue;
}
$value = array_shift($args);
switch ($part)
{
case '?n':
$part = $this->escapeIdent($value);
break;
case '?s':
$part = $this->escapeString($value);
break;
case '?i':
$part = $this->escapeInt($value);
break;
case '?a':
$part = $this->createIN($value);
break;
case '?u':
$part = $this->createSET($value);
break;
case '?p':
$part = $value;
break;
}
$query .= $part;
}
return $query;
}
private function escapeInt($value)
{
if ($value === NULL)
{
return 'NULL';
}
if(!is_numeric($value))
{
$this->error("Integer (?i) placeholder expects numeric value, ".gettype($value)." given");
return FALSE;
}
if (is_float($value))
{
$value = number_format($value, 0, '.', ''); // may lose precision on big numbers
}
return $value;
}
private function escapeString($value)
{
if ($value === NULL)
{
return 'NULL';
}
return "'".mysqli_real_escape_string($this->conn,$value)."'";
}
private function escapeIdent($value)
{
if ($value)
{
return "`".str_replace("`","``",$value)."`";
} else {
$this->error("Empty value for identifier (?n) placeholder");
}
}
private function createIN($data)
{
if (!is_array($data))
{
$this->error("Value for IN (?a) placeholder should be array");
return;
}
if (!$data)
{
return 'NULL';
}
$query = $comma = '';
foreach ($data as $value)
{
$query .= $comma.$this->escapeString($value);
$comma = ",";
}
return $query;
}
private function createSET($data)
{
if (!is_array($data))
{
$this->error("SET (?u) placeholder expects array, ".gettype($data)." given");
return;
}
if (!$data)
{
$this->error("Empty array for SET (?u) placeholder");
return;
}
$query = $comma = '';
foreach ($data as $key => $value)
{
$query .= $comma.$this->escapeIdent($key).'='.$this->escapeString($value);
$comma = ",";
}
return $query;
}
private function error($err)
{
$err = __CLASS__.": ".$err;
if ( $this->emode == 'error' )
{
$err .= ". Error initiated in ".$this->caller().", thrown";
trigger_error($err,E_USER_ERROR);
} else {
throw new $this->exname($err);
}
}
private function caller()
{
$trace = debug_backtrace();
$caller = '';
foreach ($trace as $t)
{
if ( isset($t['class']) && $t['class'] == __CLASS__ )
{
$caller = $t['file']." on line ".$t['line'];
} else {
break;
}
}
return $caller;
}
/**
* On a long run we can eat up too much memory with mere statsistics
* Let's keep it at reasonable size, leaving only last 100 entries.
*/
private function cutStats()
{
if ( count($this->stats) > 100 )
{
reset($this->stats);
$first = key($this->stats);
unset($this->stats[$first]);
}
}
}
//HOW I'M CURRENTLY CONNECTING TO THE DATABASE & CREATING GLOBAL VAR
global $db;
$db = new SafeMySQL('localhost', 'user', 'password', 'database');
This is the class I would like to extend the above database class to:
class News
{
public $news_id;
var $author;
var $title;
var $body;
var $date;
var $comments_count;
function __construct($id)
{
global $db;
$row = $db->getRow('SELECT *
FROM news_articles
WHERE id = ?i', $id);
$this->news_id = $row[id];
$this->author = $row[author];
$this->title = $row[title];
$this->body = $row[body];
$this->date = $row[date];
$this->comments_count = $this->countComments();
}
public static function getAllArticles(){
global $db;
$all_articles_array = $db->getAll('SELECT id
FROM news_articles ORDER BY date DESC');
return $all_articles_array;
}
Please do not use the word 'extends'. It has a very special meaning and may confuse a reader. You rather need to 'use' another class' instance in this .
Although in my opinion using global keyword for the site-wide global variables is all right, you'd be teared in pieces if spotted by local 'global police'. So, it's safer to pass a $db object into constructor and assign it as a class property:
class News
{
public $news_id;
private $db;
var $author;
var $title;
var $body;
var $date;
var $comments_count;
function __construct($db, $id)
{
$this->db = $db;
$sql = 'SELECT * FROM news_articles WHERE id = ?i';
$row = $this->db->getRow($sql, $id);
$this->news_id = $row['id'];
$this->author = $row['author'];
$this->title = $row['title'];
$this->body = $row['body'];
$this->date = $row['date'];
$this->comments_count = $this->countComments();
}
public static function getAllArticlesIds()
{
$sql = 'SELECT id FROM news_articles ORDER BY date DESC';
return $thus->db->getCol($sql);
}
}
Note that I renamed the other method and used getCol() method here as you are selecting only one column.
However i don't quite understand why do you set some properties in the constructor. It seems you are confusing two classes - News and Article. It's for the single Article object you have to initialize it's properties in the constructor. While for the News I doubt it is the right way.
I'm not sure if I understand your problem. However, I notice that SafeMySql does not provide the connection handle. It might help to add this:
/**
* Function to get the connection handle.
* Addition to original SafeDB.
*
* Examples:
* mysqli_autocommit($db->getHandle(),FALSE);
* mysqli_commit($db->getHandle());
*
* #param string $getHandle - an SQL connection handle
* #return object
*/
public function getHandle()
{
return $this->conn;
}

ZF2 ldap pagination

I'm having some issues with the LDAP libraries and Active Directory. I cannot search with the LDAP libraries if the parameters will return more than 1000 results, because of the limitations of AD. Also there doesn't seem to be a way to paginate the results using the ZF2 LDAP libraries. I know how to paginate but i would rather use a ZF2 in-built method if one exists.
Is there something I'm missing or do I have to create my own method to achieve this?
P.S. I have looked over the manual and the code but i cannot see any methods to achieve this.
This is what i did to get around the issue
/**
* An LDAP search routine for finding information and returning paginated results
*
* Options can be either passed as single parameters according to the
* method signature or as an array with one or more of the following keys
* - filter
* - baseDn
* - scope
* - attributes
* - sort
* - collectionClass
* - sizelimit
* - timelimit
*
* #param string|Filter\AbstractFilter|array $filter
* #param string|Dn|null $basedn
* #param array $attributes
* #param string|null $sort
* #param string|null $collectionClass
* #param integer $timelimit
* #param integer $pageSize
* #return Array
* #throws Exception\LdapException
*/
public function multiPageSearch(
$filter, $basedn = null, array $attributes = array(), $sort = null,
$collectionClass = null, $timelimit = 0, $pageSize = 1000
)
{
if (is_array($filter)) {
$options = array_change_key_case($filter, CASE_LOWER);
foreach ($options as $key => $value) {
switch ($key) {
case 'filter':
case 'basedn':
case 'scope':
case 'sort':
$$key = $value;
break;
case 'attributes':
if (is_array($value)) {
$attributes = $value;
}
break;
case 'collectionclass':
$collectionClass = $value;
break;
case 'sizelimit':
case 'timelimit':
$$key = (int) $value;
break;
}
}
}
if ($basedn === null) {
$basedn = $this->getBaseDn();
} elseif ($basedn instanceof Dn) {
$basedn = $basedn->toString();
}
if ($filter instanceof Filter\AbstractFilter) {
$filter = $filter->toString();
}
$resource = $this->getResource();
$results = new \ArrayIterator;
Stdlib\ErrorHandler::start(E_WARNING);
$cookie = '';
do {
ldap_control_paged_result($resource, $pageSize, true, $cookie);
$result = ldap_search($resource, $basedn, $filter, $attributes, 0, $pageSize, $timelimit);
if ($sort !== null && is_string($sort)) {
$isSorted = ldap_sort($resource, $result, $sort);
if ($isSorted === false) {
throw new Exception\LdapException($this, 'sorting: ' . $sort);
}
}
$entries = new \ArrayIterator(ldap_get_entries($resource, $result));
foreach ($entries as $e) {
$results[] = $e;
}
ldap_control_paged_result_response($resource, $result, $cookie);
} while($cookie !== null && $cookie != '');
Stdlib\ErrorHandler::stop();
if ($results->count() == 0) {
throw new Exception\LdapException($this, 'searching: ' . $filter);
}
return $results;
}
This class/method was made to extends the Zend\Ldap\Ldap class, it will allow more than 1000 results to be returned, but it will not return them in the same format as the Ldap::search method.
Check out the Zend LDAP Library: Zend\Ldap\Ldap.php
/**
* A global LDAP search routine for finding information.
*
* Options can be either passed as single parameters according to the
* method signature or as an array with one or more of the following keys
* - filter
* - baseDn
* - scope
* - attributes
* - sort
* - collectionClass
* - sizelimit
* - timelimit
*
* #param string|Filter\AbstractFilter|array $filter
* #param string|Dn|null $basedn
* #param integer $scope
* #param array $attributes
* #param string|null $sort
* #param string|null $collectionClass
* #param integer $sizelimit
* #param integer $timelimit
* #return Collection
* #throws Exception\LdapException
*/
public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB, array $attributes = array(),
$sort = null, $collectionClass = null, $sizelimit = 0, $timelimit = 0
)
{
// ..
}
You can see from the signature you can pass in a parameter to limit the resultset.
A very simple wrapper for this method:
/**
* Search for entries.
*
* #parram string
* #param string
* #param int
* #param int
* #return array
*/
public function search($filter, $basedn = null, $scope = \Zend\Ldap\Ldap::SEARCH_SCOPE_SUB, $sizelimit = 0)
{
$attributes = array();
$sort = null;
$collectionClass = null;
$result = $this->_getLdap()
->search($filter, $basedn, $scope, $attributes, $sort, $collectionClass, $sizelimit)
;
return $result;
}
A simple example to find users could be somethig like this:
/**
* Get all Users..
*
* #param string
* #return \Zend\Ldap\Collection
*/
public function getUsers($baseDn, $sizelimit = 0)
{
return $this->search('(objectCategory=user)', $baseDn, $sizelimit);
}
You could then Use an Iterator Adapter with the Paginator to get this to work correctly:
use Zend\Paginator\Adapter\Iterator as IteratorAdapter;
// ..
$users = $this->_getMyLdapService()->getUsers();
$paginator = new Paginator(new IteratorAdapter($users));

Categories