Using Buffer for Resultset::rewind() after Resultset::next() called in Zend\Db
In Zend\Db, there is Resultset
which can be used as result of db records as instanceof PHP Iterator. For example, we have the following table structure:
CREATE TABLE test ( id serial NOT NULL PRIMARY KEY, name character varying(255) );
and we have the following data:
To build resultset, we can use the following code:
include './vendor/autoload.php'; use Zend\Db\Adapter\Adapter; use Zend\Db\TableGateway\TableGateway; use Zend\Db\ResultSet\ResultSet; $adapter = new Adapter([ 'username' => 'developer', 'password' => '123456', 'driver' => 'pdo_pgsql', 'database' => 'learn', 'host' => 'localhost', ]); $resultSetPrototype = new ResultSet( null, new ArrayObject([], ArrayObject::ARRAY_AS_PROPS) ); $tableGateway = new TableGateway( 'test', $adapter, null, $resultSetPrototype ); $select = $tableGateway->getSql()->select(); $resultSet = $tableGateway->selectWith($select);
On getting the data, we can go to specific record position by using next()
, for example: we want to get the 2nd record of selected data, we can use the following code:
$resultSet->current(); $resultSet->next(); var_dump($resultSet->current());
and we will get the following data:
class ArrayObject#16 (1) { private $storage => array(2) { 'id' => int(2) 'name' => string(6) "test 2" } }
However, when we need to back to first position, we can’t just use rewind()
as follow:
$resultSet->rewind(); var_dump($resultSet->current());
Above code will result wrong data, which is a next record data:
class ArrayObject#16 (1) { private $storage => array(2) { 'id' => int(3) 'name' => string(6) "test 3" } }
To make that work, we need to use buffer()
method early after result set created, so, the code will need to be:
$select = $tableGateway->getSql()->select(); $resultSet = $tableGateway->selectWith($select); $resultSet->buffer(); $resultSet->current(); // ensure hit 1st record first $resultSet->next(); // next position var_dump($resultSet->current()); // get 2nd record $resultSet->rewind(); // back to position 0 var_dump($resultSet->current()); // get 1st record again
That will show the correct data:
# second record by call of next() class ArrayObject#16 (1) { private $storage => array(2) { 'id' => int(2) 'name' => string(6) "test 2" } } # first record after call of rewind() class ArrayObject#16 (1) { private $storage => array(2) { 'id' => int(1) 'name' => string(6) "test 1" } }
Using conflict in composer.json for BC Break handling on Optional feature
You may have a PHP library that can work with multiple project skeletons with optional feature, so the dependencies are not required. For example, your composer.json library looks like:
{ "name": "your/library", "require": { "php": "^7.1", }, "require-dev": { "foo/bar": "^2.0", "phpunit/phpunit": "^7.0" }, "suggest": { "foo/bar": "^2.0 usage via Your\\Library\\Adapter\\Foo\\Bar adapter" } }
The “foo/bar” library is an optional library which used in “your/library” that consume it, collected in the require-dev above for testing purpose.
The “foo/bar” library has class “Foo\Bar\Way”. There are 2 versions of “foo/bar” lib, which version 1 and version 2 have different signature as follow:
a. “foo/bar” version 1 of “Foo\Bar\Way”
namespace Foo\Bar; class Way { public function execute(string $a, string $b) { // ... } }
b. “foo/bar” version 2 of “Foo\Bar\Way”
namespace Foo\Bar; use stdClass; class Way { public function execute(stdClass $std) { // ... } }
In above code, if current user are using foo/bar:^1.0, the user code will be break when user upgrade “your/library” to version 2.0. So, to avoid that, you can restrict it via “conflict”, as follow:
{ "name": "your/library", "require": { "php": "^7.1", }, "require-dev": { "foo/bar": "^2.0", "phpunit/phpunit": "^7.0" }, "suggest": { "foo/bar": "^2.0 usage via Your\\Library\\Adapter\\Foo\\Bar adapter" }, "conflict": { "foo/bar": "<2.0" } }
Now, you can tag “your/library” as “2.0” and user won’t be allowed to install new tagged library if they still uses “foo/bar”:”^1.0″ in existing project, That’s it!
Replace Hard Dependency Class with New Simulation Class via “replace” and “classmap” in Composer Configuration
If we are using 3rd party library that managed by composer, which has hard dependency that we don’t want to use, for example, at the following use case:
- “realexpayments/rxp-remote-php” library require a “apache/log4php” dependency for apache “Logger” class usage.
- we don’t want to log everything via “apache/log4php”‘s Logger, for whatever reason.
The one of the solutions for that is by using “replace” and “classmap” configuration in our composer.json
. First, we need to prepare of the class to simulate the Logger
class, for example, we have it in src/App/Apache/Logger.php
:
<?php // src/App/Apache/Logger.php class Logger { function debug(...$args) {} function info(...$args) {} function trace(...$args) {} function warn(...$args) {} function error(...$args) {} function fatal(...$args) {} public static function configure(...$args) {} public static function getLogger() { return new self(); } }
Yes, above class doesn’t do anything, for silent action when Logger::{themethod()}
called in realexpayments/rxp-remote-php
library classes.
Next, we can register it to our composer.json:
{ "require": { // ... "realexpayments/rxp-remote-php": "^1.2" // ... }, "replace": { "apache/log4php": "^2.3.0" }, "autoload": { "psr-4": { "App\\": "src/App/", }, "classmap": [ "src/App/Apache/Logger.php" ] } }
In above configuration, the replace
of “apache/log4php” doesn’t has replacement library in ‘require’ part will make the dependency removed entirely as we don’t want to use it anymore, and by the classmap
configuration, we have new redefined of the Logger class as simulation of “apache/log4php” Logger class.
Last step, we can run:
➜ composer update
That’s it!
Using “no-api” key on Github Forked Repository in Composer repositories configuration for Travis Build
So, you’re doing a pull request, you want the feature to be merged so bad to public repository, but for whatever reason, it is not merged yet. You can register your forked repository under composer “repositories” config:
{ "require": { "awesome/library": "dev-my-fork-awesome-feature" }, "repositories" : [ { "type" : "vcs", "url" : "https://github.com/yourgithubuser/library" } ] }
That will work for your local dev and your server, but unfortunatelly, that won’t work on Travis! We will get the following error:
Failed to clone the git@github.com:yourgithubuser/library.git repository, try running in interactive mode so that you can enter your GitHub credentials [RuntimeException] Failed to execute git clone --mirror 'git@github.com:yourgithubuser/library.git' '/home/travis/.composer/cache/vcs/git-github.com-yourgithubuser-library.git/'
To make that work, we will need the “no-api” key in your composer.json under the per-repository inside “repositories”, as follow:
// ... { "type" : "vcs", "url" : "https://github.com/yourgithubuser/library", "no-api": true } // ...
Now, our composer.json will look like this:
{ "require": { "awesome/library": "dev-my-fork-awesome-feature" }, "repositories" : [ { "type" : "vcs", "url" : "https://github.com/yourgithubuser/library", "no-api": true } ] }
That’s it!
Introduce IsDeprecated: PHP7+ Helper for E_USER_DEPRECATED and E_DEPRECATED Detection
Before I continue, allow me to give you my suggestions:
Live with deprecated function is bad, you may can’t upgrade to newer PHP version or newer library as the function that was deprecated already removed in next major/minor version.
Use same environment or at least same major and minor version between local dev and production environment when possible, your life will be easier.
When reality is not always what you want
You may found a a reality when you work at different version of PHP version or library that rely on specific function which already deprecated in some environment, the IsDeprecated may be solution at that time that can verify E_USER_DEPRECATED
or E_DEPRECATED
trigger error. It utilize jeremeamia/FunctionParser for user defined function check, and zendframework/zend-stdlib’s ErrorHandler for E_DEPRECATED
function check.
When you found the passed function is deprecated, you can use alternative function.
Installation
This helper can be installed via composer:
composer require samsonasik/is-deprecated
This helper have features:
1. Detect E_USER_DEPRECATED
- At independent function
- At function inside class
You can use IsDeprecated\isDeprecatedUser
function with signature:
/** * @param string|array $function the "functionName" or ["ClassName" or object, "functionName"] or "ClassName::functionName" * @throws InvalidArgumentException when trigger_error found but the error is not E_USER_DEPRECATED * @throws InvalidArgumentException when trigger_error and E_USER_DEPRECATED found but misplaced * @return bool */ function isDeprecatedUser($function): bool
Note: when trigger_error E_USER_DEPRECATED inside condition, you need to use
actual
call with signature:
/** * @param callable $function callable function * @return bool */ function isDeprecatedWithActualCall(callable $function)
1a. Independent Function
The usage is like the following:
use function IsDeprecated\isDeprecatedUser; function foo() { trigger_error('this method has been deprecated.', E_USER_DEPRECATED); } if (isDeprecatedUser('foo')) { // apply alternative/new function to call... } else { foo(); }
1.b Function Inside Class
The usage is like the following:
use function IsDeprecated\isDeprecatedUser; class AClass { public function foo() { trigger_error('this method has been deprecated.', E_USER_DEPRECATED); } // check inside with $this public function execute() { if (isDeprecatedUser([$this, 'foo'])) { // apply alternative/new function to call... return; } $this->foo(); } } // you may call after instantiation $object = new \AClass(); if (isDeprecatedUser([$object, 'foo'])) { // apply alternative/new function to call... } else { $object->foo(); } // or if (isDeprecatedUser(['AClass', 'foo'])) { // apply alternative/new function to call... } else { (new \AClass())->foo(); }
2. Detect E_DEPRECATED
E_DEPRECATED can be triggered on Core PHP function call.
You can use IsDeprecated\isDeprecatedCore
function with signature:
/** * @param callable $function callable function * @return bool */ function isDeprecatedCore(callable $function): bool
The usage is like the following:
use function IsDeprecated\isDeprecatedCore; $function = function () { mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); }; if (isDeprecatedCore($function)) { // alternative function, eg: openssl ... } else { $function(); }
Limitation
For Core PHP Functions or user function with condition (T_IF or T_SWITCH token), the function passed actually need to be called. It ensure that we don’t get error during call deprecated function, and we can use alternative function if the isDeprecatedCore()
returns true with call of isDeprecatedWithActualCall
.
You want to use it? You can check my repository https://github.com/samsonasik/IsDeprecated
That’s it ;).
3 comments