I'm requesting the community's wisdom because I want to avoid bad coding practices and/or mistakes.
I'm having a php class wich is an objects manager. It does all the work with the database: inserting new data, updating it, getting it and deleting it (I've read it's called CRUD...). So it has a function that gets an element by id.
What I want to write is a function that gets a list of objects from the table.
I will then use a mysql query that goes something like
SELECT * FROM mytable WHERE column1='foo'
And then some order by and limit/offset.
However, in my application there are different cases in which I will need different lists from this table. The WHERE clause will then be different.
Should I write different functions, one per type of list?
Or should I write one generic function to which I will send arguments that then dynamically creates the query? If so, do you have any advice on how to do this properly?
EDIT:
Thanks for all your answers! I should tell that I'm not using any framework (maybe wasn't the best idea...), so I didn't know about query builders. I'll investigate that (either finding a standalone uery builder or migrating to a framework or writing my own, I don't know yet). That will be useful any time I need to execute a mysql query :-)
Although I'm still confused:
Let's say I need several lists of clients (objects), for example all clients, clients over 18, clients currently online...
What approach would be best to retrieve those lists? I can either have 3 functions in my clients manager
allClients() {//execute a specific query and return list of objects}
allClientsOver18() {//execute specific query and return list of objects}
allClientsOnline() {//execute specific query and return list of objects}
or I can have one function tht builds the query based on parameters
listClients($some, $parameters)
{
//Build the query based on the parameters (definitely need a query builder!)
//Execute the query
//return list of objects
}
Which approach would be best (I guess it depends on circumstances) and mostly, why?
Thanks in advance!
Rouli
Thanks for all the info on query builders, I didn't even know it existed! :-) However I'm still confused as to wether I should write one specific function for each case (that function can still use the query builder to write its specific query), or write one generic function that builds dynamically the query based onf parameters. Which would be better in which case? I've added an example in my question, hope it makes it clearer!
This depends on how often you use each of these isolated queries, how complex the conditions are and how often you my need to combine the conditions with other queries. For eaxample if each the "online" and "over18" are just simple conditions then you could just use the normal findBy logic from my example:
$table = new MyTable($db);
$onlineOnly = $table->findBy(array('is_online' => true), null, null);
$over18Only = $table->findBy(array('is_over_18' => true), null, null);
$onlineOver18 = $table->findBy(array('is_over_18' => true, 'is_online' => true), null, null);
If the query is more complex - for example to get over 18 clients you have to do:
select client.*, (YEAR(CURDATE()) - YEAR(client.birthdate)) as age
FROM client
WHERE age >= 18
Then its probably better to make this into a separate method or create methods to work on Query objects directly to add complex conditions for example - especially if you will need this condition in a few different queries in the app:
$table = new MyTable($db);
// creates a basic query defaulted to SELECT * FROM table_name
$query = $table->createQuery();
// adds the complex condition for over 18 resulting in
// SELECT table_name.*, (YEAR(CURDATE()) - YEAR(table_name.birthdate)) as age WHERE age >= 18
$over18 = $table->applyOver18Query($query)->execute();
This way you can apply your over 18 condition easily to any query with out manually manipulating the builder ensure that your over 18 condition is consistent. But for simplicity you could also have a convenience method like the following:
public function findOver18By(array $criteria, $limit = null, $offest = null) {
$query = $this->findBy($criteria, $limit, $offset);
$this->applyOver18Query($query);
return $query->execute();
}
Normally you would use some kind of query builder at the lower level like:
$query = $db->createQuery()
->select($fields)
->from($tableName)
->where($fieldName, $value);
$results = $query->execute();
Then you might have a class that makes use of this like:
class MyTable
{
protected $tableName = 'my_table';
protected $db;
public function __construct($db) {
$this->db = $db;
}
public function findBy(array $criteria, $limit = null, $offset = null) {
$query = $this->db->createQuery();
$query->select('*')->from($this->tableName);
foreach ($criteria as $col => $value) {
// andWhere would determine internally whether or not
// this is the initial WHERE clause or an AND clause
// something similar would happen with an orWhere method
$query->andWhere($col, $value);
}
if (null !== $limit) {
$query->limit($limit);
}
if (null !== $offset) {
$query->offset($offset);
}
return $query->execute();
}
}
Usage would look like:
$table = new MyTable($db);
$result = $table->findBy(array('column1' => 'foo'), null, null);
This is a lot to implement on your own. Most people use an ORM or a DBAL to provide these features and those are often included with a framework like Eloquent with Laravel, or Doctrine with Symfony.
I guess at start you should need some main data like
$main = [
'from' = '`from_table`',
]
Then you should add selects if had
$selects = ['fields1','field2'];
$where = ['some condition', 'other condition'];
Then you could
$query = "SELECT ".implode(',', $selects ." FROM ".$main['from']."
WHERE ".implode('AND ', $where .";";
That's some approaches for simple one table query.
If you need Joins, then $selects better would be make with aliasos, so no field will be lost if they are not different, like
select temp.id as temp_id , temp2.id temp2_id from temp
left join temp2 on temp2.temp_id = temp.id
Feel free to ask some questions, maybe i haven't told , but you should also check bound parameters with some functions to avoid sql injections
I suggest using a CLASS for your database which holds all your database accessing functions as it makes your code cleaner making it more easier to look through for errors or modifications.
class Database
{
public function connect() { }
public function disconnect() { }
public function select() { }
public function insert() { }
public function delete() { }
public function update() { }
}
sample connect function for connecting to a selected database.
private db_host = ‘’;
private db_user = ‘’;
private db_pass = ‘’;
private db_name = ‘’;
public function connect()
{
if(!$this->con)
{
$myconn = mysqli_connect($this->db_host,$this->db_user,$this->db_pass);
if($myconn)
{
$seldb = mysqli_select_db($this->db_name,$myconn);
if($seldb)
{
$this->con = true;
return true;
} else
{
return false;
}
} else
{
return false;
}
} else
{
return true;
}
}
with this approach will make creating CRUD functions easier. Heres a sample insert function.
public function insert($table,$values,$rows = null)
{
if($this->tableExists($table))
{
$insert = 'INSERT INTO '.$table;
if($rows != null)
{
$insert .= ' ('.$rows.')';
}
for($i = 0; $i < count($values); $i++)
{
if(is_string($values[$i]))
$values[$i] = '"'.$values[$i].'"';
}
$values = implode(',',$values);
$insert .= ' VALUES ('.$values.')';
$ins = #mysql_query($insert);
if($ins)
{
return true;
}
else
{
return false;
}
}
}
heres a quick view on using this.
;<?php;
$db->insert('myDataBase',array(3,"Name 4","this#wasinsert.ed")); //this takes 3 paramteres
$result = $db->getResult(); //Assuming you already have getResult() function.
print_r($result);
?>
EDIT
there are more purist approach to handling database operations. I highly suggest it because handling information is very delicate and should be fronted with many safety measures But it requires deeper php knowledge. Try PDO for php and this article by matt bango on prepared statements and its significance.
Related
It's hard to explain what I want exactly but I've gotta try to...
Laravel Eloquent inspired me to write a simple php class to work with databse.
As we know We can do this in laravel:
$run = DB::table('users')->where('id', 3)->where('level', 2)->get();
Also we do that:
$run = DB::table('users')->where('id', 3)->where('level', 2)->get()->count();
Also we can do that:
$run = DB::table('users')->where('id', 3)->where('level', 2)->get()->first();
Even we can do that too:
$run = DB::table('users')->where('id', 3)->where('level', 2)->get()->pluck('id')->toArray();
And that I have not ever tried but I believe it works too:
$run = DB::table('users')->where('id', 3)->where('level', 2)->get()->pluck('id')->toArray()->first();
The question is "How does it work?"
How should I write to return suitable results in any of their ways?
// It was easy to write my code to return total results if I write like that
$run = DB::from('users')->where('id', 3)->where('level', 2)->get()->count();
// Or to return first result if I write like that
$run = DB::from('users')->where('id', 3)->where('level', 2)->get()->first();
// But what sould I do to return all the results if write like that (As eloquent works).
$run = DB::from('users')->where('id', 3)->where('level', 2)->get();
I need something like "if - else case for methods" like:
function __construct() {
if(if aint`t no calling any methods except **get()** ){
// Lets return default method
return $this->results();
}
else{
// Do whatever...
}
}
There is my whole code:
https://github.com/amirandev/PHP-OOP-DB-CLASS/blob/main/db.php
As I know, when you are trying something like that
$run = DB::from('users')->get()->count();
You get all users and php/laravel count users, meaning
$users = DB::from('users')->get(); // get all users
$usersConut = $users->count(); //count them away of database/mysql
The same thing with first()
when you are using this code DB::from('users')->count(); you are actually asking MySql for count not counting them in the backend.
I highly recommend using this package barryvdh/laravel-debugbar to help you see the database queris.
Each method returns $this. So the next method will have the class with the modifications done by the previous method. It's quite easy to achieve that.
class Example
{
private string $sentence = '';
public function make()
{
return $this->start()->end();
}
public function start()
{
$this->sentence .= 'This will be a ';
return $this;
}
public function end()
{
$this->sentence .= 'whole sentence.';
}
}
BTW, Eloquent query builder converts the method chain into an SQL query string. That's why it works really fast. If you just query one table, let's say users, via the query builder, and then filter the results in your application (like it was based on the where condition), the process will be quite slow because the hard work will be done by your app.
SQL is extremely fast, that's why we want to complete as many tasks as possible on the SQL side.
I want to build a model class for my PHP application. It will have methods meant to select/update/insert/delete specific data from a database according to the method's parameters. I only want to use prepared statements.
Here is an overview of what the class should look like :
class Database {
private $_db;
// Stores a PDO object (the connection with the database) within the $_db property
public function __construct($host, $user, $password) {...}
public function select() {...}
public function update() {...}
public function insert() {...}
public function delete() {...}
}
The problem is that I don't really know how to do this. Let's say I want to select everything from the table "farm" where the animal is a dog. The syntax for this statement would be the following :
$animal = 'dog';
$query = $this->_db->prepare('SELECT * FROM farm WHERE animal = :animal');
$query->execute(array(':animal' => $animal));
$result_set = $query->fetchAll();
This is very complicated to implement within a class method. As you can see, I call the execute() method but I don't even know in advance if the WHERE clause will be used !
And even worse : what if I will want to use, let's say, the LIMIT x, y clause later on ?
Which parameters should I ask for and how to treat them ? Should I simply require the parameters to be one query + multiple variables that will be passed to the execute() method ?
Are these types methods reasonable for what I want to do ? Maybe I should to a dedicated method for each MySQL query the application will perform, but this is quite complicated because it's a big database and a big application.
What do you guys think ?
Thanks in advance :P
Your API looks pretty useless to me, because as I see it it's just a wrapper around PDO. What do you gain by wrapping PDO like that?
Instead it would probably make more sense to have your object actually representing things, e.g.:
namespace Project\Storage\Database;
class Farm
{
private $pdo;
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
public function getAnimalsByType(string $animalType): AnimalCollection
{
$stmt = $this->pdo->prepare('SELECT * FROM farm WHERE animal = :animal');
$stmt->execute([
'animal' => $animalType,
]);
// alternatively use a factory to build this to prevent tight coupling
return new AnimalCollection($stmt->fetchAll());
}
}
On a side note: forget about MVC in PHP (it's not even possible). Just focus on the more important separation of concerns.
Maybe I should to a dedicated method for each MySQL query the application will
perform, but this is quite complicated because it's a big database and
a big application.
Yes, this is an easy way to organize your database access.
But you should not put ALL of them in the same class. You should separate your classes by their domain.
class animalRepository {
// ...
public function getAnimalByName($animal){
$query = $this->_db->prepare('SELECT * FROM farm WHERE animal = :animal');
$query->execute(array(':animal' => $animal));
$result_set = $query->fetchAll();
// ...
}
}
To make this communicate more clearly you could call those classes repositories, as they are storing the data for the specific domain.
Another common name would be mappers, because they are mapping the data to your objects.
Very opinionated answer. Anyway:
PDO's Prepared Statements are a little more capable than being created and calling execute on them. How you would usually do this is by first building your query and then binding the values:
$querystring = 'SELECT * FROM farm';
$args = array();
if($animal != '') {
$querystring .= 'WHERE animal = :animal';
$args[':animal'] = $animal;
}
$query = $this->_db->prepare($querystring);
$result = $query->execute($args)
if($result !== false) {
// fetch ...
} else {
// error output / return val
}
This is the general idea. Depending on your input parameters you build a query. It will probably become more sophisticated than that, for example filling a $where = array() and then you add to the $where[] = ... your where conditions and in the end you just join them all together with sql AND:
$this->_db->prepare($querystring.
( count($where) > 0 // the > 0 is redundant btw
? 'WHERE '.implode('AND',$where)
: '' )
);
You might similar things with joined tables, select statements and the like. It can get very complex. It's probably wise to mix this approach with separating at sensible points with Philipp's answer/approach.
Could you help to extend a little bit about the Zend Quickstart: In the tutorial, we use the Mapper to update a single Guestbook. What if I want to update more than one Guestbook? And based on some conditions?
For example, I have an action to delete all Guestbooks that were created before 2012-12-21. What should I update to achieve that?
Does my approach make sense?
// application/models/GuestbookMapper.php
class Application_Model_GuestbookMapper
{
public function deleteByCreatedBefore($date)
{
$this->getDbTable()->deleteByCreatedBefore($date);
}
}
// application/models/DbTable/Guestbook.php
class Application_Model_DbTable_Guestbook extends Zend_Db_Table_Abstract
{
public function deleteByCreatedBefore($date) {
$where = $this->getAdapter()->quoteInto('created < ?', $date);
$this->delete($where);
}
}
Thanks,
If you are using the quickstart model/mapper and want to stay true to that data mapper paradigm you wouldn't have anything in your Application_Model_DbTable_Guestbook except for properties ('name', 'primary'...). The DbTable model would exist as the database adapter for that single table.
Your delete function would be placed in the mapper.
class Application_Model_GuestbookMapper
{
public function deleteByCreatedBefore($date)
{
$where = $this->getDbTable()->quoteInto('created < ?', $date);
//delete() returns num of rows deleted
$this->getDbTable()->delete($where);
}
}
This will work but may not be the best/safest way to achieve the required functionality.
This particular example of the Data Mapper is very simple and might be somewhat misleading to some people. The Guestbook example of the Mapper is really not a good representation of the mapper as the database row and the domain model (Application_Model_Guestbook) map 1 to 1 (one database column to one model property).
Where the Data Mapper starts to shine is when you need to map several database tables to a single Domain Model. With the understanding that your Domain Model (Application_Model_Guestbook) may have to effect more then one database table each time delete() is called, the structure for the delete() function is important.
What should you do to accomplish a delete with the mapper?
First: update Application_Model_GuestbookMapper::fetchAll() to accept a $where parameter, I usually setup this type of function to accept an array that sets the column and the value.
//accepted parameters: Zend_Db_Table::fetchAll($where = null, $order = null, $count = null, $offset = null)
//accepts array (column => value )
public function fetchAll(array $where = null)
{
$select = $this->getDbTable()->select();
if (!is_null($where) && is_array($where)) {
//using a column that is not an index may effect database performance
$select->where($where['column'] = ?, $where['value']);
}
$resultSet = $this->getDbTable()->fetchAll($select);
$entries = array();
foreach ($resultSet as $row) {
$entry = new Application_Model_Guestbook();
$entry->setId($row->id)
->setEmail($row->email)
->setComment($row->comment)
->setCreated($row->created);
$entries[] = $entry;
}
return $entries;
}
Second: Refactor your Application_Model_GuestbookMapper::deleteByCreatedBefore() to accept the output from fetchAll() (actually it would be simpler to just build a delete() function that accepts the output: array of Guestbook objects)
//accepts an array of guestbook objects or a single guestbook object
public function deleteGuestbook($guest)
{
if (is_array($guest) {
foreach ($guest as $book) {
if ($book instanceof Application_Model_Guest){
$where = $this->getDbTable()->quoteInto('id = ?', $book->id);
$this->getDbTable()->delete($where);
}
}
} elseif ($guest instanceof Application_Model_Guest) {
$where = $this->getDbTable()->quoteInto('id = ?', $guest->id);
$this->getDbTable()->delete($where);
} else {
throw new Exception;
}
}
Deleting a domain object as an object will become more important as you have to consider how deleting an object will affect other objects or persistence (database) paradigms. You will at some point encounter a situation where you don't want a delete to succeed if other objects still exist.
This is only an opinion but I hope it helps.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
PHP file cannot enter some part of code
I am building a news website and I have a function file, for all functions but, nearly all the functions made the same way as the below example:
function one(){
$query = "SELECT * FROM table WHERE id='$id'....";
$result = mysql_query($query);
while($row=mysql_num_rows($result))
{echo "$row[data]";} }
So, basically there are many functions that serve different purpose, but have the same exact template as the above, So, to save time, codding I was thinking if is would be possible, to simplify the 5 lines? to one with function like:
function(something here, and here){then the result here?}
Since the only thing that may change from the first code are: Select, table names, id,
and the $row[data], and everything will remain the same what if I assign those to variables and the rest structure will stay as it is in a function:
check this idea.
function simplify() {
$query = "$crud * FROM $table WHERE id= '$id' ";
$result = mysql_query($query);
while($row=mysql_fetch_array($result)) {echo "$row[data]";}
}
So anytime, I need to create the first code, I could just do
echo simlify(and here would be table name, id, select, create, update...) {
and here the result}
So sorry, for asking this complex question, or asking it with complexity but If anyone has any idea, about what I am talking about, I need your help.
How about this, for a simple database call:
$db = new DBAdapter(array('host' => 'localhost'));
$results = $db->Select('mytable')->Where('id > 2')->Execute();
You can achieve this by building your own database class. Put the class in a separate file and reuse it many times.
class DBAdapter extends PDO
{
public function __construct( array $params)
{
// construct the connection
}
public function Select ($table)
{
// do stuff for a table select
return $this;
}
public function Count ($table)
{
// do stuff for a table count
return $this;
}
public function Where ($condition)
{
// do stuff for a where
return $this;
}
public function Execute ()
{
// execute stuff
return $result;
}
}
Just recently I started rewriting a previously procedurally written website by myself, I chose PDO as the wrapper since I'm also getting used to the OOP way of doing things. I would like some advice about the structure of the classes.
Mostly everything is database-driven, like adding categories and subcategories, brands of products, products, users, etc. I suppose each of them could be one class and since I need CRUD operations on all of them, I need a generic way of inserting, updating, deleting records in the MySql database. The problem is not the code, I'd like to (and already have) coded some of the CRUD operations by myself according to my needs, the real problem is the structure and how would I go to correctly distribute and extend those classes.
Right now I've coded 3 different approaches:
A class called 'Operations' which will be extended by all the other classes that need CRUD functions, this class contains pretty generic properties such as $id, $atributes, $fields and $table, and of course the generic methods to insert, update, delete. That way I can create, let's say my Product object with some parameters (name, category, price) and immediately Product->insert() it into the database, without passing any parameters to the insert function. The CRUD functions in this class don't accept parameters, they depend on the created object's properties.
Same as above but the CRUD functions accept parameters, making them (I suppose) more generic, in case I just need to insert something without creating an object with useless properties previously.
The 'Operations' class extends PDO, the way of working is similar to 2, but now they can be directly accessed when I create the database connection, not depending of other objects.
I'm leaning towards the first option because I think, for the most part, that it will satisfy everything I'll do with this website, again the website is already coded but procedurally, which has been a mess to maintain, so basically I need to re-do things but OO.
CMSs or already coded wrappers aside (the purpose of doing this is to learn PDO and getting used to OOP), which would be the best way to do that? not limited to the options I mentioned.
Here's the 'Operations' class I've managed to code so far, where I've been doing tests sandbox-like, don't mind the spanish variable names. Advices on the code are welcome too.
class Operaciones {
private $database;
protected $id;
protected $atributos;
protected $tabla;
protected $campos;
public function __construct($link) {
$this->database = $link;
}
public function insertar() {
if (!$this->verificarCamposNulos($this->atributos, $this->campos))
echo 'Campos nulos<br />';
else {
$this->prepararCampos();
$placeholders = $this->generarPlaceholders();
$stmt = $this->database->prepare("INSERT INTO {$this->tabla} ({$this->campos}) VALUES ({$placeholders})");
$valores = array_values($this->atributos);
$stmt->execute($valores);
$stmt = NULL;
echo 'Se ha insertado exitosamente';
}
}
public function modificar() {
if (!$this->verificarCamposNulos() || empty($this->id))
echo 'Campos nulos<br />';
else {
$this->prepararCampos('=?');
$stmt = $this->database->prepare("UPDATE {$this->tabla} SET {$this->campos} WHERE id = {$this->id}");
$valores = array_values($this->atributos);
$stmt->execute($valores);
$stmt = NULL;
echo 'Se ha modificado exitosamente';
}
}
private function generarPlaceholders() {
for($i=0;$i<count($this->atributos);$i++)
$qmarks[$i] = '?';
return implode(',', $qmarks);
}
// Check if the values to be inserted are NULL, depending on the field format given
private function verificarCamposNulos() {
$n_campos = explode(',', $this->campos);
$valores = array_values($this->atributos);
foreach($n_campos as $i => $result) {
if (strstr($result, '#'))
if (empty($valores[$i]))
return false;
}
return true;
}
// Removes the '#' from each field, used to check which fields are NOT NULL in mysql
private function prepararCampos($sufijo = NULL) {
$n_campos = explode(',', $this->campos);
foreach($n_campos as $i => $result)
$n_campos[$i] = str_replace('#', '', $result) . $sufijo;
$this->campos = implode(',', $n_campos);
}
}