2021/11/24/things you can't do in PHP: Difference between revisions

From Woozle Writes Code
Jump to navigation Jump to search
No edit summary
No edit summary
 
Line 2: Line 2:
==Take 1: my bad==
==Take 1: my bad==
This is where I started -- and admittedly there was already a problem in that I'm <code>use</code>ing '''taScalarAccess''' twice in '''cScalarReadOnly''' (once each via '''cScalarLocal''' and '''taReadOnlyScalar'''). The idea, though, was to override the SetIt() and ClearIt() methods in whatever class was ''using'' '''taReadOnlyScalar'''. Those methods being implemented in '''cScalarLocal''', the <code>use</code> of the '''taReadOnlyScalar''' trait was intended to rename those methods with a "_" prefix and call them conditionally from the replacement methods.
This is where I started -- and admittedly there was already a problem in that I'm <code>use</code>ing '''taScalarAccess''' twice in '''cScalarReadOnly''' (once each via '''cScalarLocal''' and '''taReadOnlyScalar'''). The idea, though, was to override the SetIt() and ClearIt() methods in whatever class was ''using'' '''taReadOnlyScalar'''. Those methods being implemented in '''cScalarLocal''', the <code>use</code> of the '''taReadOnlyScalar''' trait was intended to rename those methods with a "_" prefix and call them conditionally from the replacement methods.
<syntaxhighlight lang=php line start=2>
<syntaxhighlight lang=php line start=2 highlight=27>
trait taScalarAccess {
trait taScalarAccess {
     abstract public function SetIt(mixed $v);
     abstract public function SetIt(mixed $v);
Line 38: Line 38:
==Take 2: decouple the conflicting traits==
==Take 2: decouple the conflicting traits==
First, I tried moving <code>use taScalarAccess</code> out of '''tScalarLocal''', even though I wanted the latter to descend from the former because of a method implemented in the former (not shown). Classes that <code>use</code> '''tScalarLocal''' would also need to explicitly <code>use</code> '''taScalarAccess''' in order to get that functionality.
First, I tried moving <code>use taScalarAccess</code> out of '''tScalarLocal''', even though I wanted the latter to descend from the former because of a method implemented in the former (not shown). Classes that <code>use</code> '''tScalarLocal''' would also need to explicitly <code>use</code> '''taScalarAccess''' in order to get that functionality.
<syntaxhighlight lang=php line start=2>
<syntaxhighlight lang=php line start=2 highlight=26>
trait taScalarAccess {
trait taScalarAccess {
     abstract public function HasIt() : bool;
     abstract public function HasIt() : bool;
Line 74: Line 74:
==Take 3: interface==
==Take 3: interface==
Next, I tried moving the abstract functions from '''taScalarAccess''' into an <code>interface</code>, to avoid the collision. (Had this worked, it [[PHP wishlist/trait implements|would have been nice if traits could implement interfaces]], but it didn't so the point is moot as far as this issue goes.)
Next, I tried moving the abstract functions from '''taScalarAccess''' into an <code>interface</code>, to avoid the collision. (Had this worked, it [[PHP wishlist/trait implements|would have been nice if traits could implement interfaces]], but it didn't so the point is moot as far as this issue goes.)
<syntaxhighlight lang=php line start=2>
<syntaxhighlight lang=php line start=2 highlight=14>
interface ifScalarAccess {
interface ifScalarAccess {
     function HasIt() : bool;
     function HasIt() : bool;

Latest revision as of 14:02, 25 November 2021

Codeblog

Take 1: my bad

This is where I started -- and admittedly there was already a problem in that I'm useing taScalarAccess twice in cScalarReadOnly (once each via cScalarLocal and taReadOnlyScalar). The idea, though, was to override the SetIt() and ClearIt() methods in whatever class was using taReadOnlyScalar. Those methods being implemented in cScalarLocal, the use of the taReadOnlyScalar trait was intended to rename those methods with a "_" prefix and call them conditionally from the replacement methods.

trait taScalarAccess {
    abstract public function SetIt(mixed $v);
    abstract public function GetIt() : mixed;
    abstract public function HasIt() : bool;
    abstract public function ClearIt();
}
trait tScalarLocal {
    use taScalarAccess;
    public function GetIt() : mixed {}
    public function HasIt() : bool {}
    public function SetIt(mixed $v) {}
    public function ClearIt() {}
}
trait taReadOnlyScalar {
    use taScalarAccess {
      taScalarAccess::SetIt as protected _SetIt;
      taScalarAccess::ClearIt as protected _ClearIt;
      }
    public function SetIt(mixed $v) { if ($something) { $this->_SetIt($v); } }
    public function ClearIt() { if ($something) { $this->_ClearIt(); } }
}

class cScalarLocal {
    use taScalarAccess;
    use tScalarLocal;
}
class cScalarReadOnly extends cScalarLocal {
    use tScalarLocal;
    use taReadOnlyScalar;
}

Result

PHP Fatal error: Trait method SetIt has not been applied, because there are collisions with other trait methods on cScalarReadOnly in /home/htnet/site/git/ferreteria/base/tests/php2.php on line 28

Take 2: decouple the conflicting traits

First, I tried moving use taScalarAccess out of tScalarLocal, even though I wanted the latter to descend from the former because of a method implemented in the former (not shown). Classes that use tScalarLocal would also need to explicitly use taScalarAccess in order to get that functionality.

trait taScalarAccess {
    abstract public function HasIt() : bool;
    abstract public function GetIt() : mixed;
    abstract public function SetIt(mixed $v);
    abstract public function ClearIt();
}
trait tScalarLocal {
    public function HasIt() : bool {}
    public function GetIt() : mixed {}
    public function SetIt(mixed $v) {}
    public function ClearIt() {}
}
trait taReadOnlyScalar {
    use taScalarAccess {
      taScalarAccess::SetIt as protected _SetIt;
      taScalarAccess::ClearIt as protected _ClearIt;
      }
    public function SetIt(mixed $v) { if ($something) { $this->_SetIt($v); } }
    public function ClearIt() { if ($something) { $this->_ClearIt(); } }
}

class cScalarLocal {
    use taScalarAccess;
    use tScalarLocal;
}
class cScalarReadOnly extends cScalarLocal {
    use tScalarLocal;
    use taReadOnlyScalar;
}

Result

PHP Fatal error: Trait method SetIt has not been applied, because there are collisions with other trait methods on cScalarReadOnly in /home/htnet/site/git/ferreteria/base/tests/php2.php on line 27

i.e. that didn't fix the problem.

Take 3: interface

Next, I tried moving the abstract functions from taScalarAccess into an interface, to avoid the collision. (Had this worked, it would have been nice if traits could implement interfaces, but it didn't so the point is moot as far as this issue goes.)

interface ifScalarAccess {
    function HasIt() : bool;
    function GetIt() : mixed;
    function SetIt(mixed $v);
    function ClearIt();
}
trait taScalarAccess {}
trait tScalarLocal {
    public function HasIt() : bool {}
    public function GetIt() : mixed {}
    public function SetIt(mixed $v) {}
    public function ClearIt() {}
}
trait taReadOnlyScalar {
    use taScalarAccess {
      taScalarAccess::SetIt as protected _SetIt;
      taScalarAccess::ClearIt as protected _ClearIt;
      } //*/
    public function SetIt(mixed $v) { if ($something) { $this->_SetIt($v); } }
    public function ClearIt() { if ($something) { $this->_ClearIt(); } }
}

class cScalarLocal implements ifScalarAccess {
    use taScalarAccess;
    use tScalarLocal;
}
class cScalarReadOnly extends cScalarLocal {
    use tScalarLocal;
    use taReadOnlyScalar;
}

Result

PHP Fatal error: An alias was defined for taScalarAccess::SetIt but this method does not exist in /home/htnet/site/git/ferreteria/base/tests/php2.php on line 15

Take 4: hide the real write fx()

Here's what I ended up with the next day, including bits of code excluded in the examples above. I basically started with the _SetIt() and _ClearIt() functions being defined even where we're just going to call them directly from SetIt() and ClearIt(), which makes it easy to just override the latter two so they can act conditionally where needed.

abstract class caScalarAccess {
    // DIRECT: WRITE
    abstract protected function _SetIt(mixed $v);
    abstract protected function _ClearIt();
  
    // API: WRITE (default)
    public function SetIt(mixed $v) { return $this->_SetIt($v); }
    public function ClearIt() { $this->_ClearIt(); }
    // API: READ
    abstract public function HasIt() : bool;
    abstract public function GetIt() : mixed;
    public function GetItNz($default=NULL) { return $this->HasIt() ? $this->GetIt() : $default; }
    
    // IDE
    public function DumpLine() : string {
        $bHas = $this->HasIt();
        $sVal = $bHas ? (values\csFormats::Render($this->GetIt())) : cEnv::ItalIt('(not set)');
        $ftClass = cEnv::BoldIt(get_class($this));
        $out = "Piece class: $ftClass Value: [$sVal]";
        return $out;
    }
}
// PURPOSE: value is stored internally
trait tScalarLocal {
    private $tscl_value;
    private $tscl_exists = FALSE;
  
    public function HasIt() : bool { return $this->tscl_exists; }
    public function GetIt() : mixed { return $this->tscl_value; }
    protected function _SetIt(mixed $v) {
        $this->tscl_value = $v;
        $this->tscl_exists = TRUE;
    }
    protected function _ClearIt() {
        $this->tscl_exists = FALSE;
        #unset($this->value);
    }
}
/*::::
  REQUIRES: tScalarLocal
  * We can't just "use" that here, though, because cScalarReadOnly ends up using both of them.
*/
trait tScalarReadOnly {
  
    private $canWrite = FALSE;
    
    protected function SetCanWrite(bool $b) { $this->canWrite = $b; }
    protected function GetCanWrite() : bool { return $this->canWrite; }

    // ++ API ++ //
    
    public function SetIt(mixed $v) {
        if ($this->GetCanWrite()) {
            $this->_SetIt($v);
        } else {
            $this->ThrowWriteError('set');
        }
    }
    public function ClearIt() {
        if ($this->GetCanWrite()) {
            $this->_ClearIt();
        } else {
            $this->ThrowWriteError('clear');
        }
    }
    
    // -- API -- //
    // ++ ERROR ++ //
    
    protected function ThrowWriteError(string $sAction) {
        $sClass = get_class($this);
        $sError = "trying to $sAction the value of a read-only $sClass object.";
        $e = new except\cUsage($sError);
        throw $e;
    }
    
    // -- ERROR -- //
}

class cScalarLocal { use tScalarLocal; }
/*::::
  THINKING: This is neither writeable nor a reference, so local storage should always be sufficient.
*/
class cScalarReadOnly extends cScalarLocal {
    use tScalarLocal;
    use tScalarReadOnly;
    
    public function __construct(mixed $value=NULL, bool $exists=FALSE) {
        $this->SetWritable(FALSE);
        if ($exists) {
            $this->_SetIt($value);
        } else {
            $this->_ClearIt();
        }
    }
}