2021/11/24/things you can't do in PHP: Difference between revisions
(Created page with "{{nav/codeblog}} ==Example 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 '''c...") |
No edit summary |
||
Line 1: | Line 1: | ||
{{nav/codeblog}} | {{nav/codeblog}} | ||
== | ==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> | ||
Line 36: | Line 36: | ||
===Result=== | ===Result=== | ||
<blockquote>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</blockquote> | <blockquote>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</blockquote> | ||
== | ==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> | ||
Line 72: | Line 72: | ||
<blockquote>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</blockquote> | <blockquote>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</blockquote> | ||
i.e. that didn't fix the problem. | i.e. that didn't fix the problem. | ||
== | ==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> | ||
Line 107: | Line 107: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===Result=== | ===Result=== | ||
<blockquote>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<? | <blockquote>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</blockquote> | ||
==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. | |||
<syntaxhighlight lang=php line start=2> | |||
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(); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> |
Revision as of 14:00, 25 November 2021
Take 1: my bad
This is where I started -- and admittedly there was already a problem in that I'm use
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 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();
}
}
}