Almacenamiento de archivos en Buckets S3 usando AWS SDK y PHP

Camilo Herrera
winkhostingla
Published in
17 min readFeb 9, 2023
Photo by Artem Maltsev on Unsplash

Todos queremos guardar archivos y que nos cueste poco dinero, tu servidor quiere guardar archivos, tu PC, tu contenedor Docker, tu vecino, y hasta tu abuelita. Todos queremos almacenamiento confiable, accesible y barato.

En mi caso particular, no quería pagar por almacenamiento tradicional para guardar backups, al final los backups se utilizan pocas veces. No sería dinero bien invertido y recuerda que en nuestros días con IaaS, un mal cálculo en el uso de recursos te puede dejar en la calle.

Pro tip: Busca siempre un proveedor de almacenamiento o de IaaS con precios fijos y predecibles. Si, te hablo a tí usuario de Google Cloud, Microsoft Azure y Amazon AWS, no sé como duermes tranquilo por las noches.

Así que, mientras paseaba a mi perro en el parque tomándome un café, (ahí es cuando surgen las buenas ideas, camina más!), me pregunté:

¿Cómo voy a almacenar más de 10TB de backups que sean accesibles, fáciles de sincronizar, descargar y restaurar… a un buen precio?

Bueno mi querido lector, la respuesta es fría, se llama “Cold Storage” y su apellido es S3.

Al recibir esa respuesta que me susurraba la brisa fría de la mañana, corrí a casa y dejé a mi perro en el parque muy seguro de que sabría como regresar por si mismo, y si no lo lograba no era digno de ser mi mascota.

Me dispuse a investigar y esta fue la solución:

Lo que vamos a necesitar

Revisemos la lista de cosas que vamos a necesitar antes de empezar:

  • Un bucket S3 obviamente para pruebas
  • Una librería compatible con PHP para conectarnos a un bucket
  • Un entorno de desarrollo y pruebas para tus scripts, tu PC será suficiente.
  • Café caliente
  • Buena música

El Bucket S3

Para nuestra solución, cualquier proveedor de buckets S3 servirá, siempre y cuando se mantenga apegado a la definición estandar de la API REST diseñada por Amazon.

Puedes abrir una cuenta en AWS que te permite usar gratis por 12 meses hasta 5GB, puedes ir a IDrive.com que te permite hasta 10GB gratis ó puedes contactarnos y contratar con nosotros en Winkhosting.co.

Sin importar la opción que selecciones, sigue las instrucciones para crear un bucket y llámalo “testing” o “mybucket” o “B. McBucket”, cualquier nombre que quieras.

A continuación dirígete a la opción “Access Keys”, en la mayoría de proveedores tiene el mismo nombre. Allí crea un… “access key” para permitir el ingreso al bucket y realizar operaciones de lectura/escritura.

La información generada para tu Access Key será algo como esto:

Region: us-west
Endpoint: my.bucketproviderendpoint.com
Access Key ID: ez4GrOLuSx9E5VlEI0ir
Secret Access Key: RMzyjCTR95VM8ilYIZuxf0VvOKoNh4209bIUuthN

Recuerda que necesitas estos cuatro parámetros para establecer la conexión. (Región, Endpoint, Access Key ID y Secret Access Key).

Eso es todo, con estos datos y nuestro bucket listo podemos continuar.

La librería PHP

Buscar la librería fue relativamente fácil. Amazon ya provee un SDK para PHP bastante completo.

Dirígete aquí y descarga el archivo .phar que se encuentra en la sección de instalación bajo el título “Installing by using the packaged phar”. El archivo debe quedar guardado con el nombre “aws.phar” en tu PC o servidor.

Implementación

Bien, vamos a la parte interesante de este asunto. Primero algunos requisitos a tener presente:

  • Estamos usando PHP 8.1.6
  • Por defecto PHP incluye soporte para archivos .phar, si tienes alguna dificultad intentando usarlos puedes consultar la documentación aquí
  • Implementaremos un componente destinado para consola pero con algunos ajustes puede ser usado en web si eres valiente.

Iniciaremos por crear nuestro directorio de la solución, yo lo llamaré “s3commander” porque suena profesional y le da personalidad. Tu puedes llamarlo como quieras, eres un adulto y ya puedes tomar tus propias decisiones.

En nuestro directorio “s3commander” guardaremos el archivo aws.phar que descargamos previamente y crearemos dos archivos y dos directorios, el primer archivo lo nombraremos “run.php”, (este será nuestro punto de entrada para ejecutar las acciones de nuestro componente) y el segundo archivo será “S3Commander.php”, (este contendrá la clase que usaremos para implementar el comportamiento y atributos de nuestro componente).

Con respecto a los dos directorios, el primero será “myfiles”, los archivos en él serán cargados al bucket, el segundo directorio se llamará “mydownloadedfiles” y será usado, (como su nombre lo indica), para guardar los archivos descargados desde el bucket a nuestro PC o Servidor.

La estructura de archivos y directorios quedará así:

/s3commander
/myfiles
/mydownloadedfiles
run.php
S3Commander.php
aws.phar

Si quieres generar algunos archivos de prueba para cargar, puedes usar el siguiente comando en Windows usando PowerShell dentro del directorio /myfiles:

for ($num=1 ; $num -le 130; $num++){ fsutil file createnew testfile_$num.txt 100000 }

o en linux:

for i in {1..130}; do dd if=/dev/urandom bs=1024 count=100 of=testfile_${i}.txt; done

Serán creados 130 archivos de 100K de tamaño cada uno.

Ahora vamos a explorar los dos archivos importantes, “S3Commander.php” y “run.php”.

S3Commander.php

Vamos a iniciar este archivo incluyendo nuestra librería phar descargada de Amazon.

<?php
require __DIR__ . '/aws.phar';

Recuerda que __DIR__ es una constante “mágica” de PHP (todo PHP es mágico, no entiendo para qué hay que decirlo de forma literal pero así lo decidieron.). Está constante retorna el directorio de ejecución actual de tu script, esto nos ayudará a tener control de la ruta una vez la usemos dentro de nuestro componente.

Una vez incluído nuestro archivo phar en “S3Commander.php”, podemos proceder con la creación de nuestra clase.

Iniciaremos con la declaración de la clase, la definición de las propiedades a utilizar y el constructor, así:

Definición de clase, constructor y atributos

<?php
//Incluímos nuestra librería para conectarnos al bucket, gracias Amazon!.
require __DIR__ . '/aws.phar';

class S3Commander
{

//Instancia de la clase Credentials del SDK de Amazon
private Aws\Credentials\Credentials $credentials;
//ID de acceso al bucket
private string $accessKeyID;
//LLave secreta asociada al ID de acceso para autenticar la conexión
private string $secretAccessKey;
//Instancia de la clase S3Client del SDK de Amazon
private Aws\S3\S3Client $s3Client;
//Cadena de texto que contendrá la región donde se encuentra el bucket
private string $region;
//URL del servidor al que se conectará el cliente
private string $endpointURL;
//Nombre del bucket
private string $bucketName;
//Ruta de guardado de los archivos descargados del bucket
private string $downloadPath;
//Flag para definir si se valida el certificado SSL/TLS del cliente,
//para pruebas usar false.
private bool $httpSSLTLSVerify;

/**
* Aquí definimos los valores por defecto para cada uno de los atributos de la clase,
* en este caso a las cadenas de texto les asignamos un valor vacío, verificación de
* SSL/TLS activa y el directorio de descargas "mydownloadedfiles" dentro de la ruta
* donde se encuentre este archivo.
*/
function __construct()
{
$this->accessKeyID = "";
$this->secretAccessKey = "";
$this->region = "";
$this->endpointURL = "";
$this->bucketName = "";
$this->httpSSLTLSVerify = true;
$this->setDownloadPath(__DIR__ . "/mydownloadedfiles");
}

A continuación vamos a definir seis funciones para gestionar la configuración de nuestra conexión al bucket.

Funciones de configuración e inicialización

    /**
* Recibe un Accesss Key ID y su respectivo Secret Access Key para crear un
* objeto Aws\Credentials\Credentials usado por nuestro SDK para autenticar
* la conexión realizada.
* @param string $accesKeyID Access Key ID a usar para la conexión
* @param string $secretAccessKey Llave secreta asociada al Access Key ID
* @return void
*/
public function setCredentials(string $accessKeyID, string $secretAccessKey): void
{
$this->accessKeyID = $accessKeyID;
$this->secretAccessKey = $secretAccessKey;
$this->credentials = new Aws\Credentials\Credentials($this->accessKeyID, $this->secretAccessKey);
}

/**
* Inicializa una instancia de S3Client (Definido en el SDK) para realizar las operaciones
* en el bucket.
* @param string $region Cadena de texto que indica la región geográfica donde se
* encuentra el bucket
* @param string $endpointURL Dirección URL del servidor a donde se conectará el cliente.
* @param bool $httpSSLTLSVerify valor true/false que indica si se debe validar el certificado
* SSL/TLS usado por el cliente para establecer la conexión
* para pruebas se recomienda fijar el valor false, para producción
* true.
* @return void
*/
public function initClient(string $region, string $endpointURL, bool $httpSSLTLSVerify = true): void
{

$this->region = $region;
$this->endpointURL = $endpointURL;
$this->httpSSLTLSVerify = $httpSSLTLSVerify;

$this->s3Client = new Aws\S3\S3Client([
'version' => 'latest',
'region' => $this->region,
'credentials' => $this->credentials,
'endpoint' => $this->endpointURL,
'http' => [
'verify' => $this->httpSSLTLSVerify
]
]);
}

/**
* Setter, permite modificar el nombre del bucket a consultar.
* @param string $bucketName Cadena de texto que contiene el nombre del bucket.
* @return void
*/
public function setBucket(string $bucketName): void
{
$this->bucketName = $bucketName;
}

/**
* Setter, permite modificar la ruta del directorio donde se guardarán archivos descargados.
* Esta función ajusta la ruta si el script se ejecuta en Windows y agrega un / al final si no lo tiene.
* @param string $downloadPath Cadena de texto que contiene la ruta del directorio.
* @return void
*/
public function setDownloadPath(string $downloadPath): void
{
$this->downloadPath = $this->fixPathWindows($downloadPath);

if (!str_ends_with($this->downloadPath, "/")) {
$this->downloadPath .= "/";
}
}

/**
* Esta función presentará el mensaje enviado en el parámetro $messageText en la consola
* @param string $messageText Mensaje a ser mostrado
* @param bool $noDate Permite indicar si se desea agregar la fecha hora y
* milisegundos al inicio del mensaje a mostrar, también
* se agregará al registro, por defecto false que
* indica que siempre se agrega, o true para no hacerlo.
* @return void
*/
private function echoMessage(string $messageText, bool $noDate = false): void
{
$echotext = $messageText . PHP_EOL;

if (!$noDate) {
$echotext = "[" . date("Y-m-d H:i:s.u") . "] " . $echotext;
}

echo $echotext;
}

/**
* Helper, encargado de normalizar el separador de directorios en Windows.
* @param string $filePath Ruta a normalizar
* @return string Ruta normalizada.
*/
private function fixPathWindows(string $filePath): string
{
return str_replace("\\", "/", $filePath);
}

Los comentarios en cada función explican su objetivo, creo que no es necesario entrar en detalles. Lo importante es estas funciones es que nos permiten lo siguiente:

  • Fijar parámetros de autenticación (setCredentials)
  • Inicializar nuestro cliente S3 con el URL de endpoint, región y verificación de certificado SSL/TLS (initClient)
  • Fijar el nombre del bucket (setBucket)
  • Fijar la ruta de guardado de archivos descargados (setDownloadPath)
  • Mostrar mensajes en pantalla (echoMessage)
  • Ajustar las rutas en sistema operativo Windows para normalizar el separador de directorios (fixPathWindows).

Más adelante veremos su utilización, ahora vamos a las acciones que podemos realizar en el bucket, serán cuatro.

Funciones asociadas a acciones en un bucket

    /**
* Esta función se encarga de cargar un archivo al bucket.
* @param string $filePath Ruta completa del archivo a cargar.
* @return void
*/
public function putFile(string $filePath): void
{
$filePath = $this->fixPathWindows($filePath);

$fileName = basename($filePath);

try {

if (!file_exists($filePath)) {
throw new Exception("El archivo {$filePath} no existe");
}

$putRes = $this->s3Client->putObject([
'Bucket' => $this->bucketName,
'Key' => $fileName,
'Body' => fopen($filePath, 'r'),
'ACL' => 'private',
]);

$this->echoMessage("El archivo {$filePath} fue cargado al bucket {$this->bucketName}");
$this->echoMessage("URL del archivo: " . $putRes->get('ObjectURL'));
} catch (Aws\S3\Exception\S3Exception | Exception $e) {
$this->echoMessage("Ocurrió un error al intentar cargar el archivo en la ruta {$filePath}" . PHP_EOL . $e->getMessage());
}
}

/**
* Esta función se encarga de descargar un archivo al bucket.
* @param string $fileNameInBucket Cadena de texto con el nombre del archivo
* en términos de bucket sería el Key del objeto.
* @return void
*/
public function getFile(string $fileNameInBucket): void
{

try {

if (!file_exists($this->downloadPath)) {
throw new Exception("El directorio de descarga {$this->downloadPath} no existe");
}

$getRes = $this->s3Client->getObject([
'Bucket' => $this->bucketName,
'Key' => $fileNameInBucket,
'SaveAs' => $this->downloadPath . $fileNameInBucket,
]);

$this->echoMessage("El archivo {$fileNameInBucket} fue descargado y guardado en {$this->downloadPath}{$fileNameInBucket}");
} catch (Aws\S3\Exception\S3Exception | Exception $e) {
$this->echoMessage("Ocurrió un error al intentar descargar el archivo {$fileNameInBucket} en la ruta {$this->downloadPath}" . PHP_EOL . $e->getMessage());
}
}

/**
* Borra un archivo (objeto) en el bucket.
* @param string $fileNameInBucket Cadena de texto con el nombre del archivo
* en términos de bucket sería el Key del objeto.
* @return void
*/
public function delFile(string $fileNameInBucket): void
{

try {

$putRes = $this->s3Client->deleteObject([
'Bucket' => $this->bucketName,
'Key' => $fileNameInBucket
]);

$this->echoMessage("El archivo {$fileNameInBucket} fue borrado del bucket {$this->bucketName}");
} catch (Aws\S3\Exception\S3Exception | Exception $e) {
$this->echoMessage("Ocurrió un error al intentar borrar el archivo {$fileNameInBucket} en el bucket {$this->bucketName}" . PHP_EOL . $e->getMessage());
}
}

/**
* Genera un listado completo de los archivos en el bucket, si son muchos ya sabes que puede
* tardar un tiempo en finalizar o generar una salida a consola bastante extensa. Procede
* con precaución.
* @return void
*/
public function listAll(): void
{
$continuationToken = "";

do {

$config = [
"Bucket" => $this->bucketName,
"MaxKeys" => 50
];

if (!empty($continuationToken)) {
$config["ContinuationToken"] = $continuationToken;
}

$resultFiles = $this->s3Client->listObjectsV2($config);
$resultFiles = $resultFiles->toArray();

foreach ($resultFiles["Contents"] as $bucketObject) {
$this->echoMessage("Nombre: " . $bucketObject["Key"] . " Modificado: " . $bucketObject["LastModified"]->__toString());
}

if (!isset($resultFiles["IsTruncated"])) {
$resultFiles["IsTruncated"] = 0;
}

if ($resultFiles["IsTruncated"] == 1) {
$continuationToken = $resultFiles["NextContinuationToken"];
}
} while ($resultFiles["IsTruncated"] == 1);
}

Como puedes ver, podemos realizar las siguientes acciones:

  • Cargar archivos (putFile)
  • Descargar archivos (getFile)
  • Borrar archivo (delFile)
  • Listar todos los archivos en el bucket (listAll), esta última función debe ser usada con precaución, puede generar una salida a pantalla bastante extensa, pero no te preocupes, el código por lo menos cuenta con paginación de los resultados descargados para evitar un alto consumo de memoria, (no somos programadores vulgares… bueno no tan vulgares).

Ahora que tenemos los instrumentos musicales, vamos a ver como logran la armonía en la ejecución cuando suenan juntos. Nuestra clase finalmente quedará de la siguiente forma:

Clase final completa S3Commander

<?php
//Incluímos nuestra librería para conectarnos al bucket, gracias Amazon!.
require __DIR__ . '/aws.phar';

class S3Commander
{

//Instancia de la clase Credentials del SDK de Amazon
private Aws\Credentials\Credentials $credentials;
//ID de acceso al bucket
private string $accessKeyID;
//LLave secreta asociada al ID de acceso para autenticar la conexión
private string $secretAccessKey;
//Instancia de la clase S3Client del SDK de Amazon
private Aws\S3\S3Client $s3Client;
//Cadena de texto que contendrá la región donde se encuentra el bucket
private string $region;
//URL del servidor al que se conectará el cliente
private string $endpointURL;
//Nombre del bucket
private string $bucketName;
//Ruta de guardado de los archivos descargados del bucket
private string $downloadPath;
//Flag para definir si se valida el certificado SSL/TLS del cliente, para pruebas usar false.
private bool $httpSSLTLSVerify;

/**
* Aquí definimos los valores por defecto para cada uno de los atributos de la clase,
* en este caso a las cadenas de texto les asignamos un valor vacío, verificación de
* SSL/TLS activa y el directorio de descargas "mydownloadedfiles" dentro de la ruta
* donde se encuentre este archivo.
*/
function __construct()
{
$this->accessKeyID = "";
$this->secretAccessKey = "";
$this->region = "";
$this->endpointURL = "";
$this->bucketName = "";
$this->httpSSLTLSVerify = true;
$this->setDownloadPath(__DIR__ . "/mydownloadedfiles");
}

/**
* Recibe un Accesss Key ID y su respectivo Secret Access Key para crear un
* objeto Aws\Credentials\Credentials usado por nuestro SDK para autenticar
* la conexión realizada.
* @param string $accesKeyID Access Key ID a usar para la conexión
* @param string $secretAccessKey Llave secreta asociada al Access Key ID
* @return void
*/
public function setCredentials(string $accessKeyID, string $secretAccessKey): void
{
$this->accessKeyID = $accessKeyID;
$this->secretAccessKey = $secretAccessKey;
$this->credentials = new Aws\Credentials\Credentials($this->accessKeyID, $this->secretAccessKey);
}

/**
* Inicializa una instancia de S3Client (Definido en el SDK) para realizar las operaciones
* en el bucket.
* @param string $region Cadena de texto que indica la región geográfica donde se
* encuentra el bucket
* @param string $endpointURL Dirección URL del servidor a donde se conectará el cliente.
* @param bool $httpSSLTLSVerify valor true/false que indica si se debe validar el certificado
* SSL/TLS usado por el cliente para establecer la conexión
* para pruebas se recomienda fijar el valor false, para producción
* true.
* @return void
*/
public function initClient(string $region, string $endpointURL, bool $httpSSLTLSVerify = true): void
{

$this->region = $region;
$this->endpointURL = $endpointURL;
$this->httpSSLTLSVerify = $httpSSLTLSVerify;

$this->s3Client = new Aws\S3\S3Client([
'version' => 'latest',
'region' => $this->region,
'credentials' => $this->credentials,
'endpoint' => $this->endpointURL,
'http' => [
'verify' => $this->httpSSLTLSVerify
]
]);
}

/**
* Setter, permite modificar el nombre del bucket a consultar.
* @param string $bucketName Cadena de texto que contiene el nombre del bucket.
* @return void
*/
public function setBucket(string $bucketName): void
{
$this->bucketName = $bucketName;
}

/**
* Setter, permite modificar la ruta del directorio donde se guardarán archivos descargados.
* Esta función ajusta la ruta si el script se ejecuta en Windows y agrega un / al final si no lo tiene.
* @param string $downloadPath Cadena de texto que contiene la ruta del directorio.
* @return void
*/
public function setDownloadPath(string $downloadPath): void
{
$this->downloadPath = $this->fixPathWindows($downloadPath);

if (!str_ends_with($this->downloadPath, "/")) {
$this->downloadPath .= "/";
}
}

/**
* Esta función se encarga de cargar un archivo al bucket.
* @param string $filePath Ruta completa del archivo a cargar.
* @return void
*/
public function putFile(string $filePath): void
{
$filePath = $this->fixPathWindows($filePath);

$fileName = basename($filePath);

try {

if (!file_exists($filePath)) {
throw new Exception("El archivo {$filePath} no existe");
}

$putRes = $this->s3Client->putObject([
'Bucket' => $this->bucketName,
'Key' => $fileName,
'Body' => fopen($filePath, 'r'),
'ACL' => 'private',
]);

$this->echoMessage("El archivo {$filePath} fue cargado al bucket {$this->bucketName}");
$this->echoMessage("URL del archivo: " . $putRes->get('ObjectURL'));
} catch (Aws\S3\Exception\S3Exception | Exception $e) {
$this->echoMessage("Ocurrió un error al intentar cargar el archivo en la ruta {$filePath}" . PHP_EOL . $e->getMessage());
}
}

/**
* Esta función se encarga de descargar un archivo al bucket.
* @param string $fileNameInBucket Cadena de texto con el nombre del archivo
* en términos de bucket sería el Key del objeto.
* @return void
*/
public function getFile(string $fileNameInBucket): void
{

try {

if (!file_exists($this->downloadPath)) {
throw new Exception("El directorio de descarga {$this->downloadPath} no existe");
}

$getRes = $this->s3Client->getObject([
'Bucket' => $this->bucketName,
'Key' => $fileNameInBucket,
'SaveAs' => $this->downloadPath . $fileNameInBucket,
]);

$this->echoMessage("El archivo {$fileNameInBucket} fue descargado y guardado en {$this->downloadPath}{$fileNameInBucket}");
} catch (Aws\S3\Exception\S3Exception | Exception $e) {
$this->echoMessage("Ocurrió un error al intentar descargar el archivo {$fileNameInBucket} en la ruta {$this->downloadPath}" . PHP_EOL . $e->getMessage());
}
}

/**
* Borra un archivo (objeto) en el bucket.
* @param string $fileNameInBucket Cadena de texto con el nombre del archivo
* en términos de bucket sería el Key del objeto.
* @return void
*/
public function delFile(string $fileNameInBucket): void
{

try {

$putRes = $this->s3Client->deleteObject([
'Bucket' => $this->bucketName,
'Key' => $fileNameInBucket
]);

$this->echoMessage("El archivo {$fileNameInBucket} fue borrado del bucket {$this->bucketName}");
} catch (Aws\S3\Exception\S3Exception | Exception $e) {
$this->echoMessage("Ocurrió un error al intentar borrar el archivo {$fileNameInBucket} en el bucket {$this->bucketName}" . PHP_EOL . $e->getMessage());
}
}

/**
* Genera un listado completo de los archivos en el bucket, si son muchos ya sabes que puede
* tardar un tiempo en finalizar o generar una salida a consola bastante extensa. Procede
* con precaución.
* @return void
*/
public function listAll(): void
{
$continuationToken = "";

do {

$config = [
"Bucket" => $this->bucketName,
"MaxKeys" => 50
];

if (!empty($continuationToken)) {
$config["ContinuationToken"] = $continuationToken;
}

$resultFiles = $this->s3Client->listObjectsV2($config);
$resultFiles = $resultFiles->toArray();

foreach ($resultFiles["Contents"] as $bucketObject) {
$this->echoMessage("Nombre: " . $bucketObject["Key"] . " Modificado: " . $bucketObject["LastModified"]->__toString());
}

if (!isset($resultFiles["IsTruncated"])) {
$resultFiles["IsTruncated"] = 0;
}

if ($resultFiles["IsTruncated"] == 1) {
$continuationToken = $resultFiles["NextContinuationToken"];
}
} while ($resultFiles["IsTruncated"] == 1);
}

/**
* Esta función presentará el mensaje enviado en el parámetro $messageText en la consola
* @param string $messageText Mensaje a ser mostrado en el registro
* @param bool $noDate Permite indicar si se desea agregar la fecha hora y
* milisegundos al inicio del mensaje a mostrar, también
* se agregará al registro, por defecto false que
* indica que siempre se agrega, o true para no hacerlo.
* @return void
*/
private function echoMessage(string $messageText, bool $noDate = false): void
{
$echotext = $messageText . PHP_EOL;

if (!$noDate) {
$echotext = "[" . date("Y-m-d H:i:s.u") . "] " . $echotext;
}

echo $echotext;
}

/**
* Helper, encargado de normalizar el separador de directorios en Windows.
* @param string $filePath Ruta a normalizar
* @return string Ruta normalizada.
*/
private function fixPathWindows(string $filePath): string
{
return str_replace("\\", "/", $filePath);
}
}

Continuemos con “run.php

Run.php

Este archivo lo usaremos para probar las funciones creadas en nuestro componente y se verá así:

<?php
//Incluímos el archivo de nuestra clase.
require __DIR__ . '/S3Commander.php';

/**
* En esta sección inicializamos algunas constantes a usar en las pruebas,
* estos son los valores que deberías modificar para reflejar el entorno
* y datos de acceso al bucket que vayas a usar.
*
* $_FILES_DIR es la ruta donde se encuentran tus archivos a cargar *
* $_ACCESS_KEY_ID Identificador de acceso al bucket
* $_SECRET_ACCESS_KEY Llave secreta asociada al ID para autenticar
* la conexión al bucket.
* $_REGION La región donde está ubicado el bucket, esta información
* es entregada por el proveedor del servicio al crearlo.
* $_BUCKET Nombre del bucket
* $_ENDPOINT URL de conexión al servidor donde se encuentra el bucket
* $_DOWNLOAD_DIR Directorio donde serán guardados los archivos
* que sean descargados del bucket.
*/
$_FILES_DIR = str_replace("\\", "/", __DIR__) . "/myfiles";
$_ACCESS_KEY_ID = "rl0z4GeOuE5V9riLSEIx";
$_SECRET_ACCESS_KEY = "bIUilY4209uthVMIZNRMzy5uoNh8xf0VvOK9jCTR";
$_REGION = "Oregon";
$_BUCKET = "testing";
$_ENDPOINT = "https://my.endpointurl.com";
$_DOWNLOAD_DIR = str_replace("\\", "/", __DIR__) . "/mydownloadedfiles";

//Instanciamos el objeto
$s3Comm = new S3Commander();

//Fijamos las credenciales de acceso
$s3Comm->setCredentials($_ACCESS_KEY_ID, $_SECRET_ACCESS_KEY);

/**
* Fijamos la región y el URL del endpoint, adicionalmente se incluye
* un parámetro para determinar si se valida localmente el certificado
* SSL/TLS usado, para efectos prácticos y de prueba no validamos esto
* pero en producción si debe hacerse por seguridad.
*/
$s3Comm->initClient($_REGION, $_ENDPOINT, false);

//Fijamos el nombre del bucket
$s3Comm->setBucket($_BUCKET);

//Fijamos la ruta de guardado de los archivos descargados.
$s3Comm->setDownloadPath($_DOWNLOAD_DIR);

/**
* Para este ejemplo vamos a generar una lista sencilla de los archivos
* en el directorio predefinido /myfiles, si necesitas cargar una estructura
* de directorios o archivos más elaborada vas a necesitar cambios en
* la función de carga en la clase.
*/
$arrUploadFiles = array_diff(
scandir($_FILES_DIR),
array('..', '.')
);

foreach ($arrUploadFiles as $key => $fileName) {
//Cargamos cada uno de los archivos en el directorio definido.
$s3Comm->putFile($_FILES_DIR . "/" . $fileName);
}

//listamos el contenido del bucket.
$s3Comm->listAll();

//Prueba de descarga de un archivo
$s3Comm->getFile("testfile_1.txt");

//Prueba de borrado de un archivo en el bucket
$s3Comm->delFile("testfile_1.txt");

//Fin.

Lo primero que hacemos en nuestro archivo es incluir nuestro la definición de la clase S3Commander y, a continuación, inicializamos algunos valores requeridos para configurar el cliente S3:

  • $_FILES_DIR Es la ruta del directorio donde se encuentran los archivos que cargaremos al bucket.
  • $_ACCESS_KEY_ID Es el identificador de acceso al bucket que fue generado en el proveedor y nos permitirá conectarnos a él.
  • $_SECRET_ACCESS_KEY Es la llave secreta asociada al Access Key ID para autenticar la conexión al bucket.
  • $_REGION Indicará la región donde está ubicado el bucket, esta información es entregada por el proveedor del servicio al crearlo.
  • $_BUCKET Esta variable contiene el nombre del bucket al que nos conectaremos.
  • $_ENDPOINT Contiene el URL de conexión al servidor donde se encuentra el bucket, normalmente inicia con https://
  • $_DOWNLOAD_DIR Es el directorio donde serán guardados los archivos que sean descargados del bucket. Debe existir y ser un directorio al que pueda acceder el script.

A continuación instanciaremos la clase S3Commander, fijamos las credenciales de acceso (setCredentials), inicializamos el cliente con los datos del servidor que contiene nuestro bucket (initClient), seleccionamos el bucket (setBucket) y fijamos el directorio de descarga (setDownloadPath).

Luego vamos a proceder con la lectura de los nombres de archivo en el directorio “myfiles” y los cargaremos en un arreglo $arrUploadFiles para realizar la prueba de carga al bucket.

Al recorrer el arreglo de nombres de archivo iremos cargando cada uno de ellos usando la función “putFile” de nuestro objeto $s3Comm, se mostrará en la consola el resultado de la carga de cada uno de ellos

Cuando finalice la carga de archivos, usaremos la función “listAll” para consultar el bucket y generar el listado de archivos con su última fecha de modificación (cuando fueron cargados) y mostrarlos en la consola.

Para finalizar, realizaremos una prueba descargando uno de los archivos en el bucket con el nombre “testfile_1.txt”, si todo sale como esperamos, el archivo será guardado en “mydownloadedfiles” y será mostrado el mensaje del resultado en la consola. Luego, borraremos el archivo con el mismo nombre en el bucket (la copia en nuestro directorio local se mantiene).

Ejecutando nuestro script

Para realizar la prueba, fija los valores de configuración en las variables dentro de “run.php”, cuando estén listos verifica que tengas archivos por cargar en “myfiles”, inicia una consola de comandos, ingresa al directorio “s3commander” y ejecuta el script así:

>php run.php

Estoy asumiendo que php es un comando que tienes preconfigurado para ejecutar en cualquier directorio, es decir que es global, de lo contrario tendrías que usar la ruta completa del ejecutable en el comando para que funcione.

Si todo sale bien, el resultado mostrado será algo de este estilo:

#Para archivos cargados así:
[2023-02-09 18:20:23.000000] El archivo /s3commander/myfiles/testfile_1.txt fue cargado al bucket testing
[2023-02-09 18:20:23.000000] URL del archivo: https://my.endpoint.com/testfile_1.txt
#Listado de archivos así:
[2023-02-09 18:20:47.000000] Nombre: testfile_1.txt Modificado: 2023-02-09T18:20:20+00:00
[2023-02-09 18:20:47.000000] Nombre: testfile_10.txt Modificado: 2023-02-09T18:20:21+00:00
#Archivo descargado
[2023-02-09 18:20:48.000000] El archivo testfile_1.txt fue descargado y guardado en /s3commander/mydownloadedfiles/testfile_1.txt
#Archivo borrado
[2023-02-09 18:20:48.000000] El archivo testfile_1.txt fue borrado del bucket testing

Y eso es todo. Ya puedes implementar este tipo de almacenamiento para mover tus backups y almacenarlos de forma segura, no olvides probar regularmente tus archivos de backups y tomar mucha agua.

Y si sigues pensando en qué le pasó a mi perro, está bien, adjunto una prueba gráfica haciendo lo que más le gusta:

Y recuerda que en Winkhosting.co somos mucho más que hosting!.

--

--