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.
Related
I'm tryin to document a my PHP library with doxygen, but I cannot configure it to let recognize the correct usage of member function of my class, using the keyword "self". For instance the following code:
Class myclass{
public static function myfunc1(){
return 10; }
public static function myfunc2(){
return self::myfunc1(); }
}
is not correctly documented. Doxygen maps the two funcions, but when it refer to the internal or external call to these function, it doesn't take in account the myfunc1 called by myfunc2.
My workaround at the moment is to changed the code as follows:
Class myclass{
public static function myfunc1(){
return 10; }
public static function myfunc2(){
return myclass::myfunc1(); }
}
In this case doxygen refers correctly to the usage of myfunc1 related to myfunc2. Of course I don't like very much this solution. How can I solve this issue?
thank you very much
Doxygen provides input filter option, which enables us to change the source code at the time of documentation creation. For e.g. It gives us ability to intercept the code and change the code self::myfunc1 to myclass::myfunc1 on fly, which Doxygen understands. It does not change the actual source code in any way.
I have created a filter based on code from Doxygen PHP Filters with some modifications, which can do the changes for you.
Please create a file /path/to/selfFilter.php and put the code inside it:
<?php
//Create file /path/to/selfFilter.php
$source = file_get_contents($argv[1]);
$tokens = token_get_all($source);
$classes = array();
foreach($tokens as $key => $token)
{
if($token[0] == T_CLASS)
$classes[] = $tokens[$key+2][1];
}
if(!empty($classes))
{
list($source, $tail) = explode('class ' . $classes[0], $source, 2);
$class_code = '';
for($i = 1; $i < count($classes); $i++)
{
list($class_code, $tail) = explode('class ' . $classes[$i], $tail, 2);
$class_code = preg_replace('#\bself::#', $classes[$i-1].'::', $class_code);
$source .= 'class ' . $classes[$i-1] . $class_code;
}
$class_code = preg_replace('#\bself::#', $classes[count($classes)-1].'::', $tail);
$source .= 'class ' . $classes[count($classes)-1] . $class_code;
}
echo $source;
Update the following options in your doxygen config file.
INPUT_FILTER = "php /path/to/selfFilter.php"
FILTER_PATTERNS =
FILTER_SOURCE_FILES = YES
FILTER_SOURCE_PATTERNS =
Please make sure that the /path/to/selfFilter.php is executable and php is available on the path, otherwise use the full php path
INPUT_FILTER = /usr/bin/php /path/to/selfFilter.php
If you run the Doxygen now it should work. Please let me know if you have any issues.
Note: The keyword 'class' should be defined in lower case for the above filters to work.
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.
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).
i am wondering is it possible to restrict php variable to accept only certain type of variable it is defined to accept.
e-g if we see in C#
public int variable = 20;
public string variable2 = "Hello World";
so what would it be like in php if i want to restrict type for variable.
public int $variable = 20;
so that if i try to assign string to integer variable i get the error.
public int $varaible = "Hello World"; //As type is integer, it should throw error.
is there such thing defining types in PHP for variables before assigning values to it??
TL;DR Not directly, no. PHP is not strictly-typed. There are, however, a few workarounds that may work for you in the context of function parameters or properties of classes.
Long answer: PHP is not a strictly-typed language, but loosely-typed. You can give a variable any value you want, regardless of how it was initialized. So, you can't simply type something like int $myVar and expect $myVar = "foo"; to throw an error. But PHP does offer a few handy features to get you to the same end when dealing with function parameters or properties of a class.
Option 1: Type hints
You can use a "type hint" for function parameters:
class SomeClass
{
/* code here */
}
function foo(SomeClass $data)
{
/* code here */
}
foo() will only accept parameters of type SomeClass. Passing it, say, an int will throw a fatal error. This doesn't work in PHP < 7 if the parameters are intended to be base types, like int, string, etc., so you can't do function foo(int $data) {...}. That said, there are a few libraries out there that attempt to force it to work at the expense of a little speed. Also, PHP 7 adds a lot of support for this kind of thing, as does the Hack language based on PHP.
Pros:
Easy
Intuitive
Cons:
Only works for program-defined classes
Unavailable for base types
Option 2: Getters and Setters
You can also use getters and setters, like so:
class SomeClass
{
private $foo = 0;
function setFoo($val = 0)
{
// force it to be an int
if (is_integer($val) {
$this->foo = $val;
} else {
// throw an error, raise an exception, or otherwise respond
}
}
}
Pros:
Relatively easy
Relatively intuitive
Cons:
Only works in program-defined classes
Unavailable for base types
Requires lots of code
Option 3: Magic Methods
This method is my favorite, but also the most complicated. Use the __set() magic method to deal with class properties.
class MyClass {
private $type = 0; // we will force this to be an int
private $string = ''; // we will force this to be a string
private $arr = array(); // we will force this to be an array
private $percent = 0; // we will force this to be a float in the range 0..100
function __set($name, $value) {
switch ($name) {
case "type":
$valid = is_integer($value);
break;
case "string":
$valid = is_string($value);
break;
case "arr":
$valid = is_array($value);
break;
case "percent":
$valid = is_float($value) && $value >= 0 && $value <= 100;
break;
default:
$valid = true; // allow all other attempts to set values (or make this false to deny them)
}
if ($valid) {
$this->{$name} = $value;
// just for demonstration
echo "pass: Set \$this->$name = ";
var_dump($value);
} else {
// throw an error, raise an exception, or otherwise respond
// just for demonstration
echo "FAIL: Cannot set \$this->$name = ";
var_dump($value);
}
}
}
$myObject = new MyClass();
$myObject->type = 1; // okay
$myObject->type = "123"; // fail
$myObject->string = 1; // fail
$myObject->string = "123"; // okay
$myObject->arr = 1; // fail
$myObject->arr = "123"; // fail
$myObject->arr = array("123"); // okay
$myObject->percent = 25.6; // okay
$myObject->percent = "123"; // fail
$myObject->percent = array("123"); // fail
$myObject->percent = 123456; // fail
Pros:
Relatively easy
Intuitive
Extremely powerful: one setter to rule them all
Cons:
Only works in program-defined classes
Unavailable for base types
Requires lots of switching or if/else logic
Can cause problems with IDEs not auto-completing property types correctly
Here's a demo of this approach.
Closing Thoughts
Finally, if you're using an IDE like PHPStorm, don't forget about PHPDoc type hints:
/* #var integer */
$foo = 0; // will result in warnings if the IDE is configured properly and you try to do something like substr($foo, 1, 4);
And if you really want to go hard core, you can do strong typing using Hack, at the expense of making your code less portable and less compatible (for now) with major IDEs.
Of course, none of these is a substitute for explicitly validating user input and thoroughly testing the application's response to unexpected input types.
No. PHP is not a strictly typed language. You can however use type hints in functions and methods.
If class or interface is specified as type hint then all its children or implementations are allowed too.
Type hints can not be used with scalar types such as int or string. Resources and Traits are not allowed either.
The Scalar types being:
string
bool
int
float
Examples:
function (array $theArr) {
// body
}
class X {
public function __construct(SomeOtherClass $arg) {
// body
}
public function setFoo(Foo $foo) {
}
}
See the manual for more specifics: http://php.net/manual/en/language.oop5.typehinting.php
You have to made it by your own hands, example :
function setInt(&$var, $value) {
if(!is_integer($value) {
throw new Exception("Integer wanted " . gettype($value) . " received");
}
$var = $value;
}
I am using the test code found # http://code.google.com/apis/youtube/2.0/developers_guide_php.html to create a playlist:
$newPlaylist = $yt->newPlaylistListEntry();
$newPlaylist->summary = $yt->newDescription()->setText($desc);
$newPlaylist->title = $yt->newTitle()->setText($title);
// post the new playlist
$postLocation = 'http://gdata.youtube.com/feeds/api/users/default/playlists';
try {
$playlist = $yt->insertEntry($newPlaylist, $postLocation);
}
catch (Zend_Gdata_App_Exception $e) {
echo $e->getMessage();
}
The playlist is created, but how can I get the id or url of the playlist that was just created?
I have the same problem. I have managed to get a bit further but I still can't get the playlistID. Here is what I did:
instead of:
$playlist = $yt->insertEntry($newPlaylist, $postLocation);
I used :
$playlist = $yt->insertEntry($newPlaylist, $postLocation, 'Zend_Gdata_YouTube_PlaylistListEntry');
But when I try to get the id by $playlist->getPlaylistID() or $playlist->playlistId->text I get the same exception which says :
The yt:playlistId element is not supported in versions earlier than 2.
even if I have set it earlier with $yt->setMajorProtocolVersion(2);
This is a complete and utter hack, I had the same exact problem, so I went into the class at Zend/Gdata/YouTube/PlaylistListEntry.php on line 229 I commented out the if else statement.
/**
* Returns the Id relating to the playlist.
*
* #throws Zend_Gdata_App_VersionException
* #return Zend_Gdata_YouTube_Extension_PlaylistId The id of this playlist.
*/
public function getPlaylistId()
{
/*if (($this->getMajorProtocolVersion() == null) ||
($this->getMajorProtocolVersion() == 1)) {
require_once 'Zend/Gdata/App/VersionException.php';
throw new Zend_Gdata_App_VersionException('The yt:playlistId ' .
'element is not supported in versions earlier than 2.');
} else {*/
return $this->_playlistId;
//}
}
I would LOVE for someone to show us how to fix this the right way, but this made it so
function printPlaylistListEntry($playlistListEntry, $showPlaylistContents = false)
{
$this->yt->setMajorProtocolVersion(2);
echo '<br>Title: ' . $playlistListEntry->title->text . "\n";
echo '<br>Description: ' . $playlistListEntry->description->text . "\n";
echo '<br>playlistId: ' . $playlistListEntry->playlistId->text . "\n";
... (from the youtube v2 php api).
will return the playlistid.
Title: therighttitle
Description: therightdescription
playlistId: therightplaylistId
edit: I think this may be a better solution:
if ($this->getMajorProtocolVersion() < 2) {
require_once 'Zend/Gdata/App/VersionException.php';
throw new Zend_Gdata_App_VersionException('The yt:playlistId ' .
'element is not supported in versions earlier than 2.');
} else {
return $this->_playlistId;
}
replace the getPlaylistId() function with this, follows the logic of the preceding getDescription function, and its less hacky. Again completely open to critiques on why this is or is not a good idea from the zend people.
You don't need any special hacks to make this work. You just need to explicitly set the protocol version for the $playlist variable, along with the $yt variable. As you stated, set the major protocol version for $yt earlier:
$yt->setMajorProtocolVersion(2);
Then after you initialize $playlist, set the protocol on that as well:
$playlist = $yt->insertEntry($newPlaylist, $postLocation, 'Zend_Gdata_YouTube_PlaylistListEntry');
$playlist->setMajorProtocolVersion(2);
Once you do this, you should be able to get your playlist ID no problem :)
$playlist_id = $playlist->getPlaylistID();