auraphp / aura.sql Goto Github PK
View Code? Open in Web Editor NEWSQL database access through PDO.
License: MIT License
SQL database access through PDO.
License: MIT License
// ...
while ($row = $sth->fetch(self::FETCH_ASSOC)) {
$key = current($row);
yield $key => call_user_func($callable, $row);
}
Using a query of the following format
UPDATE table SET `value`=:value, `blank`='', `value2` = :value2, `blank2` = '', `value3`=:value3 WHERE id = :id
And passing in an array formatted as:
$arr = ['value'=> 'string', 'id'=> 1, 'value2'=> 'string', 'value3'=>'string'];
I receive the PDO " number of bound variables does not match number of tokens" error.
Examining the situation the Rebuilder
returns a valid query (i.e. the query that went in) but the array has been truncated to:
Array
(
[value] => string
[value3] => string
[id] => 1
)
This happens because the preg_split
in rebuildStatement
is splitting these "empty string" declarations incorrectly. The more empty strings in the query the more the problems. I am uncertain if the order of the strings matters.
I have added a temporary fix in my fork which adds:
$statement = str_replace("''", "_NULL_", $statement);
...
$statement = str_replace("_NULL_", "''", $statement);
Either side of the list($statement, $values) = $rebuilder->__invoke($statement, $values);
line. This could also be added inside the Rebuilder.
Is this a legitimate problem or is there a better way to be writing the original query such that ExtendedPDO doesn't support the method in question?
It looks like the issue that previously blocked its usage has been fixed:
There might be other blockers, but it's worth trying again IMO.
Currently ATTR_EMULATE_PREPARES is set to true
. This is sub-optimal.
It can open up security issues in minor edge cases. As good practice, it should be set to false, so that it always uses true prepared statements:
self::ATTR_EMULATE_PREPARES => false,
Could you release a new version of Aura.Sql? I am specifically interested in the extraction of the Rebuilder since I already have own methods for direct fetching. Thanks for the wonderful work.
When using the orWhere() method of the Select query builder, parethesis () are not put around the where conditions, making the query result inconsistent.
There are some ways to solve it, like adding a method to open and close the OR condition, or maybe trying to detect the orWhere begin and end.
If you need some help to do it or test, msg me back.
Hi Paul ,
As you have already mentioned in irc , this is just an initial commit , you can ignore if I am also wrong :-) .
Currently in many places , some variables are never assigned . For eg: currently for getRead() , see $slaves . In constructor we are accepting and setting $slaves , $masters array.
// pick a random slave, or a random master if no slaves, or default if no masters
public function getRead()
{
if ($slaves) {
return $this->getRandomSlave();
} elseif ($masters) {
return $this->getRandomMaster();
} else {
return $this->getDefault();
}
}
So I guess the below implementation will be the correct way .
// pick a random slave, or a random master if no slaves, or default if no masters
public function getRead()
{
if (!empty($this->slaves)) {
return $this->getRandomSlave();
} elseif (!empty($this->masters)) {
return $this->getRandomMaster();
} else {
return $this->getDefault();
}
}
getRandomSlave()
and getRandomSlave()
are having $key which is not assigned . See below.
// converts a random $this->masters entry to a Connection object
public function getRandomSlave()
{
if (! $key) {
$key = array_rand($this->slaves);
} elseif (! isset($this->slaves[$key])) {
throw new Exception\NoSuchConnection;
}
if (! $this->conn['slaves'][$key] instanceof Connection) {
list($adapter, $params) = $this->merge($this->slaves[$key]);
$this->conn['slaves'][$key] = $this->factory->newInstance($adapter, $params);
}
return $this->conn['slaves'][$key];
}
My commit is over
As told for the getRandomSlave()
and getRandomSlave()
, I have not made any changes . Please have a look .
Thanks
method declare:
public function update($table, array $cols, $cond, array $data = array())
if i want to increase count
field by 1, i will use update() like this:
update("tbl_name", array('count' => 'count+1", "uid=1");
it will generate sql like:
set count='count+1'
cause data type of field count is "int", so it will convert 'count+1' to 0.
so what can i do?
Actively fetching the table column meta-data on demand, every time the method is called, seems like a pretty big performance pitfall.
How about adding a getTableCols() method, which would cache the table columns (in memory) for the connection, once fetched for the first time?
Or is there some reason this responsibility is deliberately left to the consumer?
I'm running php 5.5.20 in windows with the sqlsrv extensions enabled (thread safe). When connecting to the DB with an ExtendedPdo object, I get the following error:
PDOException: SQLSTATE[IMSSP]: The given attribute is only supported on the PDOStatement object.
I'm sure that this is a problem with the ExtendedPdo object because the following code using Vanilla PDO works just fine:
$pdo = new \PDO(
'sqlsrv:Server=my_server ; DataBase=My_DB',
$userName,
$password
);
$smt = $pdo->prepare('Select Top 10 * From My_table');
$stmt->execute();
$result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// yields the appropriate associative array of results
When I try to accomplish the same thing with ExtendedPdo it doesn't work:
$pdo = new ExtendedPdo(
'sqlsrv:Server=My_Server ; DataBase=My_DB',
$userName,
$password
);
$stmt = 'Select Top 10 * From My_table';
$result = $pdo->fetchAll($stmt);
//yields the error:
//PDOException: SQLSTATE[IMSSP]: The given attribute is only supported on the PDOStatement object.
With the extended PDO this error seems to get triggered as soon as it tries to establish a connection to the DB. Calling connect()
, prepare()
, and fetch*()
yields exactly the same behavior. Am I doing something wrong or is there a compatibility issue with ExtendedPdo and sqlsrv?
After reading the doc block over the ExtendedPdo Constructor I realized that I could just pass in an already instantiated PDO object. I have tested this and it seems to work fine. Code for illustration:
$pdo = new \PDO(
'sqlsrv:Server=My_Server ; DataBase=My_DB',
$userName,
$password
);
$extended = new ExtendedPdo($pdo);
$stmt = 'Select Top 10 * from My_table';
$result = $extended->fetchAll($stmt);
// yields the appropriate array of results.
While this does work and will make my life a little bit easier, it'd be nice to take advantage of ExtendedPdo's lazy connection feature which means there is still a bug somewhere.
When the $options not passed as array
$di->params['Aura\Sql\ConnectionManager'] = array(
'factory' => $di->lazyNew('Aura\Sql\ConnectionFactory'),
'default' => array(
'adapter' => 'mysql',
'dsn' => array(
'host' => 'localhost',
'dbname' => 'auradb',
),
'username' => 'monty',
'password' => 'some_pass',
//'options' => array(),
),
);
the error is getting
Notice: Undefined index: options in /media/Linux/aurasystem/package/Aura.Sql/src/ConnectionManager.php on line 129 Catchable fatal error: Argument 4 passed to Aura\Sql\Connection\AbstractConnection::__construct() must be an array, null given in /media/Linux/aurasystem/package/Aura.Sql/src/Connection/AbstractConnection.php on line 31
When closely looking the $merge is not working as expected . A print_r( $merged ) will see what values it has . The options is undefined . A quick and dirty fix is
protected function mergeAdapterParams(array $override = array())
{
$merged = $this->merge($this->default, $override);
$adapter = $merged['adapter'];
/*
* if no options passed by defaut the options should be an array
* Currently the merge not working as expected. And the options is undefined .
* A quick and dirty fix is pass in configs default as options => array()
* or fixing like the one below
*/
if(! isset($merged['options']) ) {
$merged['options'] = array();
}
$params = array(
'dsn' => $merged['dsn'],
'username' => $merged['username'],
'password' => $merged['password'],
'options' => $merged['options'],
);
return array($adapter, $params);
}
Still some other issue :( to make a connection , looking .
Working with MySQL in Ubuntu .
lastInsertId() returns 0 .
Have a look into http://www.php.net/manual/en/pdo.lastinsertid.php#85129 , also have a look into postgre's
http://www.php.net/manual/en/pdo.lastinsertid.php#86591
So lastInsertId seems not reliable .
Note from php.net :
This method may not return a meaningful or consistent result across different PDO drivers, because the underlying database may not even support the notion of auto-increment fields or sequences.
Hi Paul ,
I am in need to issue multi query . Is there a way we can make it ?
I was looking at http://stackoverflow.com/a/4559320/487878
Do you have any suggestions how we can make it ?
Hi @pmjones ,
In the https://github.com/auraphp/Aura.Sql/blob/master/tests/bootstrap.php , we have currently the username and password for mysql , postgres, but for travis-ci the username and passwords are different .
Can you make the necessary changes on your database system or add the tests/bootstrap.php to .gitignore files , so we don't get any issues ?
You can check the correct values in
https://github.com/harikt/Aura.Sql/blob/travis/.travis.yml
or for your convenience
- DB=mysql:
adapter: mysql2
database: test
username:
encoding: utf8
- DB=postgres:
adapter: postgresql
database: test
username: postgres
The https://github.com/harikt/Aura.Sql/blob/travis/tests/bootstrap.php I was using has a username root
for mysql , but when I checked recently the root from travis-ci seems missing in their documentation. But still this works .
See http://travis-ci.org/#!/harikt/Aura.Sql
Thank you
How do you convert instanceof Aura\Sql\Select
? The method toString()
is protected . __toString()
works when we try to echo
or print
the object .
I was passing a select object to the query. I think we need to change the protected method to public.
Hi, i'm using Aura.Sql for a while...
last friday, i check mysql logs for secure purpose. i found a lot of un-quoted fields in sqls. like:
SELECT id AS `uid`, name, age, sex FROM `rel_rev_file` WHERE age > 25 AND sex = 'female' OR id < 10 GROUP BY sex, age HAVING COUNT(age) > 60 ORDER BY id DESC, age LIMIT 20 OFFSET 40;
it just process field alias and table name, how about other fields???
Did you missed to add DriverFactory
?
Its still mentioning at scripts/instance.php
.
Also the use Aura\Sql\Select;
I've found that whe one uses AuraDi to create an instance of ExtendedPDO, the arrays for attributes and driver options doesn't work properly:
# Set parameters for Aura's ExtendedPdo
$di->params['Aura\Sql\ExtendedPdo'] = array(
'dsn' => "mysql:host=" . $app_config["database"]["host"] . ";dbname=" . $app_config["database"]["dbname"],
'username' => $app_config["database"]["username"],
'password' => $app_config["database"]["password"],
array(),
array(
Aura\Sql\ExtendedPdo::ATTR_EMULATE_PREPARES => false,
Aura\Sql\ExtendedPdo::ATTR_STRINGIFY_FETCHES => false,
)
);
# Database instance of Aura Sql
$di->set(
'database',
function () use ($di) {
return $di->newInstance('Aura\Sql\ExtendedPdo');
}
);
The above results in the attributes not being set.
However, if the below is used instead:
$di->set(
'database',
function () use ($di) {
return new Aura\Sql\ExtendedPdo($di->params['Aura\Sql\ExtendedPdo']['dsn'],
$di->params['Aura\Sql\ExtendedPdo']['username'],
$di->params['Aura\Sql\ExtendedPdo']['password'],
array(),
array(
Aura\Sql\ExtendedPdo::ATTR_EMULATE_PREPARES => false,
Aura\Sql\ExtendedPdo::ATTR_STRINGIFY_FETCHES => false,
));
}
);
Then it works perfectly.
How could I get it to work correctly with the first example?
Alternatively, perhaps making the arrays named parameters like username, password and dsn are?
I'm porting you parser to my library for sphinxsearch query builder and noticed that queries like 'SELECT :foo'
not processed properly because placeholder on the end of the string.
I am not sure whether this is related with https://bugs.php.net/bug.php?id=47960
I have not checked whether we are using something like $stmt->bindParam(':max', $max, PDO::PARAM_INT);
as in the http://www.php.net/manual/en/book.pdo.php#96264
$text = 'SELECT * FROM posts LIMIT :lim';
$posts = $sql->fetchAll($text,
[
'lim' => $offset,
]
);
But returns failure . So bind seems not working.
I know this will be working, but I didn't get it working like this,
$cols['lastlogin'] = 'NOW()';
$cond = 'id = :id';
$bind['id'] = $row['id'];
$this->connection->update($this->table, $cols, $cond, $bind);
but it works with the newUpdate()
on set('lastlogin', 'NOW()')
Is this a typo?
Current line | Fixed? |
---|---|
$bind = array('foo' => 'bar', 'baz' => 'dib'); | $bind = array('foo' => 'baz', 'bar' => 'dib'); |
If the first parameter of a query with numbered placeholders is null, you will get an undefined offset in Rebuilder.php on line 220. The standard PDO library allows the first parameter to be null.
The following code generates the error.
$pdo->perform('SELECT * FROM test WHERE column_one = ?', [null]);
Currently, fetch*(), perform(), etc. assume a 1-indexed array when dealing with non-named (question-mark) params. The base PDO execute() uses a 0-indexed array. This results in inconsistent behavior between ExtendedPDO and PDO, forcing workarounds like
https://gist.github.com/iansltx/6d0181c835e5afbf2965#file-gistfile1-php-L6
(I feed those results into fetchOne() or fetchAll() depending on whether I'm counting or retrieving results).
It sounds like the inconsistency here is because http://www.php.net//manual/en/pdostatement.bindvalue.php uses a 1-indexed array and http://www.php.net/manual/en/pdostatement.execute.php uses a 0-indexed array, when interacting with non-named params. php.net docs need an edit to clarify that this is what's going on on the execute site, but the editor isn't playing nice with me right now.
Fixing this issue will be backwards incompatible with the hack posted above, though for folks who've used 1-indexed as input instead and have a properly sized array it'd be possible to keep their implementations working.
When cols are empty we are not adding *
. Does that helps to resolve the error in query ? ( SELECT FROM
)
Line 119 adding an else does that fix the issue ?
// add columns
if ($this->cols) {
$text .= $line . implode($csep, $this->cols) . PHP_EOL;
} else {
$text .= ' * ' . PHP_EOL;
}
Hi Paul,
I was looking into the fetchAll
method of the extended PDO and the fetchAll method of the normal PDO.
/**
*
* Fetches a sequential array of rows from the database; the rows
* are represented as associative arrays.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param callable $callable A callable to be applied to each of the rows
* to be returned.
*
* @return array
*
*/
public function fetchAll($statement, array $values = array(), $callable = null)
{
$this->bindValues($values);
$sth = $this->query($statement);
$data = $sth->fetchAll(self::FETCH_ASSOC);
if ($callable) {
foreach ($data as $key => $row) {
$data[$key] = call_user_func($callable, $row);
}
}
return $data;
}
http://www.php.net/manual/en/pdostatement.fetchall.php
public array PDOStatement::fetchAll ([ int $fetch_style [, mixed $fetch_argument [, array $ctor_args = array() ]]] )
You can see in ExtendedPdo
they will not get a chance to make the class bind to the return values. Will it be good, if we can make optional arguments as that of fetchAll
method signature ?
http://php.net/manual/en/pdostatement.setfetchmode.php
This is with respect to my recent experiment on fetchMode.
<?php
require __DIR__ . '/vendor/autoload.php';
class PollChoice
{
protected $id;
protected $poll_id;
protected $choice_text;
protected $votes;
public function __get($key)
{
return $this->{$key};
}
public function __set($key, $value)
{
$this->{$key} = $value;
}
}
use Aura\Sql\ExtendedPdo;
use Aura\Sql\Profiler;
$pdo = new ExtendedPdo(
'mysql:host=localhost;dbname=dbname',
'username',
'password',
array(), // driver options as key-value pairs
array() // attributes as key-value pairs
);
$sql = 'SELECT * FROM polls_choice WHERE id IN (:id) AND poll_id = :poll_id';
$bind = array(
'id' => array('1', '2'),
'poll_id' => '1',
);
$pdo->bindValues($bind);
$results = $pdo->query($sql, PDO::FETCH_CLASS, 'PollChoice');
foreach ($results as $result) {
echo "Query Result : " . $result->choice_text . PHP_EOL;
}
Let me know your thoughts.
My first instinct was to try to build a PdoFactory with various strategies to allow for different ways to build and inject the PDO object. After about two hours of trying things out, it was a bust, mostly because the ConnectionLocator really depends on the dsn-array. Just as well; it would have been a BC break anyway.
So here's another option, using setPdo() as both of you have suggested; see the diff here:
https://gist.github.com/4199388
This retains the $dsn=null
default param, which allows you to construct a connection object without PDO, and then use setPdo() immediately after.
I considered the idea of conditional checks to see if a PDO object has been injected via constructor, but I think It's just not very clean (even though it is flexible). So that's not here.
Note that the connection factory still requires the adapter name, so that it knows what kind of connection to create (it allows a null $dsn
as well).
What do we think about this?
-- pmj
How I can catch thrown excepion, e.g related to MySQL server being offline?
I'm experiencing this problem only with this package. It looks like exceptions thrown in ExtendedPdo aren't being catched? or i'm just doing it wrong?
I tried to wrap it inside try...catch block at the very beginning of my php scripts.
try {
$dbh = new Sql\ExtendedPdo(
'mysql:host='.GameConfig::$DB_HOST.';dbname='.GameConfig::$DB_DATABASE.'',
GameConfig::$DB_USER,
GameConfig::$DB_PASSWORD
);
} catch(PDOException $e) {
//do stuff
}
But it still shows me this error:
*Uncaught* exception 'PDOException' with message 'SQLSTATE[HY000] [2002] Connection refused' in /home/grayfox/www/extel/vendor/aura/sql/src/ExtendedPdo.php:218 Stack trace: #0
The documentation should discuss the major change points between 1.x and 2.x, namely the separation of the ORM concerns into a separate library.
Hey @pmjones ,
I am not good in writing about Mappers , so I prefer you itself to write for Aura.Sql as always .
Sorry for the trouble.
The documentation says:
// fetchAssoc() returns an associative array of all rows where the key is the
// first column, and the row arrays are keyed on the column names
$result = $pdo->fetchAssoc($stm, $bind);
However, only one row is returned, nominally the last row, rather than "all rows". Incorrect docs? Bug in code?
Hi
It's not really an issue, just a simple question.
I'm little surprised that there's no fetchNum() method. Do you have any plans of implementing it?
$strsql_w = "insert into rel_rev_file
(revid, fileid, addtime, modtime) values (333, 9, '2012-04-24 16:00:00', '00-00-00 00:00:00')";
$sql->fetchAll($strsql_w);
it do insert record into table rel_rev_file and throw an exception like this:
[25-Apr-2012 07:41:17 UTC] PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY0
00]: General error' in /home/guweigang/local/httpd/htdocs/cr-api/application/library/Crd/Sql/Adapter/Abst
ract.php:433
Stack trace:
#0 /home/guweigang/local/httpd/htdocs/cr-api/application/library/Crd/Sql/Adapter/Abstract.php(433): PDOSt
atement->fetchAll(2)
#1 /home/guweigang/local/httpd/htdocs/cr-api/application/controllers/Index.php(54): Crd_Sql_Adapter_Abstr
act->fetchAll('insert into `re...')
#2 [internal function]: IndexController->indexAction()
#3 /home/guweigang/local/httpd/htdocs/cr-api/index.php(7): Ap_Application->run()
#4 {main}
thrown in /home/guweigang/local/httpd/htdocs/cr-api/application/library/Crd/Sql/Adapter/Abstract.php on
line 433
How do you make a LIKE statement ?
SELECT * FROM table WHERE {$column} LIKE :{$column}
Assume you are trying to execute the following SQL query.
SELECT id
FROM users
WHERE created_at >= ':createdDate 00:00:00'::TIMESTAMP
AND created_at <= ':createdDate 23:59:59'::TIMESTAMP
The ::type
syntax, in Postgres, casts the value to that type
(above we are converting the string to a TIMESTAMP
). Now, lets try to execute the query and bind a value:
$conn->fetchAll($sql, ['createdDate' => '2015/12/01']);
This gives you an error: "Undefined offset: 0" in src/Rebuilder.php
on line 247.
It'd be nice in the README.md to mention that there are likely to be multiple profile entries in the array, numerically indexed, even if only one query has been issued. In my recent experience, I get 2 such entries for my query, both effectively identical, except the second entry actually contains the bind values and the first does not (empty array).
When using Twig and trying to access a Column property, it gives an error saying that the method doesn't exists.
For example, if you try to access the column name property:
col.name
It halts saying the method name() doesn't exists.
It can be fixed by just putting an __isset($key) checking if $key is a class property.
Thanks!
For instance MySQL supports
UPDATE tbl
SET a = 1
WHERE b = 2
LIMIT 5
Limit part is non-standard, but I need it in \Aura\Sql\Query\Update.
Same with SELECT SQL_CALC_FOUND_ROWS etc.
Can I send pull requests regarding those features or Aura\Query objects should support only standard syntax?
It would be nice to have a class that automatically formats and outputs the profiler data for easier reading.
Hi Paul,
I was trying something lately. The where was having a p.id = :p.id
, but getting error like
PHP Notice: Undefined index: p in /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php on line 1037
PHP Stack trace:
PHP 1. {main}() /var/www/github.com/harikt/experiments/closure-class.php:0
PHP 2. PostRepository->fetchOne() /var/www/github.com/harikt/experiments/closure-class.php:255
PHP 3. Aura\Sql\ExtendedPdo->fetchOne() /var/www/github.com/harikt/experiments/closure-class.php:124
PHP 4. Aura\Sql\ExtendedPdo->perform() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:451
PHP 5. Aura\Sql\ExtendedPdo->prepareWithValues() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:628
PHP 6. Aura\Sql\ExtendedPdo->rebuild() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:864
PHP 7. Aura\Sql\ExtendedPdo->prepareValuePlaceholders() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:943
PHP 8. Aura\Sql\ExtendedPdo->prepareNamedPlaceholder() /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:976
PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'NULL.id' in 'where clause'' in /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php:630
Stack trace:
#0 /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php(630): PDOStatement->execute()
#1 /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php(451): Aura\Sql\ExtendedPdo->perform('SELECT\n a.na...', Array)
#2 /var/www/github.com/harikt/experiments/closure-class.php(124): Aura\Sql\ExtendedPdo->fetchOne('SELECT\n a.na...', Array)
#3 /var/www/github.com/harikt/experiments/closure-class.php(255): PostRepository->fetchOne()
#4 {main}
thrown in /var/www/github.com/harikt/experiments/vendor/aura/sql/src/ExtendedPdo.php on line 630
I had a look into the problem and it is happening at rebuild
for ($i = 0; $i <= $k; $i += 3) {
// split into subparts by ":name" and "?"
$subs = preg_split(
"/(:[a-zA-Z_][a-zA-Z0-9_]*)|(\?)/m",
$parts[$i],
-1,
PREG_SPLIT_DELIM_CAPTURE
);
// check subparts to convert bound arrays to quoted CSV strings
$subs = $this->prepareValuePlaceholders($subs, $bind);
// reassemble
$parts[$i] = implode('', $subs);
}
The full stuff what I am experimenting is
<?php
require __DIR__ . '/vendor/autoload.php';
use Aura\SqlQuery\QueryFactory;
use Aura\Sql\ExtendedPdo;
use Aura\Sql\Profiler;
class Post
{
private $id;
private $title;
private $body;
private $author;
public function __construct($id, $title, $body, $author)
{
$this->id = $id;
$this->title = $title;
$this->body = $body;
$this->author = $author;
}
public function id()
{
return $this->id;
}
public function title()
{
return $this->title;
}
public function body()
{
return $this->body;
}
public function author()
{
if ($this->author instanceof Closure) {
$author = $this->author;
$this->author = $author();
}
return $this->author;
}
}
class Author
{
private $name;
private $id;
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
public function name()
{
return $this->name;
}
public function __tostring()
{
return $this->name();
}
}
class PostRepository
{
private $author_repository;
private $table = 'posts';
private $select;
private $hydrate;
private $connection;
private $query_factory;
public function __construct($connection, $query_factory, $author_repository)
{
$this->connection = $connection;
$this->query_factory = $query_factory;
$this->author_repository = $author_repository;
}
public function select($hydrate = false)
{
$this->hydrate = $hydrate;
$this->select = $this->query_factory->newSelect();
$this->select->from($this->table . ' as p');
if ($hydrate) {
$this->select->cols(array('a.name', 'p.title', 'p.id', 'p.body', 'p.author_id'));
$this->select->join(
'LEFT',
$this->author_repository->getTable() . ' as a',
'p.author_id = a.id'
);
} else {
$this->select->cols(array('p.title', 'p.id', 'p.body', 'p.author_id'));
}
return $this;
}
public function where($where = array())
{
if (! $this->select) {
throw Exception("No query to execute");
}
foreach ($where as $col => $value) {
if (is_array($value)) {
$this->select->where("$col IN :$col");
} else {
$this->select->where("$col = :$col");
}
$this->select->bindValue($col, $value);
}
return $this;
}
public function fetchOne()
{
if (! $this->select) {
throw Exception("No query to execute");
}
$result = $this->connection->fetchOne($this->select->__toString(), $this->select->getBindValues());
return $this->createObject($result);
}
public function fetchAll()
{
if (! $this->select) {
throw Exception("No query to execute");
}
$result = $this->connection->fetchAll($this->select->__toString(), $this->select->getBindValues());
return $this->createObjects($result);
}
protected function createObjects($results)
{
$objects = array();
foreach ($results as $result) {
$objects[] = $this->createObject($result);
}
return $objects;
}
protected function createObject($result)
{
if ($this->hydrate) {
$author = new Author($result['author_id'], $result['name']);
} else {
$author_id = $result['author_id'];
$author = function () use ($author_id) {
return $this->author_repository->find($author_id)->fetchOne();
};
}
return new Post($result['id'], $result['title'], $result['body'], $author);
}
public function getTable()
{
return $this->table;
}
}
class AuthorRepository
{
private $table = 'authors';
private $select;
private $connection;
private $query_factory;
public function __construct($connection, $query_factory)
{
$this->connection = $connection;
$this->query_factory = $query_factory;
}
public function select($id)
{
$this->select = $this->query_factory->newSelect();
$this->select->from($this->table)
->cols(array('id', 'name'));
return $this;
}
public function where($where = array())
{
if (! $this->select) {
throw Exception("No query to execute");
}
foreach ($where as $col => $value) {
if (is_array($value)) {
$this->select->where("$col IN :$col");
} else {
$this->select->where("$col = :$col");
}
$this->select->bindValue($col, $value);
}
return $this;
}
public function fetchOne()
{
if (! $this->select) {
throw Exception("No query to execute");
}
$result = $this->connection->fetchOne($this->select->__toString(), $this->select->getBindValues());
return $this->createObject($result);
}
public function fetchAll()
{
if (! $this->select) {
throw Exception("No query to execute");
}
$result = $this->connection->fetchAll($this->select->__toString(), $this->select->getBindValues());
return $this->createObjects($result);
}
protected function createObjects($results)
{
$objects = array();
foreach ($results as $result) {
$objects[] = $this->createObject($result);
}
return $objects;
}
protected function createObject($result)
{
return new Author($result['id'], $result['name']);
}
public function getTable()
{
return $this->table;
}
}
$query_factory = new QueryFactory('mysql');
$pdo = new ExtendedPdo(
'mysql:host=localhost;dbname=closure',
'root',
'mysqlroot',
array(), // driver options as key-value pairs
array() // attributes as key-value pairs
);
$pdo->setProfiler(new Profiler);
$pdo->getProfiler()->setActive(true);
$author_repository = new AuthorRepository($pdo, $query_factory);
$post_repository = new PostRepository($pdo, $query_factory, $author_repository);
$post = $post_repository->select(true)
->where(array('p.id' => 1))
->fetchOne();
echo "Post id " . $post->id() . PHP_EOL;
echo "Post title " . $post->title() . PHP_EOL;
echo "Post content " . $post->body() . PHP_EOL;
echo "Post author " . $post->author() . PHP_EOL;
// no joins
$post = $post_repository->select()
->where(array('p.id' => 2))
->fetchOne();
echo PHP_EOL . "Post id " . $post->id() . PHP_EOL;
echo "Post title " . $post->title() . PHP_EOL;
echo "Post content " . $post->body() . PHP_EOL;
echo "Post author " . $post->author() . PHP_EOL;
$post = $post_repository->select()
->fetchAll();
$profiles = $pdo->getProfiler()->getProfiles();
foreach ($profiles as $profile) {
echo PHP_EOL . "Function : " . $profile['function'] . PHP_EOL;
echo PHP_EOL . "Statement : " . PHP_EOL . $profile['statement'] . PHP_EOL;
echo PHP_EOL . "Bind values : " . PHP_EOL . var_export($profile['bind_values'], true) . PHP_EOL;
}
I don't know whether I should call it as repository or something else.
Interested to hear your thoughts. This is slightly modified than the one in gist https://gist.github.com/harikt/26d244f87db8061eff97 .
Somethings in mind are may be use a __call
which can handle the select query instance , so can use the limit etc can be called or may be provide an abstract class.
Looking forward to hear your thoughts.
Here's a custom extension I'm using:
class Connection extends ExtendedPdo
{
/**
* @var int number of nested calls to transact()
* @see transact()
*/
private $transaction_level = 0;
/**
* @var bool net result of nested calls to transact()
* @see transact()
*/
private $transaction_result;
/**
* @param callable $func function to call; must return TRUE to commit or FALSE to roll back
*
* @return bool TRUE on success (committed) or FALSE on failure (rolled back)
*
* @throws InvalidArgumentException if the provided argument is not a callable function
* @throws LogicException if an unhandled Exception occurs while calling the provided function
* @throws UnexpectedValueException if the provided function does not return TRUE or FALSE
*/
public function transact($func)
{
if (! is_callable($func)) {
throw new InvalidArgumentException("invalid argument: \$func is not callable");
}
if ($this->transaction_level === 0) {
// starting a new stack of transactions - assume success:
$this->beginTransaction();
$this->transaction_result = true;
}
$this->transaction_level += 1;
$commit = false; // assume roll back
try {
$commit = call_user_func($func);
} catch (Exception $exception) {
$commit = false;
}
$this->transaction_result = (bool) $commit && $this->transaction_result;
$this->transaction_level -= 1;
if ($this->transaction_level === 0 && $this->transaction_result === true) {
$this->commit();
return true; // the net transaction is a success!
}
if ($this->transaction_result === false) {
$this->rollBack();
}
if (isset($exception)) {
// re-throw an unhandled Exception:
throw new LogicException("unhandled Exception during transaction", 0, $exception);
}
if (! is_bool($commit)) {
throw new UnexpectedValueException("\$func must return TRUE (to commit) or FALSE (to roll back)");
}
return $this->transaction_result;
}
}
This new method will track sub-transactions, so you can do something like this:
$connection->transact(function() {
return true; // commits the transaction (false would roll back the transaction)
});
This eliminates the need to synchronize calls to beginTransaction()
and commit()
or rollBack()
and instead performs transactions "transactionally", heh ;-)
An important point here, is that nested calls to transact()
will be counted, and if any nested call returns false (or throws an exception) the entire transaction will correctly roll back.
If it were up to me, there wouldn't be any other way to perform transactions - having to call begin/commit/rollback individually is semantically "broken", since it does resemble a transaction or guarantee (the only correct) execution order.
This is fully unit-tested and works. Let me know if you're interested in integrating this?
Hey @pmjones ,
Do you think we can release 2.0.1 1.0.1
or 2.1 1.1
for the Aura.Sql with the new features?
Hi ,
I just had a thought that when we do via PDO , there are possibilities that the query can fail .
And was looking through some of the articles like
http://stackoverflow.com/questions/2411182/how-to-debug-pdo-database-queries
http://daveyshafik.com/archives/605-debugging-pdo-prepared-statements.html
http://php.net/manual/en/pdostatement.debugdumpparams.php
Do you have any thoughts on this to make it easy for developers to track the issue ?
Using PostgreSQL at least three different cases of SQL keywords are considered parameters.
First is the cast operator "::"
$pdo->perform('SELECT 1::TEXT', ['someparameter' => 'b']);
This one can be avoided using the verbose way to cast but the short syntax looks better.
The problem comes from https://github.com/auraphp/Aura.Sql/blob/2.x/src/Rebuilder.php#L159 for which a quick fix could be "/[^:]+(:[a-zA-Z_][a-zA-Z0-9_]*)|(\?)/m"
Second one is using colons in comments:
$pdo->perform(' -- :should not be replaced SELECT 1', ['someparameter' => 'b']);
$pdo->perform(" /* :should not be replaced */ SELECT '2'", ['someparameter' => 'b']);
The third problem comes with the new jsonb operators: ?, ?| and ?&
$pdo->perform('SELECT CAST(\'{"a":1, "b": 2, "c": 3}\' AS JSONB) ? \'b\'', ['someparameter' => 'b']);
This third one can hardly be fixed by the user.
I think the best way to handle those cases and I'm sure a lot more with other RDBMS would be to not use regular expressions to parse the query but a simple state machine. And have one per driver.
Hi @pmjones ,
I was trying to checkout to develop branch. But I was getting error: pathspec 'develop' did not match any file(s) known to git.
. So I removed my repo and forked again and the same happening :( .
Is it may be due to wrong way done by release script ?
For this reason probably no more bug fixes by others or PR will come. We want to fix ASAP .
hari@hari-Vostro1510:/media/Linux/aurasystem/package$ git clone [email protected]:harikt/Aura.Sql.git
Cloning into Aura.Sql...
remote: Counting objects: 1267, done.
remote: Compressing objects: 100% (594/594), done.
remote: Total 1267 (delta 588), reused 1207 (delta 531)
Receiving objects: 100% (1267/1267), 910.96 KiB | 47 KiB/s, done.
Resolving deltas: 100% (588/588), done.
hari@hari-Vostro1510:/media/Linux/aurasystem/package$ cd Aura.Sql/
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git remote add upstream https://github.com/auraphp/Aura.Sql
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git fetch upstream
From https://github.com/auraphp/Aura.Sql
* [new branch] 1.0.0-beta2 -> upstream/1.0.0-beta2
* [new branch] develop -> upstream/develop
* [new branch] gh-pages -> upstream/gh-pages
* [new branch] master -> upstream/master
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git checkout develop
error: pathspec 'develop' did not match any file(s) known to git.
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git checkout upstream/develop
Note: checking out 'upstream/develop'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at 47b3e06... update composer.json
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git status
# Not currently on any branch.
nothing to commit (working directory clean)
hari@hari-Vostro1510:/media/Linux/aurasystem/package/Aura.Sql$ git branch
* (no branch)
master
I wished for the v1, we don't have asked for the gateway_name to insert the object. Rather get the class name from the object and use it as gateway_name by default.
Currently as the gateway_name is the first parameter for UnitOfWork.php we cannot alter it also.
$this->unit_of_work->insert('Entity\Article', $source);
For the consideration of v2 mappers.
In case of an empty array being bound inside a IN(?)
, it will generate WHERE name IN()
, which is a syntax error
I was looking into the method lastInsertId
,
Postgres have a different way and I am not sure MSSQL has something different too for I don't remember or used it recently .
I feel the method public function lastInsertId($table = '' , $col = '' )
can be moved to AbstractDriver.php
, so we don't repeat the same method in Mysql.php
https://github.com/auraphp/Aura.Sql/blob/master/src/Aura/Sql/Driver/Mysql.php#L162 and Sqlite.php
https://github.com/auraphp/Aura.Sql/blob/master/src/Aura/Sql/Driver/Sqlite.php#L228 .
public function lastInsertId($table = '' , $col = '' )
{
$pdo = $this->getPdo();
return $pdo->lastInsertId();
}
And we can override it from Pgsql.php
https://github.com/auraphp/Aura.Sql/blob/master/src/Aura/Sql/Driver/Pgsql.php#L234 in the same fashion. Do you think its a good way ?
Beta3 API and Doc Missing at http://auraphp.github.com/Aura.Sql/
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.