Ferreteria/v0.6/clade/Data/Mem/QVar

From Woozle Writes Code
< Ferreteria‎ | v0.6‎ | clade‎ | Data‎ | Mem
Revision as of 18:00, 26 November 2025 by Woozle (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
clade: Data\Mem\QVar
Clade Family
StandardBase QVar
Clade Aliases
Alias Clade
Base* [c,i] Aux\StandardBase
StaticTypes Data\Mem\Val\Types
ArrayOptsEnum Data\Mem\QVar\opts\Array
ArrayIface Sys\Data\Things\Array
Subpages

About

  • Purpose: queryable value-wrapper objects
  • Naming: Ideally, there would be QVal for read-only values and QVar for R/W, but PHP already makes it difficult enough to implement what I've already got...

The idea here is that I wanted to be able to indicate whether a value {exists, has been set, etc.} without relying on a coding convention (the most obvious/common being the use of NULL to indicate that a value is unavailable). While "NULL=unavailable" is not a bad convention (and I still use it occasionally), it feels klugey to rely on it too much -- while providing an object to wrap the results is both semantic: $qx->HasIt() is much less ambiguous in intent than !is_null($x).

Complications

The only problem with doing it this way involves a combination of circumstances:

  • In order to continue properly enforcing return-types, we need to have a QVar-clade for each type we want to return.
  • The simplest and most maintainable way to do this is to have
    • a parent-clade (QVar) which defines the interface and implements some general rules, and
    • a set of podling-clades which each restrict the value being handled to a specific type.
  • However, PHP's inheritance rules for method definitions only allow return-values to be {the same as or more strict than} the parental definition ("covariance"), while argument-types must be {the same or less strict} ("contravariance") (see Wikipedia: type variance).

This means that we cannot have a common (public) interface for all types, except for methods that only return values (e.g. HasIt(), GetIt()); each podling-clade must re-define those methods with the appropriate type for its parameters.

Advantages

This methodology does have some advantages, despite the awkwardness:

  • It allows type-checking for different types of scalars, which is not currently supported by PHP. (It is more commonly supported by classic compiled languages like Pascal and C/C++.)
    • e.g. if you want an int which is specifically an error-code and not a quantity – or even an error-code from a specific library (where two or more libraries might return the same code but mean completely different things by it)
  • It allows additional ways of handling values and operations between them that simplify syntax.
    • e.g. FromAValue() allows initializing a QVar from an array plus a key in that array, while handling element-nonexistence in a sensible way.

History

  • 2024-07-18 starting this from scratch to replace the awkward double-wrapper Wrap and Core class-family.
  • 2025-01-14 Decided SetIt_from[O]Array() should specifically not save a reference, because that can cause problems if the value is set elsewhere, or if we're trying to let go of the array to save memory.
  • 2025-04-20 Renamed RefFromOArrElem()WithOAElement(), for consistency with FromOAElement() (see From*())
  • 2025-04-24 Restored FromValue() from code removed on 04/14.
  • 2025-05-11 Changed some terminology:
    • [From/With]AElement()[From/With]AValue()
    • [From/With]OAElement()[From/With]OAValue()
    ...and added [From/With]AIndex()

Code

interface iQVar extends BaseIface {

    // SETUP
    static function AsNew() : self;
    static function FromVarRef(&$v) : self;
    static function FromAValue(array $ar, int|string $vKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : self;
    static function FromOAValue(ArrayIface $oa, int|string $vKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : self;

    // ACCESS: typeless
    function HasIt() : bool;
    function ZapIt();
    function TypeOk(mixed $v) : bool;

    function WithOAValue(ArrayIface $oa, int|string $key, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void;

    // ++ ACCESS: basic types ++ //

    //// - int

    function HasInt() : bool;
    function SetInt(int $n);
    function SetIntNz(?int $n);
    function &GetInt() : int;
    function GetIntNz(int $nDefault=0) : int;
    function RefInt(int &$n) : void;

    //// - string

    function HasStr() : bool;
    function SetStr(string $s);
    function SetStrNz(?string $s);
    function &GetStr() : string;
    function GetStrNz(string $sDefault='', string $sPfx='', string $sSfx='') : string;
    function RefStr(string &$s) : void;

    //// - object

    function HasObj() : bool;
    function SetObj(object $o);
    function SetObjNz(?object $o);
    function GetObj() : object;
    function GetObjNz(object $oDefault=NULL) : ?object;

    /*
    // ACCESS: undeclarable
    // These can't be declared because their types change in each podling. "mixed" is replaced by the corresponding type.

    function SetIt(mixed $n);
    function SetItNz(?mixed $v);
    function &GetIt() : mixed;
    function GetItNz(mixed $vDefault=0) : mixed;
    function RefIt(mixed &$v) : void;

    function SetDefault(mixed $n);
    */
}

/**
 * NOTE:
 *  2024-07-19 There is an implied SetIt(variant) method, but we can't declare it here
 *    because that would prevent $v from having a required type.
 */
trait tQVar {

    // ++ SETUP ++ //

    public static function AsNew() : iQVar { return new static; }
    public static function FromVarRef(&$v) : iQVar {
        $oThis = new static;
        $oThis->RefIt($v);
        return $oThis;
    }
    public static function FromValue(mixed $v) : iQVar {
        $oThis = new static;
        $oThis->SetIt($v);
        return $oThis;
    }
    public static function FromAIndex(array $ar, int|string $vKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : iQVar {
        $oThis = new static;
        $oThis->WithAIndex($ar,$key,$eOpt);
        return $oThis;
    }
    public static function FromAValue(array $ar, int|string $vKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : iQVar {
        $oThis = new static;
        $oThis->WithAValue($ar,$key,$eOpt);
        return $oThis;
    }
    public static function FromOAValue(ArrayIface $oa, int|string $key, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : iQVar {
        $oThis = new static;
        $oThis->WithOAValue($oa,$key,$eOpt);
        return $oThis;
    }

    // -- SETUP -- //
    // ++ ACCESS: typeless ++ //

    protected $v = NULL;
    public function ZapIt()          { $this->v = NULL; }

    protected function WithAIndex(array $ar, int|string $snKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void {
        $isFnd = FALSE;
        if (array_key_exists($snKey,$ar)) {
            $this->SetIt($snKey);
        }
    }

    public function WithAValue(array $ar, int|string $snKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void {
        $isFnd = FALSE;
        if (array_key_exists($snKey,$ar)) {
            $vDup =& $ar[$snKey];
            $isFnd = $this->TypeOk($vDup);
        }
        if (!$isFnd) {
            $isFnd = $this->HandleAbsent($snKey,$eOpt);
        }
        if ($isFnd) {
            $this->RefIt($vDup);
        }
    }
    public function WithOAValue(ArrayIface $oa, int|string $snKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void {
        $isFnd = FALSE;
        if ($oa->HasIt($snKey)) {
            $vDup =& $oa->GetRef($snKey);
            $isFnd = $this->TypeOk($vDup);
        }
        if (!$isFnd) {
            $isFnd = $this->HandleAbsent($snKey,$eOpt);
        }
        if ($isFnd) {
            $this->RefIt($vDup);
        }
    }
    protected function HandleAbsent(int|string $snKey, ArrayOptsEnum $eOpt) : bool {
        $isFnd = FALSE;
        switch($eOpt) {
          case ArrayOptsEnum::MakeElem:
            $vDup = $oa->MakeIt($snKey);  // create an entry for the key so we can RefIt
            $isFnd = TRUE;
            echo " - used MakeIt($snKey)".CRLF;
            break;
          case ArrayOptsEnum::ZapLocal:
            $this->ZapIt();
            echo " - used ZapIt()".CRLF;
            break;
        }
        return $isFnd;
    }

    // ++ ACCESS: basic types ++ //

    //// - int

    public function HasInt() : bool           { return is_int($this->v); }
    public function SetInt(int $n)            { $this->v = $n; }
    public function SetIntNz(?int $n)         { if (is_int($n)) { $this->SetInt($n); } else { $this->ZapIt(); } }
    public function &GetInt() : int           { return $this->v; }
    public function GetIntNz(int $nDefault=0) : int { return $this->HasInt() ? $this->v : $nDefault; }
    public function RefInt(int &$n) : void { $this->v =& $n; }

    //// - string

    public function HasStr() : bool      { return is_string($this->v); }
    public function SetStr(string $s)    { $this->v = $s; }
    public function SetStrNz(?string $s) { if (is_string($s)) { $this->SetStr($s); } else { $this->ZapIt(); } }
    public function &GetStr() : string   { return $this->v; }
    public function GetStrNz(
      string $sDefault='',
      string $sPfx='',
      string $sSfx=''
      ) : string { return ($this->HasStr() ? ($sPfx.$this->v.$sSfx) : $sDefault); }
    public function GetStrNzN(
      string $sDefault=NULL,
      string $sPfx='',
      string $sSfx=''
      ) : ?string { return ($this->HasStr() ? ($sPfx.$this->v.$sSfx) : $sDefault); }
    public function RefStr(string &$s) : void { $this->v =& $s; }

    //// - object

    public function HasObj() : bool                             { return is_object($this->v); }
    public function SetObj(object $o)                           { $this->v = $o; }
    public function SetObjNz(?object $o)                        { if (is_object($o)) { $this->SetObj($o); } else { $this->ZapIt(); } }
    public function GetObj() : object                           { return $this->v; }
    public function GetObjNz(object $oDefault=NULL) : ?object   { return $this->HasObj() ? $this->v : $oDefault; }

    // -- ACCESS -- //
}
abstract class caQVar extends BaseClass implements iQVar {
    use tQVar;
}