Saving SimpleXMLElement parameter to global variable - php

I'm currently using the Botman framework to make my bot read a XML file.
Currently, my bot is able to grab data from an XML file and output it.
I'm having issue saving the XML file back into a global variable (so I can reuse later on in the code). Here is the current error message I get when trying to do this:
"message": "Serialization of 'SimpleXMLElement' is not allowed",
"exception": "Exception",
"file": "C:\\Users\\Jack\\finalyearproject\\gfyp\\gfyp\\vendor\\opis\\closure\\src\\SerializableClosure.php
I'm having issues here:
public function nodeTest($xmlFile, $answer)
{
$this->XMLFile = $xmlFile;
...
}
Here is the class code before the function:
class StartConversation extends Conversation
{
public $XMLFile;
...
public function askForDatabase()
{
$question = Question::create('Test test test?')
->fallback('Unable to create a new database')
->callbackId('create_database')
->addButtons([
Button::create('Suicide')->value('suic'),
Button::create('Self-harm')->value('sh'),
]);
$this->ask($question, function (Answer $answer) {
$xmlResult = $this->testXMLGrabFunction($answer);
if ($answer->getValue() == 'suic') {
$this->nodeTest($xmlResult, $answer);
}
if ($answer->getValue() == 'sh') {
$this->nodeTest($xmlResult, $answer);
}
});
}
}
Here is the class where I get the XML file originally:
class testClass
{
function getXMLCategory($categoryName)
{
$xml = simplexml_load_file('ST-working-age-23-3-20.xml');
if($categoryName == 'suic')
{
$xml = $xml->node[0];
return $xml;
} elseif($categoryName == 'sh') {
$xml = $xml->node[1];
return $xml;
} else {
return null;
}
}
}
Any suggestions would be great - thanks

The error message is telling you that somewhere in the code is trying to serialize the object, that is turn it into a string representation. This is probably in the framework you're using, and what you are thinking of as a "global variable" is actually stored between requests in some form of session, e.g. in a file on disk.
Because of the way SimpleXML is implemented, it doesn't allow this operation. The simplest workaround is to instead store the XML by calling ->asXML(), and then re-parse it when you need it with simplexml_load_string().
You'll want to do that round trip as rarely as possible, so it will be worth understanding better about how the "global variables" are actually handled by the framework so you can try to do it once on each request.

Related

Invalid argument supplied for foreach() simple_html_dom.php on line 1119

here is what im doing
im using
pthreads from - pthreads.org
php Simple Html DOM parser from - simplehtmldom.sourceforge.net
now the process of what i (will) do is:
I am reading a bulk of links that is from a text file.
I initialize a thread to have a separate process
I am creating a log file for this thread so that i will know, what happens later on.
now, this is my code for my thread class.
class ReadLinks extends Thread {
private $conn;
private $links;
private $fileObj;
public function __construct($conn, $links, $fileObj) {
//.. well just asign this to the global variables
}
public function run() {
try {
$this->logMsg("Start Reading Reviews");
$this->readLinks();
} catch (Exception $ex) {
$this->logMsg($ex);
}
$this->closeLog();
}
private function readLinks() {
$this->logMsg("Links");
foreach ($this->links as $link) {
$link = trim(preg_replace('/\s\s+/', ' ', $link));
$this->logMsg("Link: " . $link);
$html = html_readLink($link);
break;
}
}
private function logMsg($msg) {//something to write on the text file
}
private function closeLog() {//closes the textfile
}}
$conn - is my mysqli link to have db actions in the future
$links - is an array of links to be read.
$fileObj- is a resource return from fopen(). ( well to write into a file)
now who is that html_readlink,
its an outer function that is like this:
function html_readLink($link) {
return file_get_html($link);}
basically it is the resource returned by a function from simple html dom parser
now, i have as well a function that reads a link alone to do the other (different business requirement) and im using the simple html dom parser with ease.
with the pthreads, i tried writing the file(logs first) so that i can ensure that everything as well works fine.
about contacting db is not yet sure., well ill try to figure it out if it works fine, but logically it should work.
now when i called this class: its like this:
try {
$thread = new readLinks($conn, $Links, createlog());
if ($thread->start()) {
$thread->join();
} else {
echo "something i need to research if this happens";
}
} catch (Exception $err) {
echo $err; //something i need to research as well if this happens
}
i got this error
Warning: Invalid argument supplied for foreach() in C:\my\path\to\simplehtmldom_1_5\simple_html_dom.php on line 1119
that simplehtmldom code is:
function clear()
{
foreach ($this->nodes as $n) {$n->clear(); $n = null;}
// This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.
if (isset($this->children)) foreach ($this->children as $n) {$n->clear(); $n = null;}
if (isset($this->parent)) {$this->parent->clear(); unset($this->parent);}
if (isset($this->root)) {$this->root->clear(); unset($this->root);}
unset($this->doc);
unset($this->noise);
}
now that is the source code coming from simple html dom. that foreach is the one that is returning the error. now my other code using simple html dom doesn't have a problem with simple html dom. but with pthreads i got this error.
also, when i change my codes and didn't use pthreads, (had some revisions like this:
on pthreads class:
class ReadLinks {// extends Thread {
//insert other codes
public function readLinks() {
$this->logMsg("Links");
foreach ($this->links as $link) {
$link = trim(preg_replace('/\s\s+/', ' ', $link));
$this->logMsg("Link: " . $link);
$html = html_readLink($link);
$this->logMsg(getTitle($html));
//
break;
}
}
and change the way this is called like this:
try {
$thread = new ReadLinks($conn, $revLinks, createlog());
$thread->readLinks();
// if ($thread->start()) {
// $thread->join();
// } else {
// echo "something i need to research if this happens";
// }
} catch (Exception $err) {
echo $err; //something i need to debug and research if this happens
}
everything works fine, i get the desired results.
pthreads is something i need to use since loading bulk links and reading each of them is quite a time consuming process. and i need it to be on a separate thread. now i dont know whats wrong with these pthreads, or simple html dom parser. have i done something unnecessary/wrong? is there other way to do this?
anyone??
EDIT
i followed the answer of Prafulla Kumar Sahu:
the new code for the function clear() of simple html dom is:
function clear() {
if (is_array($this->nodes) || $this->nodes instanceof Traversable) {
foreach ($this->nodes as $n) {
$n->clear();
$n = null;
}
}
// This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.
if (isset($this->children))
foreach ($this->children as $n) {
$n->clear();
$n = null;
}
if (isset($this->parent)) {
$this->parent->clear();
unset($this->parent);
}
if (isset($this->root)) {
$this->root->clear();
unset($this->root);
}
unset($this->doc);
unset($this->noise);
}
the result is: it eliminated the error
but it is not the desired result
when using the function
$x=$resource->find($selector,0);
//resource is the return obj of file_gets_content, selector is my css selector string
it returns null/empty where in fact it should have a value.
ive checked a separate function that uses the simple html dom after i updated their code, seems it wasn't affected, and it is working properly. but with my pthread class, it is not working correctly.
The code I have doesn't have a foreach on line 1119; maybe you have an older version. You're getting a warning only, what problem(s) do you see in the results?
1117 // save dom as string
1118 function save($filepath='')
1119 {
1120 $ret = $this->root->innertext();
1121 if ($filepath!=='') file_put_contents($filepath, $ret, LOCK_EX);
1122 return $ret;
1123 }
It happens if the variable you are trying to traverse using foreach is not irritable so please check if your variable is either an array or instanceof Traversable class .
*It may be because you are not getting any value for that variable you want to traverse.
so, I would suggest you to use is_array( $whatever ) || $whatever instanceof Traversable just before foreach.
ie.
if( is_array( $whatever ) || $whatever instanceof Traversable ){
foreach( $whatever as $what ){
//some code
}
}
In your case it is
function clear()
{
foreach ($this->nodes as $n) {$n->clear(); $n = null;}
// This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.
if (isset($this->children)) foreach ($this->children as $n) {$n->clear(); $n = null;}
if (isset($this->parent)) {$this->parent->clear(); unset($this->parent);}
if (isset($this->root)) {$this->root->clear(); unset($this->root);}
unset($this->doc);
unset($this->noise);
}
source:- https://github.com/jalbertbowden/simplehtmldom/blob/master/simplehtmldom_1_5/simple_html_dom.php#L1119
this means you are unable to get $this->nodes correctly, so please var_dump it before you are calling function clear or before the foreach .

Best way to handle static text / messages in PHP OOP project (JSON maybe?)

Until now, unless I made a multilingual website (where I would use .mo & .po files), all the text would be scrambled all around the template and / or class files. Instead, I would like to store all static text in a file that is easily editable by my coworkers and clients (that rules out database storage and POedit).
I made a JSON file that stores the messages / static text like this:
{
"titles": {
"main_title": "This is the main title of the website",
"login_page_title": "Please, sing in",
"about_page_title": "About us"
},
"errors": {
"empty_required_field": "This field is required.",
"database_connection_error": "Couldn't connect to the database.",
}
}
Then I import it in the index.php file:
$messages = json_decode(file_get_contents("messages.json"));
And use it like:
echo($messages->titles->main_title);
Which has been working so far so good (although I'm uncertain that there aren't better ways to archieve this). At least in the template pages where everything is html with minimal logic.
But I'm having trouble using the strings from the JSON file inside the classes' functions. I would like to use the error messages when throwing exceptions, for example. But I'm quite reluctant about stating "global $message" in every function where it's used (feels repetitive). Also everybody says that globals are naughty.
So my questions are two:
1) Is the JSON file a good way to handle my problem? (and if not, why, and which method would be better?).
2) How could I retrieve the stored strings from inside the classes? I'm thinking something like extending the Exception class to include the error messages, but I'm unsure of how to do it.
Thanks in advance for your help.
One approach, which Laravel takes, is creating some sort of directory tree like the following:
lang/
en/
titles.php
errors.php
titles.php could contain the following:
<?php
return [
'main_title' => 'This is the main title of the website',
'login_page_title' => 'Please, sing in',
'about_page_title' => 'About us'
];
As for errors.php:
<?php
return [
'empty_required_field' => 'This field is required.',
'database_connection_error' => "Couldn't connect to the database.",
];
I don't really like the JSON approach because it's not very flexible. For one, in PHP files, you have access to any variables you may want to give it, there's comments, possibility of using functions to create some messages, etc. This is why I recommend the above method.
In order to get the messages, you would require the file in a variable, like $titles = require 'lang/en/titles.php', using it like: $titles['main_title']. This method also makes it easy to change the language if needed.
While I'm not 100% sure I understand your exception problem, you would throw an exception with the appropriate message like: throw new Exception($errors['empty_required_field']);
In the end I opted for a Singleton class that loads/includes a separate text file. Nice global scope and should be easy to adapt to other needs (multilingüal, separate language files, or whatever). As I said I'm no expert so all critique is welcome.
<?php
class CustomText {
private static $instance = null;
private static $text;
private function __clone() {}
// On construct, checks if the strings are stored in a session.
// If not, retrieves them from file and stores them in a session.
private function __construct() {
if(self::isStoredInSession() == true) {
self::$text = $_SESSION["custom_text"];
} else {
//self::$text = json_decode(file_get_contents("messages.json"),true);
self::$text = include_once("messages.php");
self::saveToSession();
}
}
// Private initialization called on every public method so I don't have to worry about it on other files.
private static function initialize() {
if(self::$instance == null) {
self::$instance = new self;
}
}
// Session management
private static function saveToSession() {
if(session_status() == PHP_SESSION_NONE) {
session_start();
}
if(!isset($_SESSION["custom_text"])) {
$_SESSION["custom_text"] = self::$text;
}
}
private static function isStoredInSession() {
if(session_status() == PHP_SESSION_NONE) {
session_start();
}
if(isset($_SESSION["custom_text"])) {
return true;
}
return false;
}
// Sample public functions
public static function getText($section,$string){
self::initialize();
if(isset(self::$text[$section][$string])) {
return self::$text[$section][$string];
} else {
return "";
}
}
public static function getError($string) {
self::initialize();
if(isset(self::$text["error"][$string])) {
return self::$text["error"][$string];
} else {
return "";
}
}
public static function getWebsiteTitle($section,$divider = " - ") {
self::initialize();
$title = "";
if(isset(self::$text["title"]["main"])) {
$title .= self::$text["title"]["main"];
}
if(isset(self::$text["title"][$section])) {
if(!empty($title)) {
$title .= $divider;
}
$title .= self::$text["title"][$section];
}
return $title;
}
}
What worries me the most is that I'm not sure that storing the data in a session is better that including a file on each page, and I have everything twice in the session variable and the class parameter.

PHP include external method and class

I'm new to PHP and I have an issue I can't seem to fix or find a solution to.
I'm trying to create a helper function that will return an 'object' filled with information pulled from an XML file. This helper function, named functions.php contains a getter method which returns a 'class' object filled with data from an SVN log.xml file.
Whenever I try to import this file using include 'functions.php'; none of the code after that line runs the calling function's page is blank.
What am I doing wrong?
Here is what the functions.php helper method and class declaration looks like:
<?php
$list_xml=simplexml_load_file("svn_list.xml");
$log_xml=simplexml_load_file("svn_log.xml");
class Entry{
var $revision;
var $date;
}
function getEntry($date){
$ret = new Entry;
foreach ($log_xml->logentry as $logentry){
if ($logentry->date == $date){
$ret->date = $logentry->date;
$ret->author = $logentry->author;
}
}
return $ret;
}
I'm not sure what the point of having a separate helper function from the class is, personally I'd combine the two. Something like this
other-file.php
require './Entry.php';
$oLogEntry = Entry::create($date, 'svn_log.xml');
echo $oLogEntry->date;
echo $oLogEntry->revision;
Entry.php
class Entry
{
public $revision;
public $date;
public $author;
public static function create($date, $file) {
$ret = new Entry;
$xml = simplexml_load_file($file);
foreach($xml->logentry as $logentry) {
if($logentry->date == $date) {
$ret->date = $logentry->date;
$ret->author = $logentry->author;
$ret->revision = $logentry->revision;
}
}
return $ret;
}
}
EDIT
In light of the fact OP is new to PHP, I'll revise my suggestion completely. How about ditching the class altogether here? There's hardly any reason to use a class I can see at this point; let's take a look at using an array instead.
I might still move the simplexml_load_file into the helper function though. Would need to see other operations to merit keeping it broken out.
entry-helper.php
function getEntry($date, $file) {
$log_xml = simplexml_load_file($file);
$entry = array();
foreach($log_xml->logentry as $logentry) {
if($logentry->date == $date) {
$entry['date'] = $logentry->date;
$entry['author'] = $logentry->author;
$entry['revision'] = $logentry->revision;
}
}
return $entry;
}
other-file.php
require './entry.php';
$aLogEntry = Entry::create($date, 'svn_log.xml');
echo $aLogEntry['date'];
echo $aLogEntry['revision'];
EDIT
One final thought.. Since you're seemingly searching for a point of interest in the log, then copying out portions of that node, why not just search for the match and return that node? Here's what I mean (a return of false indicates there was no log from that date)
function getEntry($date, $file) {
$log_xml = simplexml_load_file($file);
foreach($log_xml->logentry as $logentry) {
if($logentry->date == $date) {
return $logentry;
return false;
}
Also, what happens if you have multiple log entries from the same date? This will only return a single entry for a given date.
I would suggest using XPATH. There you can throw a single, concise XPATH expression at this log XML and get back an array of objects for all the entries from a given date. What you're working on is a good starting point, but once you have the basics, I'd move to XPATH for a clean final solution.

How to make an object resident to a function call?

I have a PHP program I'm writing that does a SOAP request, and it returns an Object. I need to write a function where it takes the data from this Object and uses it in various ways, but I don't want it to do a SOAP request each time if the SOAP request for the data in this Object is already resident.
Pseudo-code example:
$price = GetPartPrice("1234");
function GetPartPrice($part_number) {
If Parts_List_Object not found then do SOAP request to get Parts_List_Object.
}
The problem I see is that I don't know where or how to store if the Parts_List_Object is already there. Do I need to set something up to make the StdClass object that gets requested from the SOAP/JSON request global or is there a better method to do all this? Thanks!
One method would be to build a registry of these objects where you store the ones you fetch and look up the ones you need. That allows you to simply grab a reference to the instance that you've already loaded. A very basic example:
class PartListRegistry {
private static $list = array();
// After you do the SOAP request, call this to save a reference to the object
public static function addPartObject($key, $obj) {
self::$list[$key] = $obj;
}
// Call this to see if the object exists already
public static function getPartObject($key) {
if (isset(self::$list[$key])) {
return self::$list[$key];
}
return null;
}
}
function GetPartPrice($part_number) {
$part = PartListRegistry::getPartObject($part_number);
if ($part === null) {
$part = .... // Do your SOAP request here
// Save a reference to the object when you're done
PartListregistry::addPartObject($part_num, $part);
}
// Do your stuff with the part ....
}

Instantiating a AMF PHP class not working

I am trying to use AMF PHP to pass variables to a flash file, thus far I cannot see anything wrong with my code, but I have very little experience with creating classes, so here it goes, here is my code,
index.php:
<?php
include "amfphp/services/flashMe.php";
$session = true;
if ($session == true) {
$uid = '12345';
$thing = new flashMe;
$thing->push($uid);
} else {
//login
}
?>
flashMe.php:
<?php
class flashMe {
public function __construct() {
}
public function push($one)
{
return $one;//sends the uid to the flash file?
}
}
?>
Flash is looking for the flashMe class and the push method within that class, but I keep getting null variables in my flash file when I run it, is there something wrong with this code?
Thanx in advance!
Your index.php file is unnecessary.
Your second file is incomplete. Here is the example from the docs for their "hello world" class file:
<?php
class HelloWorld
{
function HelloWorld()
{
$this->methodTable = array
(
"say" => array
(
"access" => "remote",
"description" => "Pings back a message"
)
);
}
function say($sMessage)
{
return 'You said: ' . $sMessage;
}
}
?>
This file should be saved as "HelloWorld" matching the "class HelloWorld" you have named in the php file (you did this part right with FlashMe).
The example file in the docs for the Flash piece (in actionscript) is here:
import mx.remoting.*;
import mx.rpc.*;
import mx.remoting.debug.NetDebug;
var gatewayUrl:String = "http://localhost/flashservices/gateway.php"
NetDebug.initialize();
var _service:Service = new Service(gatewayUrl, null, 'HelloWorld', null , null);
var pc:PendingCall = _service.say("Hello world!");
pc.responder = new RelayResponder(this, "handleResult", "handleError");
function handleResult(re:ResultEvent)
{
trace('The result is: ' + re.result);
}
function handleError(fe:FaultEvent)
{
trace('There has been an error');
}
The gateway URL should go to wherever your services can be reached. I'm sure if you try a few you'll find the right one. The neat thing about amfphp is that it allows you to also test your services out before you try implementing them in the gateway (if you go to the URL in your browser).
I'm pretty new to AMFPHP as well, but I've found the docs to be extraordinarily useful. If you need more help on classes, you can find more info on the PHP docs page.
You missed the parenthesis after new flashMe
$thing = new flashMe();
$thing->push($uid);
Amfphp or Zend AMF only allow you to call public methods on a remote class that is exposed by your gateway. You example is not a class and therefore no remote method can be called. This looks more like something that you would do with an http post.
http://framework.zend.com/manual/en/zend.amf.server.html

Categories