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.
Related
Error page recieved
This is the code block where the error originates. How do I handle this code snippet, I need it to work on laravel 5.4 the $extras variable is an array.
/**
* Map to configuration
*
* #param $keys
* #param $config_var
* #param $extras
* #return array
*/
private function map_to_config($keys, $config_var, $extras = [])
{
try {
$configArray =
array_map(function ($string) use ($keys, $extras) {
line 132 where the error is--> return array_combine($keys, explode(":", $string)) + $extras;
}, explode(",", $config_var));
return array_filter($configArray);
} catch (\Exception $e) {
return [];
}
}
}
array_combine returns FALSE if number of values in arrays given as arguments is different. That's probably a reason why you're getting an error about unsupported operand types - you're trying to combine a boolean with an array.
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.
Symfony converts nested YAML and PHP array translation files to a dot notation, like this: modules.module.title.
I'm writing some code that exports YAML translation files to a database, and I need to flatten the parsed files to a dot notation.
Does anyone know which function Symfony uses to flatten nested arrays to dot notation?
I cannot find it anywhere in the source code.
It's the flatten() method in Symfony\Component\Translation\Loader\ArrayLoader:
<?php
/**
* Flattens an nested array of translations.
*
* The scheme used is:
* 'key' => array('key2' => array('key3' => 'value'))
* Becomes:
* 'key.key2.key3' => 'value'
*
* This function takes an array by reference and will modify it
*
* #param array &$messages The array that will be flattened
* #param array $subnode Current subnode being parsed, used internally for recursive calls
* #param string $path Current path being parsed, used internally for recursive calls
*/
private function flatten(array &$messages, array $subnode = null, $path = null)
{
if (null === $subnode) {
$subnode = &$messages;
}
foreach ($subnode as $key => $value) {
if (is_array($value)) {
$nodePath = $path ? $path.'.'.$key : $key;
$this->flatten($messages, $value, $nodePath);
if (null === $path) {
unset($messages[$key]);
}
} elseif (null !== $path) {
$messages[$path.'.'.$key] = $value;
}
}
}
I don't know how what is written in previous Symfony versions, but in Symfony 4.2 onwards translations are returned already flattened.
Example controller which returns the messages catalogue translations. In my case I used this response to feed the i18next js library.
<?php
declare(strict_types=1);
namespace Conferences\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Symfony\Component\Translation\TranslatorBagInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class TranslationsController
{
public function __invoke(TranslatorInterface $translator): JsonResponse
{
if (!$translator instanceof TranslatorBagInterface) {
throw new ServiceUnavailableHttpException();
}
return new JsonResponse($translator->getCatalogue()->all()['messages']);
}
}
Route definition:
translations:
path: /{_locale}/translations
controller: App\Controller\TranslationsController
requirements: {_locale: pl|en}
I am trying to call a Java based service that uses a Jackson object mapper from a PHP(Magento) application. In both of them I am sending the same headers and same parameters, but the CURL call works fine where as PHP call fails with following message,
'No content to map to Object due to end of input'
My curl is as follows,
curl -v -k -X POST -H "Content-Type:application/json;charset=UTF-8" -d '{"name":"john","email":"john#doe.com"}' https://localhost:8080/webapps/api/
The PHP Request is code is as follows,
$iClient = new Varien_Http_Client();
$iClient->setUri('https://localhost:8080/webapps/api/')
->setMethod('POST')
->setConfig(array(
'maxredirects'=>0,
'timeout'=>30,
));
$iClient->setHeaders($headers);
$iClient->setParameterPost(json_encode(array(
"name"=>"John",
"email"=>"john#doe.com"
)));
$response = $iClient->request();
I am not the owner of the java service that uses jackson object mapper so I have no idea what happens on other side
Any suggestions on debugging or fixing this would be appreciated
Well, finally this worked. The issue was with the wrong implementation in my end of the code, if you refer the Zend_Http_Client. Please refer to the methods below from Zend_Http_Client,
/**
* Set a POST parameter for the request. Wrapper around _setParameter
*
* #param string|array $name
* #param string $value
* #return Zend_Http_Client
*/
public function setParameterPost($name, $value = null)
{
if (is_array($name)) {
foreach ($name as $k => $v)
$this->_setParameter('POST', $k, $v);
} else {
$this->_setParameter('POST', $name, $value);
}
return $this;
}
/**
* Set a GET or POST parameter - used by SetParameterGet and SetParameterPost
*
* #param string $type GET or POST
* #param string $name
* #param string $value
* #return null
*/
protected function _setParameter($type, $name, $value)
{
$parray = array();
$type = strtolower($type);
switch ($type) {
case 'get':
$parray = &$this->paramsGet;
break;
case 'post':
$parray = &$this->paramsPost;
break;
}
if ($value === null) {
if (isset($parray[$name])) unset($parray[$name]);
} else {
$parray[$name] = $value;
}
}
So the setParameterPost somehow honors only the array parameters(key value pairs) and my POST payload was a json string. So to solve the issue, I modified the code as below,
$iClient = new Varien_Http_Client();
$iClient->setUri('https://localhost:8080/webapps/api/')
->setMethod('POST')
->setConfig(array(
'maxredirects'=>0,
'timeout'=>30,
));
$iClient->setHeaders($headers);
$iClient->setRawData(json_encode(array(
"name"=>"John",
"email"=>"john#doe.com"
)), "application/json;charset=UTF-8");
$response = $iClient->request();
This solved the issue. I am not sure of a better way, but if there is anything better would be glad to use it.
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.