interface iMyMar extends BaseIface {
// LIFECYCLE
static function FromSpecs(string $sAddr, string $sUser, string $sPass, ?string $sSchema, ?int $nzPort) : self;
// INFO
function SchemaExists(string $sName) : bool; // 2025-05-30 Eventually, will want a base-class for all multi-schema engines.
}
abstract class caMyMar extends BaseClass implements iMyMar {
// ++ CONFIG ++ //
protected function CommOpClass() : string { return CommOpClass::class; } // specialize later
protected function SchemaClass() : string { return SchemaClass::class; }
protected function ScribeClass() : string { return ScribeClass::class; }
protected function ExportClass() : string { return ExportClass::class; }
// -- CONFIG -- //
// ++ SETTINGS ++ //
private $eFmt = BkupModeEnum::USE_SQL; // DEFAULT
public function DataFormat(?BkupModeEnum $eMode=NULL) : BkupModeEnum { return is_object($eMode) ? ($this->eFmt = $eMode) : $this->eFmt; }
// -- SETTINGS -- //
// ++ STATE ++ //
private $isOpen = FALSE;
protected function IsOpen(?bool $b=NULL) : bool { return $this->isOpen ?? ($this->isOpen = $b); }
// -- STATE -- //
// ++ SETUP ++ //
public static function FromSpecs(string $sAddr, string $sUser, string $sPass, ?string $sSchema, ?int $nzPort) : iMyMar {
$oThis = new static;
$oThis->WithSpecs($sAddr,$sUser,$sPass,$sSchema,$nsPort);
return $oThis;
}
public function __destruct() { $this->Shut(); }
// ++ SETUP: dynamic ++ //
protected function WithSpecs(string $sAddr, string $sUser, string $sPass, ?string $sSchema, ?int $nzPort) : iMyMar {
$oAct = $this->ItWentLike(); // get an ItWent object
$oAct->Clear();
#$this->AmHere("ADDR: [$sAddr] - USER: [$sUser] - PASS: [$sPass]");
$onDB = new mysqli();
$this->QNative()->SetIt($onDB);
try {
$ok = $onDB->connect($sAddr,$sUser,$sPass,$sSchema,$nzPort); // open the DB connection
} catch(\Exception $e) {
$ok = FALSE;
#echo "EXCEPTION: ".$e.CRLF;
}
if ($ok) {
$oAct->SetOkay(TRUE);
$this->IsOpen(TRUE);
} else {
// 2022-01-26 probably want a method to do all this
$oAct->SetOkay(FALSE);
#echo $this->ReflectThis()->Report();
$oAct->SetError($e->getCode());
$oAct->AddMsgString($e->getMessage());
}
return $oAct;
}
// -- SETUP -- //
// ++ OBJECTS ++ //
private $oStatus = NULL;
protected function CommOpStatus() : CommOpIface { return $this->oStatus ?? ($this->oStatus = new ($this->CommOpClass())); }
// -- OBJECTS -- //
// ++ LIFECYCLE ++ //
public function Shut() : CommOpIface {
$oAct = $this->ItWentLike();
if ($this->IsOpen()) {
// Did Engine actually get opened? (2025-12-06 We probably need a separate flag for this. TODO)
// close endpoint-to-engine connection
$onDB = $this->QNative()->GetIt();
$ok = $onDB->close();
// close code-to-endpoint connection
$oAct->SetOkay($ok);
$this->IsOpen(FALSE);
}
return $oAct;
}
// -- LIFECYCLE -- //
// ++ ACTION ++ //
// ++ ACTION: UI requests ++ //
public function DoDataRequest(DataReqIface $oReq) : PlayerIface {
$this->Open();
$sql = $oReq->SQL();
#echo "SQL: $sql".CRLF;
$onRes = $this->QNative()->GetIt()->query($sql);
$oReq->Action()->SetOkay(is_object($onRes));
$scScribe = $this->ScribeClass();
# echo $oReq->ReflectThis()->Report();
$oScribe = $scScribe::FromRequestAndNative($oReq,$onRes);
$oPlay = $oReq->MakePlayer($oScribe);
$this->Shut();
return $oPlay;
}
// ++ ACTION: Schema info ++ //
private $oaSchemas = NULL;
public function SchemaList() : SchemasIface { return $this->oaSchemas ?? ($this->oaSchemas = $this->NewSchemaList()); }
public function SchemaExists(string $sName) : bool { return $this->SchemaList()->HasIt($sName); }
protected function NewSchemaList() : SchemasIface {
// OPEN THINGS:
$oServer = $this->OServer(); // get next-hop connection object
$oServer->Open(); // make sure it's open
$oCmd = CmdLineClass::FromString('SHOW SCHEMAS;');
#echo $oServer->ReflectThis()->Report();
$oActCmd = $oServer->DoCommand($oCmd);
// NOTE: For remote ssh connections, this ultimately gets forwarded to [WFe]IO\Aspect\Connx\Plug\Shell\Remote\cSSH
// ... which then wraps it and punts it back to [WFe]IO\Aspect\Connx\Plug\caShell.
#$this->AmHere('RESULTS from '.get_class($oConn).'->DoCommand(): '.$oActCmd->VIEW_AsBlock());
// SHUT THINGS:
#$oPlug->Shut();
$oServer->Shut();
#$oBuff->Shut();
$oMsgs = $this->MsgsOp();
$oMsgs->HandleResults($oActCmd); // deal with standard errors/warnings
$oActApp = $this->CommOpStatus();
$oScrn = self::Screen();
$oaSchemas = SchemasClass::AsNew();
if ($oActCmd->GetOkay()) {
#echo 'oActCmd: '.$oActCmd->VIEW_AsLine();
#echo $oActCmd->ReflectThis()->Report();
#$this->AmHere('LIST COMMAND RESULT: '.$oActCmd->VIEW_AsBlock());
$sNames = $oActCmd->QData()->GetIt();
$arNames = explode("\n",trim($sNames)); // we might want to ensure that CRLF, CR, and LF all work as EOL.
$sFirst = array_shift($arNames);
if ($sFirst == "Database") {
$oActApp->SetOkay(TRUE);
foreach ($arNames as $sName) {
$oSchema = $this->GetSchema($sName);
$oaSchemas->AddSchema($oSchema);
}
} else {
$oActApp->SetOkay(FALSE);
// "Database" is the standard header for list of databases (schemas).
// If we don't see this, then something isn't right.
$sMsg = $oScrn->ErrorIt('Problem').": Something's not right with the db list. Here's what we got:".CRLF.$sNames;
$oActApp->AddMsgString($sMsg);
}
} else {
$sMsg = self::Screen()->ErrorIt('Problem').': the connection does not seem to be working.';
$oActApp->SetOkay(FALSE);
$oActApp->AddMsgString($sMsg);
}
return $oaSchemas;
}
// ++ ACTION: UI request callbacks ++ //
// 2026-02-08 TODO This is going to need updating.
public function DoUserSetup() {
$oScrn = self::Screen();
// 2025-03-30 has worked at least once now
#echo $oScrn->YellowIt('Warning').': This functionality has not been tested to the point of success yet. It may not work.'.CRLF;
$ok = $this->TestRootAccess();
if ($ok) {
echo $oScrn->GreenIt('Ok').': we seem to have unconfigured root access. Proceeding...'.CRLF;
} else {
echo $oScrn->ErrorIt('Error').': This DB engine is configured, and can only be accessed with credentials.'.CRLF;
exit;
}
$oCreds = $this->Creds();
$sDUser = $oCreds->QSUser()->GetIt();
$sDPass = $oCreds->QSPass()->GetIt();
$sDHost = $oCreds->QSHost()->GetIt();
// IN-SHELL COMMAND: sudo mysql "CREATE USER '<USERNAME>'@<HOST> IDENTIFIED BY '<PASSWORD>'"
#$q = '"';
#$sql = $q."ALTER USER '$sDUser'@$sDHost IDENTIFIED BY '$sDPass';$q";
$sql = "ALTER USER '$sDUser'@$sDHost IDENTIFIED BY '$sDPass';";
#$sCmd = "echo $sql | sudo mysql";
$oCmd = CmdLineClass::FromArray(['echo',$sql,'|','sudo','mysql']);
// DEBUGGING ONLY (shows password)
#echo $oScrn->BoldIt('Command').': '.$oScrn->BlueIt($sCmd).CRLF;
$oConnx = $this->Connx(); // shell (outside) connection details
echo "Adding initial user '$sDUser' on db server '$sDHost'...".CRLF;
#$oBuff = new BufferClass;
echo 'Running the SQL to create the first user...'.CRLF;
#$oAct = $oConnx->DoCommand($oCmd,$oBuff);
$oAct = $oConnx->DoCommand($oCmd);
$ok = $oAct->GetOkay();
if (!$ok) {
// See if we can handle this condition...
$sMsg = $oAct->GetMessage();
if (str_contains($sMsg,'server is running with the --skip-grant-tables option')) {
// NOTE: It may be that what actually needs to happen is just "FLUSH PRIVILEGES;" and try again.
echo 'Apparently the server is in permissionless mode, so using alternative SQL.'.CRLF;
// SET PASSWORD FOR 'root'@'localhost' = PASSWORD('new_password');
echo "(Full message: $sMsg)".CRLF;
// FOR DEBUGGING ONLY (shows password):
$sql = "ALTER USER '$sDUser'@'$sDHost' IDENTIFIED BY '$sDPass';";
echo "SQL: $sql".CRLF;
#$sCmd = "echo $sql | sudo mysql";
$oCmd = CmdLineClass::FromArray(['echo',$sql,'|','sudo','mysql']);
#$sCmd = 'echo "$sql" | sudo mysql';
#$oAct = $oConnx->DoCommand($oCmd,$oBuff);
$oAct = $oConnx->DoCommand($oCmd);
$ok = $oAct->GetOkay();
if (!$ok) {
echo 'Meh, that failed too. Giving up for now.'.CRLF;
}
}
}
if ($ok) {
echo ' - ' . (($oAct->GetOkay() ? $oScrn->GreenIt('Done.') : ($oScrn->ErrorIt('Error: ').$oAct->GetMessage()))) . CRLF;
} else {
echo $oScrn->ErrorIt('Error: ')
.$oScrn->YellowIt($sMsg)
.' - The setup failed for some reason.'
.CRLF;
}
$oAct = $oBuff->Shut();
}
// -- ACTION -- //
}