Ferreteria/archive/DataScript/data-script.php

From Woozle Writes Code
Jump to navigation Jump to search

Requires

Code

<php><?php /* ===========================

*** DATA UTILITY CLASSES ***
 AUTHOR: Woozle (Nick) Staddon
 TODO: This needs to be descended from clsTreeNode (tree.php) so all the tree-management fx can be removed
 HISTORY:
   2011-09-24 Data Scripting classes started (in data.php)
   2011-10-07 extracted from data.php
   2011-11-23 added intIndex and objParent properties
   2011-12-29 wrote AddBefore()
  • /

define('SCRIPT_DEPTH_LIMIT',5000); // when to interupt a recursive loop

abstract class Script_Element {

   static protected $cntDepth;
   protected $cntIndex;	// array counter
   protected $ranOkay;	// was the attempt successful?
   protected $intExec;	// number of times this step has been executed
   private $Name;
   private $arDir;	// flat directory (array) of named Script_Element objects, if any
   // these are not always set
   protected $intIndex;	// index within parent script
   protected $objParent;	// pointer to parent script object
   protected $arScript;	// array of Script_Element objects
   public function __construct() {

$this->ranOkay = NULL; // run status is unknown $this->Name = NULL; $this->intExec = 0; $this->cntIndex = 0; $this->objParent = NULL; $this->arScript = NULL; self::$cntDepth = 0;

   }
   public function HasParent() {

return !is_null($this->objParent);

   }
   public function Parent() {

return $this->objParent;

   }
   /*----
     HISTORY:

2011-11-30 added $iForce argument, and changed logic so passed name does not override existing name by default

   */
   public function Name($iName=NULL,$iForce=FALSE) {

if (is_null($this->Name) || $iForce) { // stored name overrides arg name, unless iForce=TRUE /* this just causes ridiculous problems if ($this->HasParent()) { $this->Parent()->RenameNode($this->Name,$iName); }

  • /

$this->Name = $iName; } return $this->Name;

   }
   public function HasName() {

return (!is_null($this->Name));

   }
   protected function NameShow() {

if ($this->intExec > 0) { $htExec = ' x'.$this->intExec.''; } else { $htExec = ; } if ($this->HasName()) { $out = '('.get_class($this).')['.$this->Name().''.$htExec.'] '; } else { $out = NULL; } return $out;

   }
   abstract public function Exec($iDo);
   protected function RegStep(Script_Element $iStep,$iIndex,$iName=NULL) {

$iStep->Name($iName); $iStep->intIndex = $iIndex; $iStep->objParent = $this;

   }
   public function Add(Script_Element $iAct=NULL, $iName=NULL) {

if (!is_null($iAct)) { $this->cntIndex++; $this->arScript[$this->cntIndex] = $iAct; $this->RegStep($iAct,$this->cntIndex,$iName); //parent::Add($iAct,$iName); }

   }
   /*----
     ACTION: Modifies the script queue so that $iAct will get executed before $this.

Currently, this replaces $this with $iAct and makes $this a subscript of $iAct -- but try not to depend on that behavior.

     HISTORY:

2011-12-29 created

   */
   public function AddBefore(Script_Element $iAct=NULL) {

if (!is_null($iAct)) { $this->ReplaceWith($iAct); $iAct->Add($this); }

   }
   /*----
     PURPOSE: checks for runaway loop
   */
   protected function StepIn() {

//echo '

  • CLASS: '.get_class($this).' - NAME: '.$this->Name().'
  • '; //echo '

      '; $this->intExec++; self::$cntDepth++; if (self::$cntDepth > SCRIPT_DEPTH_LIMIT) { throw new exception('Recursive loop detected'); } } protected function StepOut() { //echo '

    ';

    self::$cntDepth--;

       }
       public function RanOkay() {
    

    return $this->ranOkay;

       }
       public function Dir() {
    

    if (isset($this->arDir)) { return $this->arDir; } else { return NULL; }

       }
       /*----
          NOTES:
    

    This code allows the Add() name to override the element's internal name. Doing the opposite turns out to be more useful because sometimes the Add() is done by a recursive process, but the individual elements serve discrete functions. So if an internal name exists, we want that to override Add().

         HISTORY:
    

    2011-11-30 we now defer to $this->Name() to decide what overrides what

       */
    

    /*

       public function Add(Script_Element $iAct=NULL,$iName=NULL) {
    

    if (!is_null($iAct)) { $strName = $iAct->Name($iName); if (!is_null($strName)) { $this->arDir[$strName] = $iAct; }

    $arDir = $this->arDir; $arSub = $iAct->Dir(); if (is_array($arSub)) { if (is_array($arDir)) { // add new sub-action's directory to ours $this->arDir = array_merge($arDir,$arSub); } else { // create directory array from the sub-action's directory: $this->arDir = $arSub; } } }

       }
    
    • /
       /*----
         ACTION: Looks for the node named $iName in the tree
         HISTORY:
    

    2011-12-18 very non-optimized rewrite to avoid issues with renaming of subnodes after they've been added; can probably make this more efficient later. 2012-01-02 modified code to return this node if its name matches

       */
       public function Get_byName($iName,$iReq) {
    

    if ($this->Name() == $iName) { // if this node is the right name, return it return $this; } else { // otherwise, search through subnodes $arSub = $this->arScript; if (is_array($arSub)) { foreach ($arSub as $idx => $act) { $actFnd = $act->Get_byName($iName,FALSE); if (!is_null($actFnd)) { return $actFnd; // found further down } } } }

    if ($iReq) { echo 'Get_byName() is having a problem. Here is the script - '.$this->Exec(FALSE); throw new exception('Action name ['.$iName.'] not found in script.'); }

    return NULL; // not found

       }
    

    /* version 1

       public function Get_byName($iName,$iReq) {
    

    $isFnd = FALSE; $actOut = NULL; $arDir = $this->arDir; if (is_array($arDir)) { $isArr = TRUE; if (array_key_exists($iName,$arDir)) { $actOut = $this->arDir[$iName]; $isFnd = TRUE; } } else { $isArr = FALSE; } if (!$isFnd) { if ($iReq) { echo 'Get_byName() is having a problem. Here is the '.$this->Exec(FALSE);

    if ($isArr) { echo 'The lookup array has '.Count($arDir).' elements:'; foreach ($arDir as $name => $obj) { echo ' ['.$name.']'; } echo '
    '; } else { echo 'Apparently the lookup array was not initialized.'; }

    throw new exception('Action name ['.$iName.'] not found in script.'); } } return $actOut;

       }
    
    • /
       /*----
         ACTION: Removes self from the script and installs iStep at the same point.
         RETURNS: the step object that was replaced (not used, as of this writing)
         PURPOSE: so we can replace a scratch object with a more full-functioned object later
         HISTORY:
    

    2011-11-23 created

       */
       public function ReplaceWith(Script_Element $iStep) {
    

    $idx = $this->intIndex; if ($this->HasParent()) { $actOld = $this->objParent->Replace_Step($idx,$iStep); } else { // this is root, or has not been added to main script yet $actOld = NULL; } return $actOld;

       }
       /*----
         ACTION: Updates the search index with a new name for this element.
       */
    

    /*

       protected function RenameNode($iOld,$iNew) {
    

    $arDir = $this->arDir; if (array_key_exists($iOld,$arDir)) { $obj = $arDir[$iOld]; unset($arDir[$iOld]); $arDir[$iNew] = $obj;

    if ($this->HasParent()) { $ok = $this->Parent()->RenameNode($iOld,$iNew); } else { $ok = TRUE; }

    return $ok; } else { return FALSE; }

       }
    
    • /

    } /*====

     HISTORY:
       2011-11-23 renamed $intIndex to $cntIndex so as not to conflict with the new Script_Element::intIndex
    
    • /

    class Script_Script extends Script_Element {

    /*

       public function __construct() {
    

    parent::__construct(); $this->cntIndex = 0;

       }
    
    • /
       /*----
         RETURNS: TRUE if script has no steps in it
       */
       public function IsEmpty() {
    

    return ($this->cntIndex == 0);

       }
    

    /*

       protected function RegStep(Script_Element $iStep,$iIndex,$iName=NULL) {
    

    $iStep->Name($iName); $iStep->intIndex = $iIndex; $iStep->objParent = $this;

       }
       public function Add(Script_Element $iAct=NULL, $iName=NULL) {
    

    if (!is_null($iAct)) { $this->cntIndex++; $this->arScript[$this->cntIndex] = $iAct; $this->RegStep($iAct,$this->cntIndex,$iName); parent::Add($iAct,$iName); }

       }
    
    • /
       public function Exec($iDo) {
    

    $this->StepIn();

    $out = 'script:

      '; $this->ranOkay = TRUE; // any subscript failure means this script has failed if (is_null($this->arScript)) { $outErr = 'Internal warning: Script "'.$this->Name().'" expected to have some steps, but found none.'; $this->Add(new Script_Status($outErr)); } foreach ($this->arScript as $idx => $act) { $out .= '
    • '.$idx.'. '.$act->NameShow().$act->Exec($iDo).'
    • '; if (!$act->RanOkay()) { $this->ranOkay = FALSE; } } $out .= '

    ';

    $this->StepOut(); return $out;

       }
       /*----
         ACTION: Replace A with B in the script:
    

    A: the step with the given index ($iOldIndex) B: the given step-object ($iNewObj)

         RETURNS: the step object that was replaced
         USAGE: intended to be called by Script_Element::ReplaceWith()
         HISTORY:
    

    2011-11-23 created

       */
       protected function Replace_Step($iOldIndex,Script_Element $iNewObj) {
    

    $actOld = $this->arScript[$iOldIndex]; $iNewObj->Values($actOld->Values()); // preserve the values array

    $this->arScript[$iOldIndex] = $iNewObj; $this->RegStep($iNewObj,$iOldIndex,$actOld->Name()); return $actOld;

       }
    

    } /*====

     PURPOSE: ancestor class for scripts that manage named data objects
     USED BY: Script_Row_Update, Script_Copy_Named
     HISTORY:
       2011-11-18 created for revision of Script_Row_Update
       2011-11-21 moved ObjName() here from Script_SQL_DataRow_Command
         lots of reshuffling of the class hierarchy without proper mapping -- no time...
    
    • /

    abstract class Script_RowObj extends Script_Element {

       public $arList;	// array of fields/values (for INS, UPD)
    
       public function __construct(array $iarList=array()) {
    

    parent::__construct(); $this->arList = $iarList;

       }
    

    /*

       abstract public function Record();
       abstract public function ObjName();
    
    • /
       public function GetRecord() { return NULL; }		// this is a kluge
       public function LetRecord(clsRecs_abstract $iRow) { }	// this is a kluge
       public function ObjName() { return NULL; }	// this is a kluge
       public function Value($iName,$iVal=NULL) {
    

    if (!is_null($iVal)) { $this->arList[$iName] = $iVal; } return NzArray($this->arList,$iName);

       }
       public function Values(array $iList=NULL) {
    

    if (!is_null($iList)) { $this->arList = $iList; } return $this->arList;

       }
    

    /* 2011-11-24 this may be useful eventually, but it's not needed yet.

       public function MergeValues(array $iList) {
    

    $this->arList = array_merge($this->arList,$iList);

       }
    
    • /

    } /*====

     PURPOSE: placeholder for writable datarow object which can be created later
     2011-11-21 created for scratch order record script in shop.php 
    
    • /

    class Script_RowObj_scratch extends Script_RowObj {

       private $strName;
    
       public function __construct($iName) {
    

    $this->strName = $iName;

       }
    

    /*

       public function GetRecord() {
    

    return NULL;

       }
    
    • /
       public function ObjName() {
    

    return $this->strName;

       }
       public function Exec($iDo) {
    

    return 'Scratch object - does nothing';

       }
    

    } /*====

     PURPOSE: placeholder for array data for other classes to use; doesn't actually do anything
     USED BY:
       Script_Copy_Named - sometimes we just need a place to store data before translation
     HISTORY:
       2011-10-08 
         Value() is now public, so other script elements can access and edit the change values
         extracted about half of Script_SQL_DataRow_Command to create Script_SQL_DataRow
       2011-11-20 added Values() method
       2011-11-21 Value() method now returns NULL instead of crashing if $this->arList[$iName] does not exist.
         Now descending from Script_RowObj instead of Script_Element.
    
    • /

    class Script_DataRow extends Script_RowObj {

       public function Exec($iDo) {
    

    return 'Nothing happens here; why is this being executed?';

       }
    

    } /*====

     PURPOSE: placeholder for writable datarow object which can be created later
     USED BY: nothing yet; created mainly for completeness. Commented out until needed.
     HISTORY:
       2011-11-18 created as part of thinking-through process for Script_Row_Update revision
    
    • /

    /* class Script_RowObj_direct extends Script_RowObj {

       private $rc;
    
       public function __construct(clsRecs_keyed_abstract $iRec=NULL) {
    

    $this->rc = $iRec;

       }
       public function Record() {
    

    return $this->rc;

       }
    

    }

    • /

    /*====

     PURPOSE: same as Script_RowObj, but pulls the object from a Script_Tbl_Insert
     USED BY: Script_Row_Update
     HISTORY:
       2011-11-18 created for revision of Script_Row_Update
    
    • /

    /* may not be needed class Script_RowObj_fromInsert extends Script_RowObj {

       private $act;
    
       public function __construct(Script_Tbl_Insert $iAct) {
    

    $this->act = $iAct;

       }
       public function Record() {
    

    $id = $this->act->ID(); $tbl = $this->act->Table(); $rc = $tbl->GetItem($id); return $rc;

       }
    

    }

    • /

    /*====

     PURPOSE: classes that do something with a single row of data that has an ID
    
    • /

    abstract class Script_SQL_DataRow_Command extends Script_DataRow {

       public $ID;		// ID of row last affected (direct access is DEPRECATED)
    
       public function ID() { return $this->ID; }
       abstract protected function SQL_Command();
       abstract public function Engine();	// database object
    

    } /*====

     LATER: some additional coding is needed to support multiple keys... if that's useful.
    
    • /

    class Script_Tbl_Insert extends Script_SQL_DataRow_Command {

       protected $tbl;	// direct access to this is DEPRECATED
    
       public function __construct(array $iarList, clsTable_abstract $iTable) {
    

    parent::__construct($iarList); $this->tbl = $iTable;

       }
       public function Table() {
    

    return $this->tbl;

       }
       public function GetRecord() {
    

    $tbl = $this->Table(); if ($this->ranOkay) { $id = $this->ID; $rc = $tbl->GetItem($id); } else { $rc = $tbl->SpawnItem(); } return $rc;

       }
       public function Exec($iDo) {
    

    $this->StepIn(); if ($iDo) { $tbl = $this->tbl; $this->ranOkay = $tbl->Insert($this->arList); $this->ID = $tbl->Engine()->NewID(); } $out = $this->SQL_Command(); $this->StepOut(); return $out;

       }
       protected function SQL_Command() {
    

    return $this->tbl->SQL_forInsert($this->arList);

       }
       public function ObjName() {
    

    return $this->tbl->Name();

       }
       public function Engine() {
    

    return $this->tbl->Engine();

       }
    

    } class Script_Row_Update extends Script_SQL_DataRow_Command {

       public $Record;	// DEPRECATED for public access; use GetRecord()
    
       public function __construct(array $iarList=array(), clsRecs_keyed_abstract $iRecord) {
    

    parent::__construct($iarList); $this->Record = $iRecord;

       }
       public function GetRecord() {
    

    return $this->Record;

       }
       public function Exec($iDo) {
    

    $this->StepIn(); if ($iDo) { $this->ranOkay = $this->Record->Update($this->arList); $this->ID = $this->Record->KeyValue(); } $out = $this->SQL_Command(); $this->StepOut(); return $out;

       }
       protected function SQL_Command() {
    

    return $this->Record->SQL_forUpdate($this->arList);

       }
       public function ObjName() {
    

    return $this->Record->Table->Name();

       }
       public function Engine() {
    

    return $this->Record->Engine();

       }
    

    } /*====

     ACTION: holds scriptable data from a loaded recordset
     HISTORY:
       2011-11-29 created
    
    • /

    class Script_Row_Data extends Script_RowObj {

       private $rc;
    
       public function LetRecord(clsRecs_abstract $iRow) {
    

    $this->rc = $iRow; $this->Values($iRow->Values());

       }
       public function ObjName() {
    

    return $this->rc->Table()->Name();

       }	// this is a kluge
       public function Exec($iDo) {
    

    $rc = $this->rc;

    $cnt = $rc->RowCount(); $rc->FirstRow(); $msg = 'DATA from '.$cnt.' existing row'.Pluralize($cnt); if (method_exists($rc,'KeyValue')) { $id = $rc->KeyValue(); $msg .= ' - first ID: '.$id; } return $msg;

       }
    

    } /*====

     ACTION: updates a newly-created data record
     HISTORY:
       2011-11-18 modified to take an script data object instead of a row object
    
    • /

    class Script_Row_Update_fromInsert extends Script_SQL_DataRow_Command {

       private $act;
    
       public function __construct(array $iarList=array(), Script_Tbl_Insert $iactIns) {
    

    parent::__construct($iarList); $this->act = $iactIns;

       }
       public function Exec($iDo) {
    

    $this->StepIn(); if ($iDo) { /* this shouldn't affect anything; the update is done from the local arList foreach ($this->Values() as $key => $val) { $this->act->Value($key,$val); }

    • /

    $rc = $this->Record(); $this->ranOkay = $rc->Update($this->arList); $this->ID = $rc->KeyValue(); } $out = $this->SQL_Command(); $this->StepOut(); return $out;

       }
       protected function SQL_Command() {
    

    return $this->Record()->SQL_forUpdate($this->arList);

       }
       public function Record() {
    

    return $this->act->Record();

       }
       public function ObjName() {
    

    return $this->Record()->Table()->Name();

       }
       public function Engine() {
    

    return $this->Record()->Engine();

       }
    

    } /*====

     PURPOSE: debugging -- just prints a status line so we know where we are.
    
    • /

    class Script_Status extends Script_Element {

       public $Text;
    
       public function __construct($iText) {
    

    parent::__construct(); $this->Text = $iText;

       }
       public function Exec($iDo) {
    

    $this->ranOkay = TRUE; // this class has no error modes $out = $this->Text; return $out;

       }
    

    } /*----

     SCRIPT ACTION:
       Executes $iTrial
       If it returns TRUE, executes $iIfGood
     LATER: an iIfBad parameter may be useful too
    
    • /

    class Script_IF_Ok extends Script_Element {

       protected $Trial;	// script to try executing
       protected $IfGood;	// script to run if Trial is successful
    
       public function __construct(Script_Element $iTrial, Script_Element $iIfGood) {
    

    parent::__construct(); $this->Trial = $iTrial; $this->IfGood = $iIfGood; $this->Add($iTrial); $this->Add($iIfGood);

       }
       public function Exec($iDo) {
    

    $this->StepIn(); if ($iDo) {

    $out = 'TRYING this script:

    • '; $out .= $this->Trial->NameShow(); $out .= $this->Trial->Exec(TRUE); $out .= '

    ';

    $this->ranOkay = FALSE; if ($this->Trial->RanOkay()) { $out .= $this->IfGood->NameShow(); $out .= $this->IfGood->Exec($iDo); $this->ranOkay = $this->IfGood->RanOkay(); } else { $out .= "FAILED; no further action."; } } else {

    $out = 'IF this script succeds:

    • '; $out .= $this->Trial->NameShow(); $out .= $this->Trial->Exec(FALSE); $out .= '

    '; $out .= 'THEN run this one too:

    • '; $out .= $this->IfGood->NameShow(); $out .= $this->IfGood->Exec(FALSE); $out .= '

    ';

    } $this->StepOut(); return $out;

       }
       public function Trial(Script_Element $iTrial=NULL) {
    

    if (!is_null($iTrial)) { $this->Trial = $iTrial; } return $this->Trial;

       }
    

    } /*----

     SCRIPT ACTION:
       Copies the applicable ID from a Script_SQL_DataRow_Command to a value in a Script_SQL_InsUpd
       Usually this is used for grabbing the ID of a newly-created row from a Script_Tbl_Insert,
         but sometimes we don't know if we're creating a row or updating an existing one;
         using Script_SQL_DataRow_Command instead of Script_Tbl_Insert allows us to get the current
         ID from a Script_Row_Update, if that's what we have available.
     HISTORY:
       2011-10-07 constructor 2nd parameter is now object instead of array, because array args were not
         preserving the values written to them. Hoping that this works better (it also may have advantages
         for debugging).
    
    • /

    class Script_SQL_Use_ID extends Script_Element {

       public $Srce;
       public $Dest;
       public $Name;
    

    // public function __construct(Script_SQL_DataRow_Command $iSource, array &$iDest, $iFieldName) {

       public function __construct(
         Script_RowObj $iSrce,
         Script_RowObj $iDest,
         $iFieldName) {
    

    parent::__construct(); $this->Srce = $iSrce; $this->Dest = $iDest; $this->Name = $iFieldName;

       }
       public function Exec($iDo) {
    

    $this->StepIn(); $strAct = 'from ['.$this->Srce->ObjName().'] ' .'to ['.$this->Dest->ObjName().'.'.$this->Name.']';

    if ($iDo) { // get the new ID $id = $this->Srce->ID; // copy it to the designated field $out = 'COPYING row ID ['.$id.'] '.$strAct; $this->Dest->Value($this->Name,$id); $this->ranOkay = TRUE; } else { $out = 'COPY row ID '.$strAct; $this->Dest->Value($this->Name,'<'.$this->Srce->ObjName().'>'); } $this->StepOut(); return $out;

       }
    

    } /*----

     SCRIPT ACTION:
       Copy named fields from one array to named fields in another
     INPUT:
       $iSrce: source array
       $iDest: destination array
       $iXlate: translation matrix - $iXlate[dest index] is copied to $iXlate[source index]
         Yes, this is backwards, but that's because you might want to copy one source to more
    

    than one destination -- and indexed arrays don't allow the same index to have multiple values, so we have to put the source in the value and let the index be dest.

     HISTORY:
       2011-09-23 written for icky address import part of order import process
       2011-10-08 rewritten to use data objects instead of arrays
    
    • /

    class Script_Copy_Named extends Script_Element {

       public $Srce;
       public $Dest;
       public $DestShow;
       private $Xlate;
    
       public function __construct(
    

    Script_RowObj $iSrce, Script_RowObj $iDest, array $iXlate) { parent::__construct(); $this->Srce = $iSrce; $this->Dest = $iDest; $this->Xlate = $iXlate;

       }
       public function Exec($iDo) {
    

    $this->StepIn(); $out = 'COPYING:'; $actSrce = $this->Srce; $actDest = $this->Dest;

    // this should only be temporary until I figure out what base class Srce and Dest need to be if (!method_exists($actSrce,'ObjName')) { echo 'CLASS: '.get_class($actSrce).'
    '; throw new exception('Source needs ObjName() method.'); }

    $strSrce = $actSrce->ObjName(); $strDest = $actDest->ObjName(); foreach ($this->Xlate as $dest => $srce) { if (is_null($srce)) { $out .= ' ["'.$strDest.'.'.$dest.'" left alone]'; } else { if (empty($dest)) { $out .= ' [[no destination] <= ['.$strSrce.'.'.$srce.']]'; } else { $out .= ' [['.$strDest.'.'.$dest.'] <= ['.$strSrce.'.'.$srce.']]'; if ($iDo) { $val = $actSrce->Value($srce); $actDest->Value($dest,$val); // copy srce value to dest $out .= ' - value ['.$val.']'; } else { // put nicely-formatted dummy value in the array so we can see what is *supposed* to happen $actDest->Value($dest,'<'.$strSrce.'.'.$srce.'>'); } } } } $this->ranOkay = TRUE; $this->StepOut(); return $out;

       }
    

    }</php>