User:Woozle/DataScript/data-script.php

Requires

 * data.php

Code
<?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 .= '</ul>'; $out .= 'THEN run this one too:'; $out .= $this->IfGood->NameShow; $out .= $this->IfGood->Exec(FALSE); $out .= '</li></ul>'; }	$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,&lt;'.$this->Srce->ObjName.'&gt;); }	$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,&lt;'.$strSrce.'.'.$srce.'&gt;); }		}	   }	}	$this->ranOkay = TRUE; $this->StepOut; return $out; } }