PHP class unset/purge private array variable - php

I'm trying to make an error logging class, I have some functions to set up various output methods like DB, file, return and screen. I want all errors to be stored into an array and when __destruct() is called, I want to stop the client from waiting for data and the log details about errors the user experienced. This way they don't have to report errors to me.
I have 2 modes, a simple GUI to test the functionality and the actual script generates responses in JSON, machine to machine. For GUI the final dump is fine but for JSON it destroys the response. So all error reporting is off and I have to handle any errors that would be dumped on screen myself, hence the $return in function flush_log($return) which make the function return a string if set to true.
After reporting flushing the errors I want to:unset($this->log_arrays)
or empty: $this->log_arrays=Array();, but it is out of scope - I understand why, my function uses a local copy - but how do I reset the array?
[EDIT]:
I tried:
$this->log_arrays = Array();
$this->log_arrays = null;
array popping:
for ($i = 1; count($this->log_arrays); $i++)
{
array_pop($this->log_arrays);
}
But I think none of it can work because within a class function you work with copies of variables, so they're basically out of scope.
[/EDIT]:
This is an already simplified class.. :
<?php
class log_strings extends mysqli
{
private $log_arrays = Array();
public function __construct($output_to_file=false, $output_to_db=true, $fall_back_file=true, $arguments, $ip=null)
{
// Setup mysqli connection, file handle or report error if one or all have failed.
// Also check wich outputs should be used and keep that info for later.
}
public function log($level, $string)
{
$log_arrays[] = Array('level' => $level, 'string' => $string);
}
public function __destruct()
{
$this->flush_log();
}
public function flush_log($return=false)
{
if (!isset($log_arrays) && count($log_arrays) == 0)
{
return true;
}
if ($return)
{
return $this->return_output();
}
else
{
$success = false;
// if enabled, output to db
if ($this->output_to_db)
{
$success = $success || $this->mysqli_output();
}
// if enabled or if db failed and fallback is enabled, output to file
if ($this->output_to_file || ($this->fall_back_file && !$success))
{
$success = $success || $this->file_output();
}
// if neither file or db succeeded, dump on screen
if ($success = false)
{
$this->screen_dump();
}
return true;
}
unset($this->log_arrays); // <= This is what it is all about!
}
private function screen_dump()
{
foreach($this->log_arrays as $array)
{
echo "<strong>{$array['level']}</strong>{$array['string']}<br/>\n";
}
}
private function mysqli_output()
{
// Output to db functionally equal to $this->screen_dump()
}
private function file_output()
{
// Output to file functionally equal to $this->screen_dump()
}
private function return_output()
{
// Return output functionally equal to $this->screen_dump()
}
}
?>

Resetting the array should work
$this->log_arrays = array();
Unset a class property is a very bad idea. Because it may be used in other methods or other classes using a potentionally getter of your class.

Related

PHP pthreads - shared objects

Im searching a safe and fast way to use a shared object.
I asked the question already here: https://github.com/krakjoe/pthreads/issues/470
but obviuously this wasnt the right place.
Trying to share an object (Threaded) with many other contextes (Thread).
All threads are updating this shard object -- they can set own requests and have to respond to requests from others also.
Now that krakjoe responded that lock/unlock wont be available in 7 i got a problem.
I know about :.synchronized but have no idea how to use it to get it working for my needs.
How can i use ::synchronized to write methods like
lock()
unlock()
is_locked() -- check if locked, and if so dont try - just go on and try later
EDIT:
i wrote a (imo) very easy test script.
this script includes no syc/lock/... methods atm.
it should just show what im trying to do.
im still searching a way to use :: to make this shared safe.
code:
<?php
/*
TEST:
create n threads
each will
- Shared::set() its own ref
- check if Shared::exists() its own ref
- Shared::get() its ref back
- call method ::isRunning() at returned val to easily check if is ref or got overwritten by another context
TODO:
using ::synchronized to handle multi-context-access
NOTES:
every method as public to prevent pthreads v2 "Method Modifiers - Special Behaviour"
see: "Method Modifiers - Special Behaviour"
at http://blog.krakjoe.ninja/2015/08/a-letter-from-future.html
*/
class Shared extends Threaded
{
public $data;
public function exists($ident)
{
return isset($this->data[$ident]);
}
public function set($ident, $ref)
{
$return = false;
if(!isset($this->data[$ident])){
$data = $this->data;
$data[$ident] = $ref;
$this->data = $data;
$return = $this->data[$ident];
}
#echo __METHOD__ . '(' . $ident . ') => ' . gettype($return) . PHP_EOL;
return $return;
}
public function get($ident)
{
$return = false;
if($this->exists($ident) === true){
$data = $this->data;
$return = $data[$ident];
unset($data[$ident]);
$this->data = $data;
}
#echo __METHOD__ . '(' . $ident . ') => ' . gettype($return) . PHP_EOL;
return $return;
}
}
class T extends Thread
{
public $count;
public function __construct(Shared $Shared, $ident)
{
$this->Shared = $Shared;
$this->ident = $ident;
}
public function run()
{
$slowdown = true;
$this->count = 0;
while(true){
if($slowdown){
// "don't allow usleep or sleep" : https://github.com/krakjoe/pthreads/commit/a157b34057b0f584b4db326f30961b5c760dead8
// loop a bit to simulate work:
$start = microtime(true);
$until = rand(1, 100000)/1000000;
while(microtime(true)-$start < $until){
// ...
}
}
if($this->Shared->exists($this->ident) === true){
$ref = $this->Shared->get($this->ident);
}
else{
$ref = $this->Shared->set($this->ident, $this);
}
// calling a method on $ref -- if not a ref we crash
$ref->isRunning();
unset($ref);
$this->count++;
}
}
}
echo 'start ...' . PHP_EOL;
$n = 8;
$Shared = new Shared();
for($i = 0, $refs = array(); $i < $n; $i++){
$refs[$i] = new T($Shared, $i);
$refs[$i]->start();
}
while(!empty($refs)){
// print status:
if(!isset($t)or microtime(true)-$t > 1){
$t = microtime(true);
echo 'status: ' . count($refs) . ' running atm ...' . PHP_EOL;
}
// join crashed threads:
foreach($refs as $i => $thread){
if($thread->isRunning() === false){
echo 'T-' . $i . ' stopped after ' . $thread->count . PHP_EOL;
if($thread->isJoined() === false){
$thread->join();
}
unset($refs[$i]);
}
}
}
echo 'no thread running anymore.' . PHP_EOL;
/* output
start ...
status: 8 running atm ...
Notice: Undefined offset: 6 in ...\shared_test.php on line 33
Fatal error: Call to a member function isRunning() on null in ...\shared_test.php on line 82
T-6 stopped after 10
status: 7 running atm ...
Notice: Undefined offset: 4 in ...\shared_test.php on line 33
Fatal error: Call to a member function isRunning() on null in ...\shared_test.php on line 82
T-4 stopped after 35
status: 6 running atm ...
Notice: Undefined offset: 7 in ...\shared_test.php on line 33
Fatal error: Call to a member function isRunning() on null in ...\shared_test.php on line 82
T-7 stopped after 43
status: 5 running atm ...
status: 5 running atm ...
status: 5 running atm ...
[...]
*/
?>
Threaded objects are already thread safe, that is to say that, any time you read, write, check for the existence of, or delete (unset) a member, the operation is atomic - no other context can perform any of the aforementioned operations while the first operation takes place. The same is true for engine handlers that the user is unaware of, everything down to the lowest level is implicitly safe.
Quite re-assuring, however ... This has obvious limits when the logic gets more complex, such as checking the existence of a member before setting or doing something else with it, as you are doing: While the operations on the object are atomic, there is nothing to stop another context unseting a member between the call to isset and the call to read the property/dimension.
This applies to PHP7 (pthreads v3+)
Safety, and integrity are two different things here. When integrity is important you can use Threaded::synchronized in PHP7 to preserve it correctly. In PHP5 you could preserve it also, but the code would be more complicated, as would the explanation.
Your second example should run indefinitely, if I understand it's logic. So I'm using that assumption to construct the correct code, I'm going to make further assumptions about what you might want to do in this endless loop and provide some insight where it seems required.
<?php
class Referee extends Threaded {
public function find(string $ident, Threaded $reference) {
return $this->synchronized(function () use($ident, $reference) {
if (isset($this[$ident])) {
return $this[$ident];
} else return ($this[$ident] = $reference);
});
}
public function foreach(Closure $closure) {
$this->synchronized(function() use($closure) {
foreach ($this as $ident => $reference) {
$closure($ident, $reference);
}
});
}
}
class Test extends Thread {
public function __construct(Referee $referee, string $ident, bool $delay) {
$this->referee = $referee;
$this->ident = $ident;
$this->delay = $delay;
}
public function run() {
while (1) {
if ($this->delay) {
$this->synchronized(function(){
$this->wait(1000000);
});
}
$reference =
$this->referee->find($this->ident, $this);
/* do something with reference here, I guess */
/* do something with all references here */
$this->referee->foreach(function($ident, $reference){
var_dump(Thread::getCurrentThreadId(),
$reference->getIdent(),
$reference->isRunning());
});
}
}
public function getIdent() {
return $this->ident;
}
private $referee;
private $ident;
private $delay;
}
$referee = new Referee();
$threads = [];
$thread = 0;
$idents = [
"smelly",
"dopey",
"bashful",
"grumpy",
"sneezy",
"sleepy",
"happy",
"naughty"
];
while ($thread < 8) {
$threads[$thread] = new Test($referee, $idents[$thread], rand(0, 1));
$threads[$thread]->start();
$thread++;
}
foreach ($threads as $thread)
$thread->join();
?>
So we'll look at the differences, I'll tell you why they are as they are and how else you might write them, you already know that we're not talking about safety now, but integrity, you are afforded the (quite remarkable) assumption that anything you write is "safe", as explained.
The first major difference, is this:
if ($this->delay) {
$this->synchronized(function(){
$this->wait(1000000);
});
}
This is simply a suitable way to make a Thread wait, you wouldn't have to use the Thread itself to synchronize, you could use any Threaded object. The benefit of doing things properly, in case not clear is that, sleep and usleep do not leave threads in a receptive state, using ::wait does.
In the real world, where you really should only ever wait for something, that would be a more complex block, it might (and should) look more like:
if ($this->delay) {
$this->synchronized(function(){
while ($this->condition) {
$this->wait(1000000);
}
});
}
Note: waiting for a timeout is technically waiting for something, however, you might be awoken by something other than the timeout having been reached, and code should be prepared for that.
Such that, another context is able to notify the Thread that it should stop waiting and shutdown gracefully, or carry out some other important action immediately, simply by synchronizing, changing a condition and notifying the Thread.
For predictable code, it's extremely important to get comfortable with how synchronized, wait and notify work.
Next we have our logic for setting and or getting the reference:
$reference =
$this->referee->find($this->ident, $this);
Which invokes this:
public function find(string $ident, Threaded $reference) {
return $this->synchronized(function () use($ident, $reference) {
if (isset($this[$ident])) {
return $this[$ident];
} else return ($this[$ident] = $reference);
});
}
This is badly named, naming things is hard, but you can see that integrity is preserved by synchronization while these grouped operations take place. The same method could also be used to fetch a reference to another object, with a bit of tweaking.
I guess you do something with that particular reference (which is always going to be $this currently). I can't guess what. Moving on ...
I've made the assumption that you'll want to do something with each of these Threads, and you want to preserve the integrity of the data while the entire iteration takes place:
$this->referee->foreach(function($ident, $reference){
var_dump(Thread::getCurrentThreadId(),
$reference->getIdent(),
$reference->isRunning());
});
Which invokes:
public function foreach(Closure $closure) {
$this->synchronized(function() use($closure) {
foreach ($this as $ident => $reference) {
$closure($ident, $reference);
}
});
}
This is how you would do such a thing.
It's worthy of mention that synchronized is not necessarily required here; just as nothing bad will happen if you remove a member from an array you are iterating over, nothing bad will happen if you unset or set or do anything else to an object while iteration occurs.

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 APC problems when storing objects

I am trying to build an configuration parser for my application I installed APC today, but everytime I try to put an serialized object in the store, it does not get in there and does not. (I am checking with apc.php for my version[3.1.8-dev] on PHP 5.3.16 [My Dev Environment], so I am sure that the data is not in the cache). this is how I pass the data to the cacher:
// The data before the caching
array (
'key' => md5($this->filename),
'value' => serialize($this->cfg)
);
// The caching interface
function($argc){
$key = $argc['key'];
Cache\APC::getInstance()->set($key,$argc['value']);
}
// The caching method described above
public function set($key, $val) {
if (apc_exists($key)) {
apc_delete ($key);
return apc_store($key, $val);
}
else
return false;
}
// the constructor of the configuration class.
// It 1st looks for the configuration in
// the cache if it is not present performs the reading from the file.
public function __construct($filename = '/application/config/application.ini',
$type = self::CONFIG_INI)
{
if (defined('SYSTEM_CACHE') && SYSTEM_CACHE === 'APC'){
$key = md5($filename);
$cfg = APC::getInstance()->get($key);
if (!empty($cfg)) {
print "From Cache";
$this->cfg = unserialize($cfg);
return;
} else {
print "From File";
}
}
}
I did a few tests and there is not a problem with the MD5() key (which I thought while writing this question) nor with APC itself. I am really stuck on this one, nothing odd in the logs, so if anyone can give me at least some directions will be very appreciated.
Thanks in advance!
The problem is was in my code:\
public function set($key, $val) {
/*
*
* If the key exists in the cache delete it and store it again,
* but how could it exist when the else clause is like that...
*/
if (apc_exists($key)) {
apc_delete ($key);
return apc_store($key, $val);
}
// This is very wrong in the current case
// cuz the function will never store and the if will
// never match..
else
return false;
}
NOTE:
Always think and keep your eyes open, if you still can't find anything get off the PC and give yourself a rest. Get back after 10-15 minutes and pown the code. It helps! :D

PHP: Blank page, error reporting not functioning

I am running into a blank page, and although I have told PHP to report all errors I still get nothing which leads me to believe it must be a syntax error. I can't find what it is though.
Here is the script I am working on:
test.php
<?php
ini_set('display_errors', '1');
require('database.php');
print("hello");
$config = new Config("lessons.db","data/");
$db = new Database($config, array('first', 'second', 'third', 'fourth'), true);
print_r($db->dumpToArray());
?>
database.php
<?php
class Config {
private
$_db
$_file,
$_directory,
$_delimiter,
$_columns;
public function __construct(string $file, string $directory = null, string $delimiter = "|") {
$_db = $directory.$file;
$_directory = $directory;
$_delimiter = $delimiter;
}
public function db() {
return $_db;
}
public function delimiter() {
return $_delimiter;
}
}
class Database {
private
$_config,
$_columns,
$_rows,
$_pointer;
public function __construct(Config $config, array $constants = null, boolean $caseInsensitive = false) {
$_config = $config;
is_readable ($_config->db())
or exit ("The database cannot be read");
if(!is_null($constants))
$this->defineColumns($constants, $caseInsensitive);
return;
}
private function connect(string $method) {
if (!($_pointer = #fopen($_config->db(), $method)) or printf("Unable to connect to database");
}
private function disconnect() {
fclose($_pointer);
}
private function defineColumns($constants, $caseInsensitive) {
for (var $i=0;$i<count($constants);$i++)
define($constants[i], $i, $caseInsensitive);
}
public function dumpToArray() {
$arrayDump = explode($_config->delimiter(), file($_config->db(), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
return $arrayDump;
}
public function getRowByValue($column, $value) {
$this->connect('r');
$rowNumber = 0;
while (!feof($_Pointer)) {
$row = explode($_config->delimiter(), fgets($dbPointer, 1024));
if ($row[$column] == $value) {
$this->disconnect();
return $row;
}
$rowNumber++;
}
$this->disconnect();
return false;
}
}
?>
Anyone can see what could be causing it?
I think require(database.php); should be require('database.php');.
Try changing that and see if it helps
Also, you're missing a semicolon on
return $arrayDump
EDIT
Okay, I'm not too sure, but try to remove the casting from the parameters of the functions.
So...
public function __construct(Config $config, array $constants = null, boolean $caseInsensitive = false)
Would be
public function __construct($config, $constants = null, $caseInsensitive = false) {
I don't do much OOC in PHP, but just taking another shot.
At first glance... This won't fly
$db = new Database($config, ["first","second","third","fourth"], true);
Your Database class expects an array
$db = new Database($config, array('first', 'second', 'third', 'fourth'), true);
Anyone can see what could be causing it?
Thats most wrong and inefficient way of looking for errors.
you can stare in your code for ages and even ask other people to do it, yet without any success.
Why not to just read the exact error message?
You were on the right track, but gave up.
As Flukey said in the comments, you have to check error log, which by default is the ame as web-server error log.
Watching the actual errors is the only proper way to correct your code.
Tip
If you have a syntax error and are in a situation where the only way you can change the error_reporting/display_errors settings is in the script itself, you can still make it work.
The trick is to make another script that has no syntax errors, and in that script set your config options, and then include the suspected bad script.
<?php
error_reporting(-1); // show all
ini_set('display_errors', 1);
require 'file_with_parse_error.php';
Still, you should generally be able to change these settings in php.ini or webserver config files. And there's should be an error log available to look at in any case.
Then just request the url for that new script in your web browser.
there are other error reporting levels in php.ini - you might want to look into them.
Since you're unable to look at error logs, try throwing a bunch of print statements in your code and see what line is killing your script. If it's killing in your database.php script, throw some prints in and there.
<?php
ini_set('display_errors', '1');
require('database.php');
print "Test1";
print("hello");
print "Test2";
$config = new Config("lessons.db","data/");
print "Test3";
$db = new Database($config, array('first', 'second', 'third', 'fourth'), true);
print "Test4";
print_r($db->dumpToArray());
?>

error in running recursion

if running function returns server misconfiguration error
function build_path($cid)
{
$result = array();
$DB = new MySQLTable;
$DB->TblName = 'shop_categories';
$where['cat_id']['='] = $DB->CleanQuest($cid);
$res = $DB->Select('cat_id,cat_name,cat_parent', $where);
if($res !== 'false')
{
$pid = mysql_fetch_array($res);
if($pid['cat_parent'] !== 0)
{
Echo $pid['cat_parent'];
build_path($pid['cat_parent']);
} else {
Echo $pid['cat_id'];
return true;
}
}
return false;
}
I can't find an error here. Please help.
Sorry for disturbing you all. The trouble was in comparison 'if($pid['cat_parent'] !== 0)': $pid['cat_parent'] was a string with int(0)
Can i build this function to store full path without using GLOBALS and SESSION vars?
Things to check for:
Is cat_parent ever null?
Are there any recursively parented pairs (A is parent of B, B is parent of A; A->B, B->C, C->A)?
Are there any cases where the row is self parented (A is parent of A)
Try making this modification (for debugging):
function build_path($cid) {
static $done = array();
if (isset($done[$cid])) {
throw new Exception('We have already looked up CID: '.$cid);
} else {
$done[$cid] = true;
}
//.......
}
That will let you tell if you've got a recursive loop going on.
Also, set error_reporting to max, and check your error log. My guess is that it's either a stack overflow (your recursion is going too deep) or a memory limit issue (that it uses up too much memory and is killed)...

Categories