After fundamental changes on my project system architecture, I find myself in a situation where I would need to create "fake" implementation in order to test some functionality that used to be public like the following:
/**
* Display the template linked to the page.
*
* #param $newSmarty Smarty object to use to display the template.
*
* #param $parameters associative Array containing the values to pass to the template.
* The key is the name of the variable in the template and the value is the value of the variable.
*
* #param $account child class in the AccountManager hierarchy
*
* #param $partialview String name of the partial view we are working on
*/
protected function displayPageTemplateSmarty(Smarty &$newSmarty, array $parameters = array(), AccountManager $account = NULL, string $partialview = "")
{
$this->smarty = $newSmarty;
if (is_file(
realpath(dirname(__FILE__)) . "/../../" .
Session::getInstance()->getCurrentDomain() . "/view/" . (
!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
)
)) {
$this->smarty->assign(
'activeLanguage',
Session::getInstance()->getActiveLanguage()
);
$this->smarty->assign('domain', Session::getInstance()->getCurrentDomain());
$this->smarty->assign(
'languages',
Languagecontroller::$supportedLanguages
);
$this->smarty->assign(
'title',
Languagecontroller::getFieldTranslation('PAGE_TITLE', '')
);
$this->smarty->assign_by_ref('PageController', $this);
$htmlTagBuilder = HTMLTagBuilder::getInstance();
$languageController = LanguageController::getInstance();
$this->smarty->assign_by_ref('htmlTagBuilder', $htmlTagBuilder);
$this->smarty->assign_by_ref('languageController', $languageController);
if (!is_null($account)) {
$this->smarty->assign_by_ref('userAccount', $account);
}
if (!is_null($this->menuGenerator)) {
$this->smarty->assign_by_ref('menuGenerator', $this->menuGenerator);
}
foreach ($parameters as $key => $value) {
$this->smarty->assign($key, $value);
}
$this->smarty->display((!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
));
}
}
In this case, the PageController class used to be called directly in controllers, but is now an abstract class extended by the controllers and my unit tests can no longer access the method.
I also have methods like this one in my new session wrapper class that can only be used in very specific context and for which I really need to create fake page implementation to test them.
/**
* Add or update an entry to the page session array.
*
* Note: can only be updated by the PageController.
*
* #param $key String Key in the session array.
* Will not be added if the key is not a string.
*
* #param $value The value to be added to the session array.
*
* #return Boolean
*/
public function updatePageSession(string $key, $value)
{
$trace = debug_backtrace();
$updated = false;
if (isset($trace[1]) and
isset($trace[1]['class']) and
$trace[1]['class'] === 'PageController'
) {
$this->pageSession[$key] = $value;
$updated = true;
}
return $updated;
}
Even though I read a few article, it is still quite unclear in my mind if those fake classes should be considered as "stub" or a "mock" (or even "fake", "dummy" and so on).
I really need to use the proper terminology since my boss is expecting me (in a close future) to delegate most of my workload with oversea developers.
How would you call those fake class implementation created solely for testing purpose in order to be self-explanatory?
Gerard Meszaros explains the terminology of dummies, stubs, spies, mocks, and fakes here.
You can find examples from the PHP world here.
Related
I'm creating a php class which is getting slightly out of hand the deeper it gets.
Here's an example:
unset($this->file[$key]->inspect->formats);
unset($this->file[$key]->inspect->tags);
unset($this->file[$key]->inspect->chapters);
unset($this->file[$key]->inspect->annotations);
unset($this->file[$key]->inspect->automatic_captions);
unset($this->file[$key]->inspect->subtitles);
$this->file[$key]->inspect->name = trim($this->file[$key]->inspect->name);
$this->file[$key]->inspect->artist = trim($this->file[$key]->inspect->artist);
Instead of writing $this->file[$key]->inspect for every single variable I want to use is there a way I can set a variable e.g $inspect to take this place?
So that when I write $inspect->subtitles it'll know what I really mean and affect the main $this->file[$key]->inspect->subtitles?
$inspect = &$this->file[$key]->inspect;
declare this. Now you can set your data like this
$inspect->formats = 'format';
$inspect->subs = 'subs';
// ...
adding the & you will affect the variable and not only a copy of this variable
here are explanations about references http://php.net/manual/en/language.references.whatare.php
One approach you could use is replicate the object_get() helper method from Laravel that will fetch elements based on dot notation.
/**
* Get an item from an object using "dot" notation.
*
* #param object $object
* #param string $key
* #param mixed $default
* #return mixed
*/
function object_get($object, $key, $default = null)
{
if (is_null($key) || trim($key) == '') return $object;
foreach (explode('.', $key) as $segment)
{
if ( ! is_object($object) || ! isset($object->{$segment}))
{
return value($default);
}
$object = $object->{$segment};
}
return $object;
}
$ob = new StdClass();
$ob->property->name->value = 'Lol';
echo object_get($ob, 'property.name.value');
Unfortunately there'd be a bit of extra implementation if the $object->property was an array like in your example.
Does anyone know whether there is a setting in PhpStorm that can trigger identifying variables generated using extract() function?
Example would be something like the following:
/**
* #return array
*/
protected function orderSet() : array
{
//...
return [
'colour' => $colour,
'green' => $green,
'orange' => $orange
];
}
/**
* #test
*/
public function returns_correct_attribute_names()
{
$params = $this->orderSet();
extract($params);
$this->assertEquals(
'Colour',
$colour->name
);
}
At the moment any variable that's been extracted in the test is highlighted (unrecognised), but perhaps there is a setting that can change this behaviour?
The solution that LazyOne offered actually works. However there is a bit more context you need in order to implement it.
To accurately inform PHPSTORM about the variables you want to declare the comment must be placed directly above extract() and not the parent function.
public function db(){
$db = new SQLite3('db/mysqlitedb.db');
$payments = $db->query('SELECT * FROM payments');
while ($pay = $payments->fetchArray()){
/**
* #var string $to_user
* #var string $from_user
* #var number $amount
*/
extract($pay);
if (isset($to_user, $from_user, $amount))
echo "TO: {$to_user}| FROM: {$from_user}| $ {$amount} \n";
};
}
This is a working sample from my code ( couldn't copy yours for some reason ).
You can see just before I use the extract() function I declare in the comment block above it the hidden variables and data types.
Bonus: if you intend to use extract, I highly recommend you use an isset to ensure the array you're parsing contains the fields you are expecting.
example in code above
I'm trying to debug some old code from CodeIgniter 2.2. When running some data thru Session, I noticed an unserialize error, Message: unserialize(): Error at offset 160 of 163 bytes. After doing some debugging and research, I found out it's a common backslash issue when unserializing data from Sessions.
The serialized data I'm using has objects of data with backslashes in them, which causes the errors to occur. I'm in need of a replacement that can handle standard class objects as well.
Could someone recommend a quick replacement for codeigniter's Session _serialize() and _unserialize() methods?
public function data_test() {
$input = array(
(object)array('name' => 'test2', 'desc' => 'bla bla ob/gyn'),
(object)array('name' => 'test2', 'desc' => 'bla bla ob\\gyn'),
);
var_dump($input);
$data = $this->_serialize($input);
var_dump($data);
$result = $this->_unserialize($data);
var_dump($result);
}
// --------------------------------------------------------------------
/**
* Serialize an array
*
* This function first converts any slashes found in the array to a temporary
* marker, so when it gets unserialized the slashes will be preserved
*
* #access private
* #param array
* #return string
*/
function _serialize($data) {
if (is_array($data)) {
foreach ($data as $key => $val) {
if (is_string($val)) {
$data[$key] = str_replace('\\', '{{slash}}', $val);
}
}
} else {
if (is_string($data)) {
$data = str_replace('\\', '{{slash}}', $data);
}
}
return serialize($data);
}
// --------------------------------------------------------------------
/**
* Unserialize
*
* This function unserializes a data string, then converts any
* temporary slash markers back to actual slashes
*
* #access private
* #param array
* #return string
*/
function _unserialize($data) {
$data = unserialize(strip_slashes($data));
if (is_array($data)) {
foreach ($data as $key => $val) {
if (is_string($val)) {
$data[$key] = str_replace('{{slash}}', '\\', $val);
}
}
return $data;
}
return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
}
/**
* Serialize an array
*
* This function serializes the data and then base64_encodes it for
* storage with memcached. This avoids the common backslash issue.
*
* #access private
* #param array
* #return string
*/
function _serialize($data) {
return base64_encode(serialize($data));
}
// --------------------------------------------------------------------
/**
* Unserialize
*
* This function unserializes a data string. I first base64_decodes
* the data from memcached storage.
*/
function _unserialize($data) {
return unserialize(base64_decode($data));
}
You can sometimes come across this issue if you are using different versions of PHP, or if you change the version of PHP you are using while a session was open.
For example if you have a session cookie with an app that uses PHP 5.6.* and then you try to use it with an app (that resides on another sub-domain) that uses PHP 7.2.*, then you are going to get a warning error. Or, if you had an open session and then you changed the version of PHP that you are using with your app (say if you are developing locally and switching around PHP versions), then you'll get the warning. So best to use serialize/unserialize and with PHP version that does not change.
I am wondering this question for a long time, how does PHP handle references are they a good idea to use and I can't explain better than using an example, lets look at the following class and then # the comment of the setResult method.
Lets imagine we are using a model view controller framework and we are building a basic AjaxController, we only got 1 action method (getUsers) so far. Read the comments, and I hope my question is clear, how does PHP handle these kind of situations and is it true what I wrote about the x times in the memory # the setResult docblock.
class AjaxController{
private $json = array(
'result' => array(),
'errors' => array(),
'debug' => array()
);
/**
* Adds an error, always displayed to users if any errors.
*
* #param type $description
*/
private function addError($description){
$this->json['errors'][] = $description;
}
/**
* Adds an debug message, these are displayed only with DEBUG_MODE.
*
* #param type $description
*/
private function addDebug($description){
$this->json['debug'][] = $description;
}
/**
* QUESTION: How does this go in memory? Cause if I use no references,
* the array would be 3 times in the memory, if the array is big (5000+)
* its pretty much a waste of resources.
*
* 1st time in memory # model result.
* 2th time in memory # setResult ($resultSet variable)
* 3th time in memory # $this->json
*
* #param array $resultSet
*/
private function setResult($resultSet){
$this->json['result'] = $resultSet;
}
/**
* Gets all the users
*/
public function _getUsers(){
$users = new Users();
$this->setResult($users->getUsers());
}
public function __construct(){
if(!DEBUG_MODE && count($this->json['debug']) > 0){
unset($this->json['debug']);
}
if(count($this->json['errors']) > 0){
unset($this->json['errors']);
}
echo json_encode($this->json);
}
}
Another simple example: What would be better to use technique A:
function example(){
$latestRequest = $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$_SESSION['abc']['test']['abc'] = 'test';
}
}
Or technique B:
function example(){
$latestRequest =& $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$latestRequest = 'test';
}
}
Thanks for reading and advise :)
In short: don't use references.
PHP copies on write. Consider:
$foo = "a large string";
$bar = $foo; // no copy
$zed = $foo; // no copy
$bar .= 'test'; // $foo is duplicated at this point.
// $zed and $foo still point to the same string
You should only use references when you need the functionality that they provide. i.e., You need to modify the original array or scalar via a reference to it.
How do I queue functions in PHP? I need something that works just like Wordpress's add_action system. I want enqueue function which then runs when the time is right.
Edit
This seems to work perfectly. Anyone got any tips to improve my code?
$enqueued_actions = array();
/**
* Enqueue an action to run at a later time.
* #param string $hook The hook name.
* #param obj $func The function object.
* #param integer $imp The level of importance from 0-9
*/
function add_action($hook, $func, $imp = 0) {
global $enqueued_actions;
$enqueued_actions[$hook][] = array('func' => $func, 'imp' => $imp);
}
/**
* Run the enqueued actions with the correct hook.
* #param string $hook Hook name.
*/
function run_action($hook) {
global $enqueued_actions;
$actions = $enqueued_actions[$hook];
for($i = 0; $i < 9; $i++) {
foreach($enqueued_actions[$hook] as $action) {
if($action['imp'] == $i) {
call_user_func($action['func']);
}
}
}
}
You are on the right track here. You might want to make the hooks persistent, though, i.e. saving the hooks into a database, a CSV file, an XML object model, etc.
Also, you might want to introduce a function for the sake of calling all the actions that have been latched to a certain hook, something like call_actions($hook);