danielstjules / Stringy

A PHP string manipulation library with multibyte support
MIT License
2.46k stars 216 forks source link

PHP does not recognize Stringy as string #167

Open apapsch opened 7 years ago

apapsch commented 7 years ago

Please take a look at this constructor from https://github.com/lanthaler/IRI/blob/a04d4f923700dc5b4a19e1e105f978b50647efaa/IRI.php#L80

public function __construct($iri = null)
{
    if (null === $iri) {
        return;
    } elseif (is_string($iri)) {
        $this->parse($iri);
    } elseif ($iri instanceof IRI) {
        $this->scheme = $iri->scheme;
        $this->userinfo = $iri->userinfo;
        $this->host = $iri->host;
        $this->port = $iri->port;
        $this->path = $iri->path;
        $this->query = $iri->query;
        $this->fragment = $iri->fragment;
    } else {
        throw new \InvalidArgumentException(
            'Expecting a string or an IRI, got ' .
            (is_object($iri) ? get_class($iri) : gettype($iri))
        );
    }
}

I naively wrote this:

$foo = new Stringy('bar');
$baz = new IRI($foo);

and got that:

 InvalidArgumentException: Expecting a string or an IRI, got Stringy\Stringy

The solution is explicit type conversion:

$baz = new IRI((string)$foo);

My naive approach was done under the impression that Stringy should act like a PHP string, even though it is an object. How would you approach this issue? I can imagine:

  1. Do explicit type conversion everywhere. But then you litter your code and possibly copy strings when you could pass them by reference.
  2. Limit Stringy usage. But then you don't have the nice API and don't get strings passed by reference most of the time.
  3. Do some magic in PHP core in order for code to treat string classes as strings without needing changes (i.e. no littering with (string)). That could include an interface which is_string recognizes and returns true for. Also, an instance of the class implementing the interface would be treated as a PHP string most of the time, except when methods are invoked on the instance. And the string object would be passed by reference of course.

Approaches 1 and 2 seem like hacks, though they are in reach. Approach 3 is the best from user perspective but I don't know if it's viable. It might entail a ton of changes in PHP core.

ArchitectNate commented 7 years ago

Peter Barney has a decent solution, replace your is_string() calls with is_stringy() calls http://php.net/manual/en/function.is-string.php#121637

<?php
// determine if the passed argument can be treated like a string.

function is_stringy($text) {

    return (is_string($text) || (is_object($text) && method_exists($text, '__toString' ));
}

This won't solve TypeHinting, especially with strict typing.

apapsch commented 7 years ago

That's doable, although not feasable for third party code. It might be worth pursuing this extension of is_string in PHP core, WDYT?

Sweetchuck commented 2 years ago

Slightly related: