So I'm not sure if this is buggy design with PHP, or if there is an understood logic to handling inconsistent outcomes for the same interface.
The SeekableIterator interface has two methods (seek and valid) that are either in conflict with one another or should be working consistently with each other, but I'm seeing both.
The documentation for the interface says that seek should throw an exception of class OutOfBoundsException, but this seems to negate the usefulness of valid unless the iterator position is updated (making valid return false) before throwing the exception (which apparently must be caught).
Three test examples
Example 1.
Custom class implementing SeekableIterator, as provided by example in docs:
The class:
class MySeekableIterator implements SeekableIterator {
private $position;
private $array = array(
"first element",
"second element",
"third element",
"fourth element"
);
/* Method required for SeekableIterator interface */
public function seek($position) {
if (!isset($this->array[$position])) {
throw new OutOfBoundsException("invalid seek position ($position)");
}
$this->position = $position;
}
/* Methods required for Iterator interface */
public function rewind() {
$this->position = 0;
}
public function current() {
return $this->array[$this->position];
}
public function key() {
return $this->position;
}
public function next() {
++$this->position;
}
public function valid() {
return isset($this->array[$this->position]);
}
}
Example 1. Test :
echo PHP_EOL . "Custom Seekable Iterator seek Test" . PHP_EOL;
$it = new MySeekableIterator;
$it->seek(1);
try {
$it->seek(10);
echo $it->key() . PHP_EOL;
echo "Is valid? " . (int) $it->valid() . PHP_EOL;
} catch (OutOfBoundsException $e) {
echo $e->getMessage() . PHP_EOL;
echo $it->key() . PHP_EOL; // outputs previous position (1)
echo "Is valid? " . (int) $it->valid() . PHP_EOL;
}
Test 1 Output:
Custom Seekable Iterator seek Test
invalid seek position (10)
1
Is valid? 1
Example 2:
Using native ArrayIterator::seek
Test 2 Code:
echo PHP_EOL . "Array Object Iterator seek Test" . PHP_EOL;
$array = array('1' => 'one',
'2' => 'two',
'3' => 'three');
$arrayobject = new ArrayObject($array);
$iterator = $arrayobject->getIterator();
$iterator->seek(1);
try {
$iterator->seek(5);
echo $iterator->key() . PHP_EOL;
echo "Is valid? " . (int) $iterator->valid() . PHP_EOL;
} catch (OutOfBoundsException $e) {
echo $e->getMessage() . PHP_EOL;
echo $iterator->key() . PHP_EOL; // outputs previous position (1)
echo "Is valid? " . (int) $iterator->valid() . PHP_EOL;
}
Test 2 Output:
Array Object Iterator seek Test
Seek position 5 is out of range
1
Is valid? 1
Example 3:
Using native DirectoryIterator::seek
Test 3 Code:
echo PHP_EOL . "Directory Iterator seek Test" . PHP_EOL;
$dir_iterator = new DirectoryIterator(dirname(__FILE__));
$dir_iterator->seek(1);
try {
$dir_iterator->seek(500); // arbitrarily high seek position
echo $dir_iterator->key() . PHP_EOL;
echo "Is valid? " . (int) $dir_iterator->valid() . PHP_EOL;
} catch (OutOfBoundsException $e) {
echo $e->getMessage() . PHP_EOL;
echo $dir_iterator->key() . PHP_EOL;
echo "Is valid? " . (int) $dir_iterator->valid() . PHP_EOL;
}
Test 3 Output:
Directory Iterator seek Test
90
Is valid? 0
So how would one reasonably expect to know whether to use valid() to confirm valid position after seek($position) while also anticipating that the seek() might throw an Exception instead of updating the position, so that valid() returns true?
It seems that the directoryIterator::seek() method here is not implemented with an exception. Instead it will just return no value, and let valid() handle it.
Your other example, the ArrayObject::seek() does work "correctly" and throws an OutOfBoundsException.
The reasoning is simple: the ArrayObject (and most likely, most custom implementations too) will know beforehand how many elements it contains, and thus can quickly check its bounds. The DirectoryIterator however, must read the directory entities from disk one by one in order to reach the given position. It does so by literally calling valid() and next() in a loop. This is the reason why the key() has changed, and valid() returns 0.
The other iterators will not even touch the current iterator state, and can quickly decide if your request falls in its range or not.
On a sidenote: if you want to seek a position in the DirectoryIterator backwards, it will reset the iterator first, and then starts iterating each element again. So if you are on position 1000, and do a $it->seek(999), it will actually iterate 999 elements again.
IMHO, The DirectoryIterator is not a good implementation of the seekableIterator interface. It's intended to quickly jump to a certain element within the iterator and clearly, with a directoryIterator this is not something that is doable. Instead, a full iteration must be done, which results in a changed iterator state.
The seekableIterator interface is useful for filterIterators that do something with the range of the iterator. Within the SPL, this is only the LimitIterator. When you do:
$it = new ArrayIterator(range('a','z'));
$it = new LimitIterator($it, 5, 10));
When the limitIterator detects that the given iterator has implemented the seekableIterator interface, it will call seek() to quickly jump to the 5th element, otherwise it will just iterate until it reached the 5th element.
Conclusion: do not use the seekableIterator when you cannot quickly jump to a position or check bounds. At best you gain nothing, at worst you get iterators that change state without knowing why.
To answer your question: seek() should throw an exception and not change state. The directoryIterator (maybe some others too) should be changed to either not implementing seekableIterator, or by finding out how many entries there are prior to seek() (but that doesn't fix the 'rewind` when seeking backwards problem).
Related
Suppose I have the following PHP code:
class Foo {
function getBar() {
return 1;
}
}
function check( Foo $foo ) {
if ( $foo->getBar() == 1 ) {
// here could be more code ...
return 'Oh no, there was an error in class' .
get_class( $foo ) . ', method ' .
'getBar';
}
}
The last string in check bothers me because if Foo::bar gets renamed by a refactoring tool, the error message will be wrong. Is there any way to get around this without using a string somewhere?
You can use __METHOD__ to get the name of the current method.
But to get reference to other method that would allow you some kind of automatic refactoring - no, it's not possible in php.
Can be done by using method_exists()
class Foo {
function getBar() {
return 1;
}
}
function check( Foo $foo , $method = 'getBar') {
if (!method_exists($foo, $method) ) {
// here could be more code ...
return 'Oh no, there was an error in class' .
get_class( $foo ) . ', method ' .
$method;
}
}
It is not possible in PHP per se, but you can implement such a feature. One possible implementation would work as follows: somewhere the file path, class name, method name and some kind of a description of where and what should match what. Your new feature whenever triggered would check the given files, check whether some values changed, fix whatever needs to be fixed and log a report about the task. It would not be simple to implement something like this, but, important to note is that there is a solution.
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.
I am using PHP 5.3.6 from MAMP.
I have a use case where it would be best to use PHP's Iterator interface methods, next(), current(), and valid() to iterate through a collection. A foreach loop will NOT work for me in my particular situation. A simplified while loop might look like
<?php
while ($iter->valid()) {
// do something with $iter->current()
$iter->next();
}
Should the above code always work when $iter implements PHP's Iterator interface? How does PHP's foreach keyword deal with Iterators?
The reason I ask is that the code I am writing may be given an ArrayIterator or a MongoCursor. Both implement PHP's Iterator interface but they behave differently. I would like to know if there is a bug in PHP or PHP's Mongo extension.
ArrayIterator::valid() returns true before any call to next() -- immediately after the ArrayIterator is created.
MongoCursor::valid() only returns true after the first call to next(). Therefore the while loop above will never execute.
At risk of being verbose, the following code demonstrates these assertions:
<?php
// Set up array iterator
$arr = array("first");
$iter = new \ArrayIterator($arr);
// Test array iterator
echo(($iter->valid() ? "true" : "false")."\n"); // Echoes true
var_dump($iter->current()."\n"); // "first"
$iter->next();
echo(($iter->valid() ? "true" : "false")."\n"); // Echoes false
// Set up mongo iterator
$m = new \Mongo();
$collection = $m->selectDB("iterTest")->selectCollection("mystuff");
$collection->drop(); // Ensure collection is empty
$collection->insert(array('a' => 'b'));
$miter = $collection->find(); // should find one object
// Test mongo iterator
echo(($miter->valid() ? "true" : "false")."\n"); // Echoes false
$miter->next();
echo(($miter->valid() ? "true" : "false")."\n"); // Echoes true
var_dump($miter->current()); // Array(...)
Which implementation is correct? I found little documentation to support either behavior, and the official PHP documentation is either ambiguous or I'm reading it wrong. The doc for Iterator::valid() states:
This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid.
This would suggest that my while loop should first call next().
Yet the PHP documentation for Iterator::next states:
This method is called after each foreach loop.
This would suggest that my while loop is correct as written.
To summarize - how should PHP iterators behave?
This is an interesting question. I'm not sure why a foreach won't work for you, but I have some ideas.
Take a look at the example given on the Iterator interface reference page. It shows the order in which PHP's internal implementation of foreach calls the Iterator methods. In particular, notice that when the foreach is first set up, the very first call is to rewind(). This example, though it's not well-annotated, is the basis for my answer.
I'm not sure why a MongoCursor would not return true for valid() until after next() is called, but you should be able to reset either type of object by calling rewind() prior to your loop. So you would have:
// $iter may be either MongoCursor or ArrayIterator
$iter->rewind();
while( $iter->valid() ){
// do something with $iter->current()
$iter->next();
}
I believe this should work for you. If it does not, the Mongo class may have a bug in it.
Edit: Mike Purcell's answer correctly calls out that ArrayIterator and Iterator are not the same. However, ArrayIterator implements Iterator, so you should be able to use rewind() as I show above on either of them.
Subclassing any Iterator and echo'ing when it's called will tell you how it behaves.
Example (demo)
class MyArrayIterator extends ArrayIterator
{
public function __construct ($array)
{
echo __METHOD__, PHP_EOL;
parent::__construct($array);
}
…
}
foreach (new MyArrayIterator(range(1,3)) as $k => $v) {
echo "$k => $v", PHP_EOL;
}
Output
MyArrayIterator::__construct
MyArrayIterator::rewind
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
0 => 1
MyArrayIterator::next
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
1 => 2
MyArrayIterator::next
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
2 => 3
MyArrayIterator::next
MyArrayIterator::valid
This is equivalent to doing
$iterator = new MyArrayIterator(range(1,3));
for ($iterator->rewind(); $iterator->valid(); $iterator->next()) {
echo "{$iterator->key()} => {$iterator->current()}", PHP_EOL;
}
The sequence in which the methods are called is identical to a custom Iterator:
class MyIterator implements Iterator
{
protected $iterations = 0;
public function current()
{
echo __METHOD__, PHP_EOL;
return $this->iterations;
}
public function key ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations;
}
public function next ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations++;
}
public function rewind ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations = 0;
}
public function valid ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations < 3;
}
}
foreach (new MyIterator as $k => $v) {
echo "$k => $v", PHP_EOL;
}
It seems you may have mixed up Iterator and ArrayIterator, as each has their own valid() api call.
Array Iterator : There is no specific mention to next() etc, but the example clearly demonstrates what you mentioned in your OP, to paraphrase; that no other AI api call needs to be made to determine if current element of array is valid.
Iterator : As you mention: "This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid."
As such, I would stick with ArrayIterator, as it demonstrates the most correct behavior, in that valid() will correctly determine if current element in array is valid, without having to make another api call (next, rewind).
If you want get Mongo to behave like AI does, you could add an instance check before starting the while loop:
if ($iter instanceof MongoCursor) {
$iter->next()
}
while ($iter->valid()) {
// Do stuff
}
I'm learning OO PHP and am trying to get some of the coding practices straight. Here is a pared down version of some code I'm using for error (and exception) handling:
final class MyErrorExceptionHandler {
private $level = array(); // error levels to be handled as standard errors
private $path = array(); // full path to file
private $path_short; // filename plus working dir
public function myErrorHandler($severity, $message, $file, $line) {
if (error_reporting() & $severity) { // error code is included in error_reporting
$this->level = array(E_WARNING => 'warning',
E_NOTICE => 'notice',
E_USER_WARNING => 'user warning',
E_USER_NOTICE => 'user notice');
if (array_key_exists($severity, $this->level)) { // handle as standard error
/*$this->severity = $severity;
$this->message = $message;
$this->file = $file;
$this->line = $line;*/
$this->printMessage($severity, $message, $file, $line);
} else { // fatal: E_USER_ERROR or E_RECOVERABLE_ERROR use php's ErrorException converter
throw new ErrorException($message, 0, $severity, $file, $line);
}
}
} // fn myErrorHandler
private function printMessage($severity, $message, $file, $line) {
echo ucfirst($this->level[$severity]) . ': ' . $message;
$this->shortenPath($file);
echo ' in ' . $this->path_short . ' on line ' . $line;
} // fn printMessage
private function shortenPath($file) {
$this->path_short = $file;
$this->path = explode(DIRECTORY_SEPARATOR, $file);
if (count($this->path) > 2) { // shorten path to one dir, if more than one dir
$this->path_short = array_pop($this->path); // filename
$this->path_short = end($this->path) . DIRECTORY_SEPARATOR . $this->path_short; // dir+file
}
} // fn shortenPath
} // cl MyErrorExceptionHandler
The title of this question is probably a little bit off because I'm not 100% on the terminology. Basically I'm trying to figure out a few things.
Is it right to explicitly declare $level and $path as arrays?
Should $level be declared as it is (and made $this->level)? If so, have I assigned its value (E_WARNING etc.) in a wise place? Would the constructor (not shown here) be a smarter choice?
Note the commented block in myErrorHandler(). Originally I had declared all of these properties at the top of the class, and then called $this->printMessage() without any parameters. Which is the more correct way? If I keep the code as is, would I want to then use $this->severity = $severity etc. inside printMessage()?
So, would it be better to:
replace
$this->shortenPath($file);
echo ' in ' . $this->path_short . ' on line ' . $line;
with
$path_short = $this->shortenPath($file);
echo ' in ' . $path_short . ' on line ' . $line;
ultimately, and give a return value in shortenPath()?
I realize this is a mishmash of several different questions, but what I'm trying to get at is a common inquiry about the proper style of declaring/using variables/properties, specifically when dealing with methods.
To summarize: When should I use $this->foo = $foo?
EDIT: sorry, I have assumed below that you would create a new instance of the 'object' with each error which obviously you are not doing. Just edited my answer to reflect this.
"When should I use $this->foo = $foo?"
There can be several cases in which you would do this, but it's usually if you create $foo within a method and wish to have that then accessed by the entire object.
For example, if you wanted to call on an object and use that within this particular object (if it doesn't make sense to extend). You would do something like:
$foo = new DataModel();
$this->foo = $foo;
OR
$this->foo = new DataModel();
That object may be a decorator or something else related to error handling and the above code would usually feature in your constructor. You could then access the methods of that object any time by using:
$this->foo->objectMethod();
..and to express something noted in the comments to this answer:
"would you assign $file to the object as that is used in several methods?"
I wouldn't assign $file to the object,
here's why. The semantics of the word
"property" means "belongs to". In your
case, your class is a error handler.
$file doesn't belong to the error
handler, it belongs to an error
instance. If your class was
MyErrorHandler_Error (created for each
instance of a triggered error), then
$file would be a property of that
class, along with $line and $level.
To answer what I can from your other questions:
It's neither. I would consider it preference.
Yes - any variables or values which should be available to your entire object and required for the object to run properly, should probably be set within your constructor, if not within your variable declarations (not sure of terminology there) at the top of the class.
read the comments below. Because this particular class deals with multiple instances of errors - assigning the properties of those errors to the object wouldn't be best practice as you will be overwriting them with each new error. However, it does make sense to store all of your errors and error properties within an array assigned to the object if you require to access historical data. For example, at the moment, if you create a new error - that is all you are doing. You have no way of accessing any old errors this object has created.
see above
You should also think about conflicts when assigning properties to objects. Are you likely to reassign, because if so, the old property will be gone. Fairly simple but still something you have to consider.
If I want to implement seek() to complete the SeekableIterator interface, should I internally revert to the old position if the seeked position is invalid? Like this:
public function seek( $position )
{
$oldPosition = $this->_position;
$this->_position = $position;
if( !$this->valid() )
{
$this->_position = $oldPosition;
throw new OutOfBoundsException( 'Invalid seek position (' . $position . ')' );
}
}
If you use the php.net interface reference's example implementation as a guideline, then no, you should not "revert" to the original position, even if the supplied target position isn't valid.
According to the sample code from the SPL documentation, you should still change the position.