Skip to content

Instantly share code, notes, and snippets.

@Bharat-B
Created September 11, 2025 08:54
Show Gist options
  • Save Bharat-B/db7da81375a18d264b01d4e605ecc7fd to your computer and use it in GitHub Desktop.
Save Bharat-B/db7da81375a18d264b01d4e605ecc7fd to your computer and use it in GitHub Desktop.
Minio PHP Wrapper
<?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