Created
September 11, 2025 08:54
-
-
Save Bharat-B/db7da81375a18d264b01d4e605ecc7fd to your computer and use it in GitHub Desktop.
Minio PHP Wrapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* MinIO PHP Wrapper | |
* | |
* A standalone PHP wrapper for MinIO Client (mc) command-line tool. | |
* Provides programmatic access to MinIO operations through the mc client. | |
* | |
* Requirements: | |
* - MinIO Client (mc) installed and accessible | |
* - PHP 7.4 or higher | |
* - Symfony Process component (composer require symfony/process) | |
* | |
* @author Bharat Bala | |
* @version 1.0.0 | |
* @license MIT | |
*/ | |
use Symfony\Component\Process\Process; | |
class MinIOWrapper | |
{ | |
/** | |
* Path to the MinIO client executable | |
*/ | |
private string $mcPath; | |
/** | |
* MinIO server configuration | |
*/ | |
private array $config; | |
/** | |
* Server alias name for mc client | |
*/ | |
private string $alias; | |
/** | |
* Full endpoint URL | |
*/ | |
private string $endpoint; | |
/** | |
* Constructor | |
* | |
* @param array $config MinIO server configuration | |
* @param string $mcPath Path to mc executable (default: /usr/local/bin/mc) | |
* @param string $alias Alias name for the server (default: minio-server) | |
* | |
* Config array should contain: | |
* - host: MinIO server hostname/IP | |
* - port: MinIO server port (optional, default: 9000) | |
* - access_key: Access key | |
* - secret_key: Secret key | |
* - ssl: Use SSL/TLS (boolean, default: false) | |
*/ | |
public function __construct(array $config, string $mcPath = '/usr/local/bin/mc', string $alias = 'minio-server') | |
{ | |
$this->validateConfig($config); | |
$this->config = array_merge([ | |
'port' => 9000, | |
'ssl' => false | |
], $config); | |
$this->mcPath = $mcPath; | |
$this->alias = $alias; | |
$this->buildEndpoint(); | |
} | |
/** | |
* Validate the configuration array | |
* | |
* @param array $config | |
* @throws InvalidArgumentException | |
*/ | |
private function validateConfig(array $config): void | |
{ | |
$required = ['host', 'access_key', 'secret_key']; | |
foreach ($required as $key) { | |
if (!isset($config[$key]) || empty($config[$key])) { | |
throw new InvalidArgumentException("Missing required configuration key: {$key}"); | |
} | |
} | |
} | |
/** | |
* Build the endpoint URL | |
*/ | |
private function buildEndpoint(): void | |
{ | |
$scheme = $this->config['ssl'] ? 'https://' : 'http://'; | |
$host = $this->config['host']; | |
$port = $this->config['port']; | |
$this->endpoint = $scheme . $host; | |
if ($port && $port != 80 && $port != 443) { | |
$this->endpoint .= ':' . $port; | |
} | |
} | |
/** | |
* Execute a MinIO client command | |
* | |
* @param array $command Command parts array | |
* @param int $timeout Process timeout in seconds (0 for no timeout) | |
* @return mixed Command output (decoded JSON) | |
* @throws Exception | |
*/ | |
public function runCommand(array $command, int $timeout = 60) | |
{ | |
// Add JSON output flag and error redirection | |
$command = array_merge($command, ['--json', '2>/dev/null']); | |
$commandString = implode(' ', $command); | |
$process = Process::fromShellCommandline($commandString); | |
$process->setTimeout($timeout); | |
$process->run(); | |
if (!$process->isSuccessful()) { | |
$this->handleCommandError($process, $commandString); | |
} | |
$output = $process->getOutput(); | |
if (empty($output)) { | |
return null; | |
} | |
$decoded = json_decode($output, true); | |
if (json_last_error() !== JSON_ERROR_NONE) { | |
throw new Exception('Invalid JSON response: ' . $output); | |
} | |
// Handle MinIO client error responses | |
if (isset($decoded['status']) && $decoded['status'] === 'error') { | |
$errorMessage = $decoded['error']['message'] ?? $output; | |
throw new Exception('MinIO Error: ' . $errorMessage); | |
} | |
return $decoded; | |
} | |
/** | |
* Handle command execution errors | |
* | |
* @param Process $process | |
* @param string $command | |
* @throws Exception | |
*/ | |
private function handleCommandError(Process $process, string $command): void | |
{ | |
$output = $process->getOutput(); | |
$errorOutput = $process->getErrorOutput(); | |
$errorMessage = "Command failed: {$command}\n"; | |
$errorMessage .= "Exit code: " . $process->getExitCode() . "\n"; | |
$errorMessage .= "Output: {$output}\n"; | |
$errorMessage .= "Error: {$errorOutput}"; | |
throw new Exception($errorMessage); | |
} | |
/** | |
* Set up the MinIO server alias | |
* Must be called before using other methods | |
* | |
* @return mixed | |
*/ | |
public function addAlias() | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'alias', 'set', | |
$this->alias, | |
$this->endpoint, | |
$this->config['access_key'], | |
$this->config['secret_key'] | |
]); | |
} | |
/** | |
* Remove the MinIO server alias | |
* | |
* @return mixed | |
*/ | |
public function removeAlias() | |
{ | |
return $this->runCommand([$this->mcPath, 'alias', 'remove', $this->alias]); | |
} | |
/** | |
* Get cluster information | |
* | |
* @return mixed | |
*/ | |
public function getClusterInfo() | |
{ | |
return $this->runCommand([$this->mcPath, 'admin', 'info', $this->alias]); | |
} | |
/** | |
* Create a new user | |
* | |
* @param string $userName | |
* @param string $userPassword | |
* @return mixed | |
*/ | |
public function createUser(string $userName, string $userPassword) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'admin', 'user', 'add', | |
$this->alias, $userName, $userPassword | |
]); | |
} | |
/** | |
* Delete a user | |
* | |
* @param string $userName | |
* @return mixed | |
*/ | |
public function deleteUser(string $userName) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'admin', 'user', 'rm', | |
$this->alias, $userName | |
]); | |
} | |
/** | |
* Create a new bucket | |
* | |
* @param string $bucketName | |
* @return mixed | |
*/ | |
public function createBucket(string $bucketName) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'mb', | |
"{$this->alias}/{$bucketName}" | |
]); | |
} | |
/** | |
* Delete a bucket (forced deletion) | |
* | |
* @param string $bucketName | |
* @return mixed | |
*/ | |
public function deleteBucket(string $bucketName) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'rb', | |
"{$this->alias}/{$bucketName}", | |
'--force' | |
]); | |
} | |
/** | |
* Set quota for a bucket | |
* | |
* @param string $bucketName | |
* @param int $sizeInGB Size in gigabytes | |
* @return mixed | |
*/ | |
public function setQuota(string $bucketName, int $sizeInGB) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'quota', 'set', | |
"{$this->alias}/{$bucketName}", | |
'--size', "{$sizeInGB}GB" | |
]); | |
} | |
/** | |
* Clear quota for a bucket | |
* | |
* @param string $bucketName | |
* @return mixed | |
*/ | |
public function clearQuota(string $bucketName) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'quota', 'clear', | |
"{$this->alias}/{$bucketName}" | |
]); | |
} | |
/** | |
* Generate a write-only policy for a bucket | |
* | |
* @param string $bucketName | |
* @return array | |
*/ | |
public function generateWritePolicy(string $bucketName): array | |
{ | |
return [ | |
"Version" => "2012-10-17", | |
"Statement" => [ | |
[ | |
"Effect" => "Allow", | |
"Action" => ["s3:PutObject", "s3:ListBucket"], | |
"Resource" => [ | |
"arn:aws:s3:::{$bucketName}/*", | |
"arn:aws:s3:::{$bucketName}" | |
] | |
] | |
] | |
]; | |
} | |
/** | |
* Generate a read-only policy for a bucket | |
* | |
* @param string $bucketName | |
* @return array | |
*/ | |
public function generateReadPolicy(string $bucketName): array | |
{ | |
return [ | |
"Version" => "2012-10-17", | |
"Statement" => [ | |
[ | |
"Effect" => "Allow", | |
"Action" => ["s3:GetObject", "s3:ListBucket"], | |
"Resource" => [ | |
"arn:aws:s3:::{$bucketName}/*", | |
"arn:aws:s3:::{$bucketName}" | |
] | |
] | |
] | |
]; | |
} | |
/** | |
* Generate a read-write policy for a bucket | |
* | |
* @param string $bucketName | |
* @return array | |
*/ | |
public function generateReadWritePolicy(string $bucketName): array | |
{ | |
return [ | |
"Version" => "2012-10-17", | |
"Statement" => [ | |
[ | |
"Effect" => "Allow", | |
"Action" => [ | |
"s3:GetObject", | |
"s3:PutObject", | |
"s3:ListBucket", | |
"s3:DeleteObject" | |
], | |
"Resource" => [ | |
"arn:aws:s3:::{$bucketName}/*", | |
"arn:aws:s3:::{$bucketName}" | |
] | |
] | |
] | |
]; | |
} | |
/** | |
* Create a policy from JSON file | |
* | |
* @param string $policyName | |
* @param string $jsonPolicyFilePath | |
* @return mixed | |
*/ | |
public function createPolicy(string $policyName, string $jsonPolicyFilePath) | |
{ | |
if (!file_exists($jsonPolicyFilePath)) { | |
throw new InvalidArgumentException("Policy file not found: {$jsonPolicyFilePath}"); | |
} | |
return $this->runCommand([ | |
$this->mcPath, 'admin', 'policy', 'create', | |
$this->alias, $policyName, $jsonPolicyFilePath | |
]); | |
} | |
/** | |
* Create a policy from array | |
* | |
* @param string $policyName | |
* @param array $policyData | |
* @return mixed | |
*/ | |
public function createPolicyFromArray(string $policyName, array $policyData) | |
{ | |
$tempFile = tempnam(sys_get_temp_dir(), 'minio_policy_'); | |
if (!$tempFile) { | |
throw new Exception('Cannot create temporary file for policy'); | |
} | |
try { | |
file_put_contents($tempFile, json_encode($policyData, JSON_PRETTY_PRINT)); | |
$result = $this->createPolicy($policyName, $tempFile); | |
return $result; | |
} finally { | |
unlink($tempFile); | |
} | |
} | |
/** | |
* Attach a policy to a user | |
* | |
* @param string $userName | |
* @param string $policyName | |
* @return mixed | |
*/ | |
public function attachPolicy(string $userName, string $policyName) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'admin', 'policy', 'attach', | |
$this->alias, $policyName, '--user', $userName | |
]); | |
} | |
/** | |
* Detach a policy from a user | |
* | |
* @param string $userName | |
* @param string $policyName | |
* @return mixed | |
*/ | |
public function detachPolicy(string $userName, string $policyName) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'admin', 'policy', 'detach', | |
$this->alias, $policyName, '--user', $userName | |
]); | |
} | |
/** | |
* Delete a policy | |
* | |
* @param string $policyName | |
* @return mixed | |
*/ | |
public function deletePolicy(string $policyName) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'admin', 'policy', 'rm', | |
$this->alias, $policyName | |
]); | |
} | |
/** | |
* Set access control for a bucket | |
* | |
* @param string $bucketName | |
* @param string $accessControl (none|download|upload|public) | |
* @return mixed | |
*/ | |
public function setAccessControl(string $bucketName, string $accessControl) | |
{ | |
$validControls = ['none', 'download', 'upload', 'public']; | |
if (!in_array($accessControl, $validControls)) { | |
throw new InvalidArgumentException( | |
"Invalid access control. Must be one of: " . implode(', ', $validControls) | |
); | |
} | |
return $this->runCommand([ | |
$this->mcPath, 'anonymous', 'set', $accessControl, | |
"{$this->alias}/{$bucketName}" | |
]); | |
} | |
/** | |
* Make an object publicly downloadable | |
* | |
* @param string $bucketName | |
* @param string $objectName | |
* @return mixed | |
*/ | |
public function setObjectPublic(string $bucketName, string $objectName) | |
{ | |
return $this->runCommand([ | |
$this->mcPath, 'anonymous', 'set', 'download', | |
"{$this->alias}/{$bucketName}/{$objectName}" | |
]); | |
} | |
/** | |
* Get public share URL for an object | |
* | |
* @param string $bucketName | |
* @param string $objectName | |
* @return string|false | |
*/ | |
public function getShareUrl(string $bucketName, string $objectName) | |
{ | |
try { | |
$output = $this->runCommand([ | |
$this->mcPath, 'anonymous', 'links', | |
"{$this->alias}/{$bucketName}/{$objectName}" | |
]); | |
if (isset($output['status']) && $output['status'] === 'success') { | |
return $output['url'] ?? false; | |
} | |
return false; | |
} catch (Exception $e) { | |
return false; | |
} | |
} | |
/** | |
* List buckets | |
* | |
* @return mixed | |
*/ | |
public function listBuckets() | |
{ | |
return $this->runCommand([$this->mcPath, 'ls', $this->alias]); | |
} | |
/** | |
* List objects in a bucket | |
* | |
* @param string $bucketName | |
* @param bool $recursive | |
* @return mixed | |
*/ | |
public function listObjects(string $bucketName, bool $recursive = false) | |
{ | |
$command = [$this->mcPath, 'ls']; | |
if ($recursive) { | |
$command[] = '--recursive'; | |
} | |
$command[] = "{$this->alias}/{$bucketName}"; | |
return $this->runCommand($command); | |
} | |
/** | |
* Get server configuration | |
* | |
* @return array | |
*/ | |
public function getConfig(): array | |
{ | |
return $this->config; | |
} | |
/** | |
* Get server alias | |
* | |
* @return string | |
*/ | |
public function getAlias(): string | |
{ | |
return $this->alias; | |
} | |
/** | |
* Get endpoint URL | |
* | |
* @return string | |
*/ | |
public function getEndpoint(): string | |
{ | |
return $this->endpoint; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment