Skip to content

Instantly share code, notes, and snippets.

@mattkenefick
Created August 14, 2021 16:47
Show Gist options
  • Save mattkenefick/e4e151e027f6c77202e4c112514cdb81 to your computer and use it in GitHub Desktop.
Save mattkenefick/e4e151e027f6c77202e4c112514cdb81 to your computer and use it in GitHub Desktop.
Magic safety in PHP
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
abstract class BaseCommand extends Command
{
/**
* If we are expecting a dry run here
*
* For destructive calls, use --dryrun=false
*
* We are intentionally using "false" explicitly because
* we don't want accidental values like "0" causing
* damaging effects.
*
* @var boolean
*/
protected bool $dryrun = true;
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$dryrun = $this->option('dryrun') === 'false';
// Set options
$this->dryrun = $dryrun ? !$dryrun : $this->dryrun;
}
/**
* Magical CALL method that checks for dynamic method
* names. Allows us to write "safelyMyFunction" but
* reference "myFunction"
*
* @param string $method
* @param mixed $arguments
* @return void
*/
public function __call($method, $arguments)
{
preg_match('/(safely|test)(.*)/', $method, $matches);
// Check if we have any matches
if (isset($matches)) {
$protectedMethod = $matches[1] . 'Call';
$classMethod = lcfirst($matches[2]);
// Check if modified method exists on this class
// e.g. "safelyCall(...)" or "testCall(...)"
if (method_exists($this, $protectedMethod) && method_exists($this, $classMethod)) {
return $this->$protectedMethod($classMethod, $arguments);
}
}
}
/**
* Special call using prefix "safely"
*
* @return void
*/
protected function safelyCall(string $classMethod, $arguments)
{
// Check if it's a dry run
if ($this->dryrun) {
$this->log("🟡 Not executing `$classMethod` on a dry-run.\n");
return false;
}
return call_user_func_array(array($this, $classMethod), $arguments);
}
// ...
}
<?php
// Rest of class...
/**
* Special call using prefix "safely"
*
* @param string $classMethod
* @param mixed $arguments
* @return void
*/
protected function safelyCall(string $classMethod, $arguments)
{
// Check if it's a dry run
if ($this->dryrun) {
$this->log("🟡 Not executing `$classMethod` on a dry-run.\n");
return false;
}
return call_user_func_array(array($this, $classMethod), $arguments);
}
<?php
// Rest of class...
/**
* Search and destroy unused content
*
* @return void
*/
protected function cleanContentTable()
{
$sql = "
SELECT `content`.*
FROM `content`
LEFT JOIN `user` ON `user`.`content_id` = `content`.`id`
WHERE (`user`.`id` IS NULL AND `content`.`type` = 1234)
";
// Determine rows found
$ids = array_column(DB::select($sql), 'id');
// Checks to see if it's a dry run or not first
$this->safelyRemoveContentByIds($ids);
// Does NOT test for dry run, just removes content
// $this->removeContentByIds($ids);
}
/**
* Remove content
*
* @param array $ids
* @return void
*/
protected function removeContentByIds(array $ids = [])
{
// Delete content...
}
<?php
// Rest of class...
/**
* Magical CALL method that checks for dynamic method
* names. Allows us to write "safelyMyFunction" but
* reference "myFunction"
*
* @param string $method
* @param mixed $arguments
* @return void
*/
public function __call($method, $arguments)
{
preg_match('/(safely|test)(.*)/', $method, $matches);
// Check if we have any matches
if (isset($matches)) {
$protectedMethod = $matches[1] . 'Call';
$classMethod = lcfirst($matches[2]);
// Check if modified method exists on this class
// e.g. "safelyCall(...)" or "testCall(...)"
if (method_exists($this, $protectedMethod) && method_exists($this, $classMethod)) {
return $this->$protectedMethod($classMethod, $arguments);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment