_ _ _ ___(_)_ __ ___ _ __ | | ___ _ __ _ __ ___ ___| |_ __ _ _ __ ___ ___ / __| | '_ ` _ \| '_ \| |/ _ \| '_ \ | '_ \ / _ \/ __| __/ _` | '__/ _ \/ __| \__ \ | | | | | | |_) | | (_) | | | | | |_) | (_) \__ \ || (_| | | | __/\__ \ |___/_|_| |_| |_| .__/|_|\___/|_| |_| | .__/ \___/|___/\__\__, |_| \___||___/ |_| |_| |___/
- Installing
- Direct vs. PgSqlManager
- Setup connection
- Usage: Direct access
4.1. Query
4.2. Insert
4.3. Update
4.4. Replace
4.5. Delete
4.6. Execute - Usage: PgSqlManager
5.1. Query
5.2. Insert
5.3. Update
5.4. Replace
5.5. Delete
5.6. Execute - IN() Clause Handling
6.1. The issue
6.2. The solution - CRUD Helper
7.1. Intro
7.2. Requirements
7.3. Flexibility/Restrictions
7.4. Conclusion
7.5. Examples
7.6. Example Custom Vo - Exceptions
- PHP >= 5.3
- PDO
Easy install via composer. Still no idea what composer is? Inform yourself here.
{
"require": {
"simplon/postgres": "*"
}
}
I implemented two different ways of interacting with Postgres. The first option is the usual one which interacts directly with the database. Following a straight forward example to show you what I mean:
$dbConn->fetchRow('SELECT * FROM names WHERE name = :name', array('name' => 'Peter'));
In constrast to the prior method the PgSqlManager uses a Builder Pattern to deal with the database. What advantage does that offer? Well, in case that we want to do more things with our query before sending it off we encapsule it as a Builder Pattern
. From there on we could pass it throughout our application to add more data or alike before sending the query finally off to the database. Again, a quick example of how we would rewrite the above direct query
:
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setQuery('SELECT * FROM names WHERE name = :name')
->setConditions(array('name' => 'Peter'));
$pgSqlManager = new \Simplon\Postgres\Manager\PgSqlManager($dbConn);
$pgSqlManager->fetchRow($sqlBuilder);
The library requires a config value object in order to instantiate a connection with Postgres. See how it's done:
$config = array(
// required credentials
'host' => 'localhost',
'user' => 'rootuser',
'password' => 'rootuser',
'database' => 'our_database',
// optional
'fetchMode' => \PDO::FETCH_ASSOC,
'charset' => 'utf8',
'port' => 3306,
'unixSocket' => null,
);
// standard setupPostgres
$dbConn = new \Simplon\Postgres\Postgres(
$config['host'],
$config['user'],
$config['password'],
$config['database']
);
The following code shows all possible parameters to setup a connection:
\Simplon\Postgres\Postgres::__construct(
$host,
$user,
$password,
$database,
$fetchMode = \PDO::FETCH_ASSOC,
$charset = 'utf8',
array $options = array('port' => 3306, 'unixSocket' => '')
);
In case that you wanna use the PgSqlManager
there is one piece missing:
$pgSqlManager = new \Simplon\Postgres\Manager\PgSqlManager($dbConn);
Returns a selected column from the first match. The example below returns id
or false
if nothing was found.
$result = $dbConn->fetchColumn('SELECT id FROM names WHERE name = :name', array('name' => 'Peter'));
// result
var_dump($result); // '1' || false
Returns an array with the selected column from all matching datasets. In the example below an array with all ids
will be returned or false
if nothing was found.
$result = $dbConn->fetchColumnMany('SELECT id FROM names WHERE name = :name', array('name' => 'Peter'));
// result
var_dump($result); // ['1', '15', '30', ...] || false
Returns one matching dataset at a time. It is resource efficient and therefore handy when your result has many data. In the example below you either iterate through the foreach loop in case you have matchings or nothing will happen.
$cursor = $dbConn->fetchColumnMany('SELECT id FROM names WHERE name = :name', array('name' => 'Peter'));
foreach ($cursor as $result)
{
var_dump($result); // '1'
}
Returns all selected columns from a matched dataset. The example below returns id
, age
for the matched dataset. If nothing got matched false
will be returned.
$result = $dbConn->fetchRow('SELECT id, age FROM names WHERE name = :name', array('name' => 'Peter'));
var_dump($result); // ['id' => '1', 'age' => '22'] || false
Returns all selected columns from all matched dataset. The example below returns for each matched dataset id
, age
. If nothing got matched false
will be returned.
$result = $dbConn->fetchRowMany('SELECT id, age FROM names WHERE name = :name', array('name' => 'Peter'));
var_dump($result); // [ ['id' => '1', 'age' => '22'], ['id' => '15', 'age' => '40'], ... ] || false
Same explanation as for FetchColumnManyCursor
except that we receive all selected columns.
$result = $dbConn->fetchRowMany('SELECT id, age FROM names WHERE name = :name', array('name' => 'Peter'));
foreach ($cursor as $result)
{
var_dump($result); // ['id' => '1', 'age' => '22']
}
Inserting data into the database is pretty straight forward. Follow the example below:
$data = array(
'id' => false,
'name' => 'Peter',
'age' => 45,
);
$id = $dbConn->insert('names', $data);
var_dump($id); // 50 || bool
The result depends on the table. If the table holds an autoincrementing ID
column you will receive the ID count for the inserted data. If the table does not hold such a field you will receive true
for a successful insert. If anything went bogus you will receive false
.
Follow the example for inserting many datasets at once:
$data = array(
array(
'id' => false,
'name' => 'Peter',
'age' => 45,
),
array(
'id' => false,
'name' => 'Peter',
'age' => 16,
),
);
$id = $dbConn->insertMany('names', $data);
var_dump($id); // 50 || bool
The result depends on the table. If the table holds an autoincrementing ID
column you will receive the ID count for the inserted data. If the table does not hold such a field you will receive true
for a successful insert. If anything went bogus you will receive false
.
Same as for insert statements accounts for updates. Its easy to understand. If the update succeeded the response will be true
. If something went wrong you will receive false
.
$conds = array(
'id' => 50,
);
$data = array(
'name' => 'Peter',
'age' => 50,
);
$result = $dbConn->update('names', $conds, $data);
var_dump($result); // true || false
Same as for insert statements accounts for updates. Its easy to understand. If the update succeeded the response will be true
. If something went wrong you will receive false
.
$conds = array(
'id' => 50,
'name' => 'Peter',
);
// custom conditions query
$condsQuery = 'id = :id OR name =: name';
$data = array(
'name' => 'Peter',
'age' => 50,
);
$result = $dbConn->update('names', $conds, $data, $condsQuery);
var_dump($result); // true || false
As Postgres states it: REPLACE
works exactly like INSERT
, except that if an old row in the table has the same value as a new row for a PRIMARY KEY
or a UNIQUE index
, the old row is deleted before the new row is inserted.
As a result you will either receive the INSERT ID
or false
in case something went wrong.
$data = array(
'id' => 5,
'name' => 'Peter',
'age' => 16,
);
$result = $dbConn->replace('names', $data);
var_dump($result); // 1 || false
As a result you will either receive an array of INSERT IDs
or false
in case something went wrong.
$data = array(
array(
'id' => 5,
'name' => 'Peter',
'age' => 16,
),
array(
'id' => 10,
'name' => 'John',
'age' => 22,
),
);
$result = $dbConn->replaceMany('names', $data);
var_dump($result); // [5, 10] || false
The following example demonstrates how to remove data. If the query succeeds we will receive true
else false
.
$result = $dbConn->delete('names', array('id' => 50));
var_dump($result); // true || false
The following example demonstrates how to remove data with a custom conditions query. If the query succeeds we will receive true
else false
.
$conds = array(
'id' => 50,
'name' => 'John',
);
// custom conditions query
$condsQuery = 'id = :id OR name =: name';
$result = $dbConn->delete('names', $conds, $condsQuery);
var_dump($result); // true || false
This method is ment for calls which do not require any parameters such as TRUNCATE
. If the call succeeds you will receive true
. If it fails an PostgresException
will be thrown.
$result = $dbConn->executeSql('TRUNCATE names');
var_dump($result); // true
The following query examples will be a rewrite of the aforementioned direct access
examples. Remember: We need an instance of the PgSqlManager
. Paragraph 3. Setup connection
shows how to get your hands on it.
Returns a selected column from the first match. In the example below id
will be returned or false
if nothing was found.
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setQuery('SELECT id FROM names WHERE name = :name')
->setConditions(array('name' => 'Peter'));
$result = $pgSqlManager->fetchColumn($sqlBuilder);
// result
var_dump($result); // '1' || false
Returns an array with the selected column from all matching datasets. In the example below an array with all ids
will be returned or false
if nothing was found.
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setQuery('SELECT id FROM names WHERE name = :name')
->setConditions(array('name' => 'Peter'));
$result = $pgSqlManager->fetchColumnMany($sqlBuilder);
// result
var_dump($result); // ['1', '15', '30', ...] || false
Returns one matching dataset at a time. It is resource efficient and therefore handy when your result has many data. In the example below you either iterate through the foreach loop in case you have matchings or nothing will happen.
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setQuery('SELECT id FROM names WHERE name = :name')
->setConditions(array('name' => 'Peter'));
foreach ($pgSqlManager->fetchColumnMany($sqlBuilder) as $result)
{
var_dump($result); // '1'
}
Returns all selected columns from a matched dataset. The example below returns id
, age
for the matched dataset. If nothing got matched false
will be returned.
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setQuery('SELECT id, age FROM names WHERE name = :name')
->setConditions(array('name' => 'Peter'));
$result = $pgSqlManager->fetchRow($sqlBuilder);
var_dump($result); // ['id' => '1', 'age' => '22'] || false
Returns all selected columns from all matched dataset. The example below returns for each matched dataset id
, age
. If nothing got matched false
will be returned.
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setQuery('SELECT id, age FROM names WHERE name = :name')
->setConditions(array('name' => 'Peter'));
$result = $pgSqlManager->fetchRowMany($sqlBuilder);
var_dump($result); // [ ['id' => '1', 'age' => '22'], ['id' => '15', 'age' => '40'], ... ] || false
Same explanation as for FetchColumnManyCursor
except that we receive all selected columns.
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setQuery('SELECT id, age FROM names WHERE name = :name')
->setConditions(array('name' => 'Peter'));
foreach ($pgSqlManager->fetchRowManyCursor($sqlBuilder) as $result)
{
var_dump($result); // ['id' => '1', 'age' => '22']
}
Inserting data into the database is pretty straight forward. Follow the example below:
$data = array(
'id' => false,
'name' => 'Peter',
'age' => 45,
);
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setTableName('names')
->setData($data);
$id = $pgSqlManager->insert($sqlBuilder);
var_dump($id); // 50 || false
The result depends on the table. If the table holds an autoincrementing ID
column you will receive the ID count for the inserted data. If the table does not hold such a field you will receive true
for a successful insert. If anything went bogus you will receive false
.
Follow the example for inserting many datasets at once:
$data = array(
array(
'id' => false,
'name' => 'Peter',
'age' => 45,
),
array(
'id' => false,
'name' => 'Peter',
'age' => 16,
),
);
$sqlBuilder = (new \Simplon\Postgres\Manager\PgSqlQueryBuilder())
->setTableName('names')
->setData($data);
$result = $pgSqlManager->insert($sqlBuilder);
var_dump($id); // [50, 51, ...] || false
The result depends on the table. If the table holds an autoincrementing ID
column you will receive the ID count for the inserted data. If the table does not hold such a field you will receive true
for a successful insert. If anything went bogus you will receive false
.
Same as for insert statements accounts for updates. Its easy to understand. If the update succeeded the response will be true
. If something went wrong you will receive false
.
$data = array(
'name' => 'Peter',
'age' => 50,
);
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setTableName('names')
->setConditions(array('id' => 50))
->setData($data);
$result = $pgSqlManager->update($sqlBuilder);
var_dump($result); // true || false
Same as for insert statements accounts for updates. Its easy to understand. If the update succeeded the response will be true
. If something went wrong you will receive false
.
$data = array(
'name' => 'Peter',
'age' => 50,
);
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setTableName('names')
->setConditions(array('id' => 50))
->setConditionsQuery('id = :id OR name =: name')
->setData($data)
$result = $pgSqlManager->update($sqlBuilder);
var_dump($result); // true || false
As Postgres states it: REPLACE
works exactly like INSERT
, except that if an old row in the table has the same value as a new row for a PRIMARY KEY
or a UNIQUE index
, the old row is deleted before the new row is inserted.
As a result you will either receive the INSERT ID
or false
in case something went wrong.
$data = array(
'id' => 5,
'name' => 'Peter',
'age' => 16,
);
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setTableName('names')
->setData($data);
$result = $pgSqlManager->replace($sqlBuilder);
var_dump($result); // 1 || false
As a result you will either receive an array of INSERT IDs
or false
in case something went wrong.
$data = array(
array(
'id' => 5,
'name' => 'Peter',
'age' => 16,
),
array(
'id' => 10,
'name' => 'John',
'age' => 22,
),
);
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setTableName('names')
->setData($data);
$result = $pgSqlManager->replaceMany($sqlBuilder);
var_dump($result); // [5, 10] || false
The following example demonstrates how to remove data. If the query succeeds we will receive true
else false
.
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setTableName('names')
->setConditions(array('id' => 50));
$result = $pgSqlManager->delete($sqlBuilder);
var_dump($result); // true || false
The following example demonstrates how to remove data with a custom conditions query. If the query succeeds we will receive true
else false
.
$sqlBuilder = new \Simplon\Postgres\Manager\PgSqlQueryBuilder();
$sqlBuilder
->setTableName('names')
->setConditions(array('id' => 50, 'name' => 'Peter'))
->setConditionsQuery('id = :id OR name =: name');
$result = $pgSqlManager->delete($sqlBuilder);
var_dump($result); // true || false
There is no way using an IN()
clause via PDO. This functionality is simply not given. However, you could do something like the following:
$ids = array(1,2,3,4,5);
$query = "SELECT * FROM users WHERE id IN (" . join(',', $ids) . ")";
Looks good at first sight - not sexy but probably does the job, right? Wrong. This approach only works with INTEGERS
and it does not ESCAPE
the user's input - the reason why we use PDO
in first place.
Just for the record here is a string example which would not work:
$emails = array('[email protected]', '[email protected]');
$query = "SELECT * FROM users WHERE email IN (" . join(',', $emails) . ")";
The only way how this would work is by wrapping each value like the following: '"email"'
. Way too much work.
To take advantage of the built in IN() Clause
with escaping and type handling do the following:
// integers
$conds = array('ids' => array(1,2,3,4,5));
$query = "SELECT * FROM users WHERE id IN (:ids)";
// strings
$conds = array('emails' => array('[email protected]', '[email protected]'));
$query = "SELECT * FROM users WHERE email IN (:emails)";
CRUD
stands for Create Read Update Delete
and reflects the for basic functions for persisent storage.
I found myself writing more and more CRUDs for all my object/database interactions simply for the reason of having a
SINGLE POINT OF ACCESS
when I was interacting with these objects for above mentioned functions. Eventually, it has
sort of a touch of a database model but with more flexibility. Also, we keep writing VALUE OBJECTS
and by that we
keep the red line for all our code base.
Note: VALUE OBJECTS
are actually MODELS
while models are not value objects. The reason for this is that a value
object is vehicle for all sorts of data while models are only vehicles for database data. At least that's what it should be.
There are really not many requirements/restrictions:
- Instance of
PgSqlCrudManager
- requires an instance ofSimplon\Postgres
. - Value object needs to extend from
PgSqlCrudVo
- Table name should be in plural or set it via
PgSqlCrudVo::$crudSource
within the value object. - Value object's instance variables must match the table's column names in
CamelCase
(see example below). - Each value object reflects
ONE OBJECT
only -Postgres::fetchRow()
fetches your data. VARIABLE = COLUMN
Don't set any property in your value object which doesn't reflect your database table. If you have to, make either use ofPgSqlCrudVo::crudColumns()
orPgSqlCrudVo::crudIgnore()
. SeeFlexibility
for description.- In case of a
PRIMARY KEY
: if your field name differs fromid
you need to set it viaPgSqlCrudVo::crudPkName
. This is necessary for auto-incrementing the PK field as well as for receiving thelastInsertId
.
-
Set source: In case you have a table name which can't be easily pluralised (e.g. person/people) you can set the source yourself via
PgSqlCrudVo::$crudSource
within value object -
Set custom read query: In case you need a custom query to get your object you can set it when you instantiate the object
new PgSqlCrudVo($query)
or simply within your__construct() { parent::construct($query); }
. -
Callbacks: You can implement two methods which will be called prior/after saving an object:
PgSqlCrudVo::crudBeforeSave($isCreateEvent)
andPgSqlCrudVo::crudAfterSave($isCreateEvent)
. The manager will pass you a boolean to let you know what type of save process happens/happened. You could use this e.g. to set automaticallycreated_at
andupdated_at
fields. -
Set columns: If you have to either match property- and column name or only want a selection of your properties make use of
PgSqlCrudVo::crudColumns()
within your value object. It should return an array where theARRAY KEY
reflects the value object'sVARIABLE NAME
and theARRAY VALUE
theCOLUMN NAME
. Example:array('createdAt' => 'created_at')
-
Ignore properties: Considering the prior point you could do the reverse and simply
IGNORE VARIABLES
. For that implementPgSqlCrudVo::crudIgnore()
which should return an array of properties you would like to ignore. -
No assumptions: There are no assumptions about primary keys or anything alike. You set all conditions for reading, updating and/or deleting objects.
-
Casted values: Thanks to your value object which is always in between you and your database you can cast all values - good bye
STRING CASTED ONLY
values.
That's all what is needed - at least for now. It's simple
, explicit
and flexible
enough not to restrict you in your requirements respectively your creativity
.
Enough talk, bring it on! Alright, what is needed? Lets assume we have a database table called users
and a value
object called UserVo
. Note: the value object name has to be the singular of the table's plural name.
Here is the table schema:
CREATE TABLE users (
id serial NOT NULL,
name varying(50) NOT NULL DEFAULT '',
email varying(254) NOT NULL DEFAULT '',
created_at int(10) unsigned NOT NULL,
updated_at int(10) unsigned NOT NULL,
CONSTRAINT users_user_pkey PRIMARY KEY (id)
);
... and here is our value object for the given table:
class UserVo extends \Simplon\Postgres\Crud\PgSqlCrudVo
{
protected $id;
protected $name;
protected $email;
protected $createdAt;
protected $updatedAt;
// ... here goes getter/setter for the above variables
}
Now, lets do some CRUD, baby! For all processes we need an instance of our PgSqlCrudManager
:
/**
* construct it with an instance of your simplon/Postgres
*/
$PgSqlCrudManager = new \Simplon\Postgres\Crud\PgSqlCrudManager($postgresInstance);
Create a user:
$userVo = new UserVo();
$userVo
->setName('Johnny Foobar')
->setEmail('[email protected]');
/** @var UserVo $userVo */
$userVo = $PgSqlCrudManager->create($userVo);
// print insert id
echo $userVo->getId(); // 1
Read a user:
// conditions: where id = 1
$conds = array('id' => 1);
/** @var UserVo $userVo */
$userVo = $PgSqlCrudManager->read(new UserVo(), $conds);
// print name
echo $userVo->getName(); // Johnny Foobar
Update a user:
// conditions: where id = 1
$conds = array('id' => 1);
/** @var UserVo $userVo */
$userVo = $PgSqlCrudManager->read(new UserVo(), $conds);
// set new name
$userVo->setName('Hansi Hinterseher');
// update
/** @var UserVo $userVo */
$userVo = $PgSqlCrudManager->update($userVo, $conds);
// print name
echo $userVo->getName(); // Hansi Hinterseher
Delete a user:
// conditions: where id = 1
$conds = array('id' => 1);
/**
* UserVo::crudGetSource() is the name of the table
* based on the value object's name
*/
$PgSqlCrudManager->update(UserVo::crudGetSource(), $conds);
Setting a custom table name
since the plural from person is not persons:
class PersonVo extends \Simplon\Postgres\Crud\PgSqlCrudVo
{
/**
* @return string
*/
public static function crudGetSource()
{
return 'people';
}
// ... here goes the rest
}
In case your column names
are totally off there is a way to match them anyway against your properties
:
class UserVo extends \Simplon\Postgres\Crud\PgSqlCrudVo
{
protected $id;
protected $name;
protected $email;
protected $createdAt;
protected $updatedAt;
/**
* @return array
*/
public function crudColumns()
{
return array(
'id' => 'xx_id',
'name' => 'xx_name',
'email' => 'xx_email',
'createdAt' => 'xx_created_at',
'updatedAt' => 'xx_updated_at',
);
}
// ... here goes the rest
}
Sometimes there are some helper properties
which are not part of your database entry. Here is a way to ignore them:
class UserVo extends \Simplon\Postgres\Crud\PgSqlCrudVo
{
protected $id;
protected $name;
protected $email;
protected $createdAt;
protected $updatedAt;
// helper property: not part of the people table
protected $isOffline;
/**
* @return array
*/
public function crudIgnore()
{
return array(
'isOffline',
);
}
// ... here goes the rest
}
For both access methods (direct, PgSqlManager) occuring exceptions will be wrapped by a PostgresException
. All essential exception information will be summarised as JSON
within the Exception Message
.
Here is an example of how that might look like:
{"query":"SELECT pro_id FROM names WHERE connector_type = :connectorType","params":{"connectorType":"FB"},"errorInfo":{"sqlStateCode":"42S22","code":1054,"message":"Unknown column 'pro_id' in 'field list'"}}
Simplon/Postgres is freely distributable under the terms of the MIT license.
Copyright (c) 2015 Tino Ehrich ([email protected])
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.