Exportar arquivos para o Amazon S3 com PHP: guia completo
Para exportar um arquivo local para o Amazon S3 com PHP, instale o AWS SDK e chame
putObject com um SourceFile. Este guia cria um
script de CLI que armazena objetos privados com metadados e criptografia no servidor, informa
falhas pelo status de saída e oferece suporte a uploads multipart.
Você também fará o download do objeto de forma independente para verificar se o S3 recebeu
exatamente os mesmos bytes.
Pré-requisitos e configuração
Use um shell Bash no Linux com PHP, Composer 2 e AWS CLI v2 instalados. Os exemplos foram testados com PHP 8.5.10, Composer 2.10.3, AWS CLI 2.37.10 e AWS SDK for PHP 3.399.2. O PHP 8.5 é uma linha de versões com manutenção ativa; use sua versão de correção atual em novas instalações. Essas são as versões testadas, e não as versões mínimas exigidas pelo SDK.
O SDK exige PHP 8.1 ou posterior e SimpleXML.
Este tutorial também usa as extensões cURL e OpenSSL do PHP para HTTPS. Instale as ferramentas
que faltam seguindo o guia de instalação do PHP,
as instruções de download do Composer e as
instruções de instalação da AWS CLI v2.
O GNU cmp, usado para
comparar arquivos, é fornecido pelo pacote GNU Diffutils.
Antes de criar os arquivos do projeto, verifique cada pré-requisito:
(
for tool in php composer aws cmp; do
if ! command -v "$tool" >/dev/null 2>&1; then
printf 'Missing prerequisite: %s\n' "$tool" >&2
exit 1
fi
done
php -r 'foreach (["simplexml", "curl", "openssl"] as $extension) {
if (!extension_loaded($extension)) {
fwrite(STDERR, "Missing PHP extension: $extension\n"); exit(1);
}
}'
)
Instalar o AWS SDK for PHP
Trabalhe em um diretório novo e vazio, fora do diretório web público da sua aplicação. Salve o
conteúdo abaixo como composer.json; não substitua o manifesto de uma
aplicação existente:
{
"require": {
"aws/aws-sdk-php": "3.399.2"
}
}
Instale a versão testada do SDK na pasta vendor/ desse diretório:
COMPOSER=composer.json COMPOSER_VENDOR_DIR=vendor \
composer install --no-interaction --no-plugins --no-scripts
Mantenha o composer.lock gerado para que as instalações sejam reproduzíveis.
Se a instalação falhar, corrija o problema de pré-requisito ou de rede informado e repita o mesmo
comando nesse diretório. Você não precisa excluir o manifesto nem o arquivo de bloqueio.
O guia de uso básico do Composer explica como o arquivo de
bloqueio fixa as versões das dependências.
Configurar as credenciais da AWS
Use um perfil existente e autorizado da AWS. Tanto a CLI quanto o SDK oferecem suporte ao provedor de credenciais compartilhadas. Escolha o perfil e a região do seu bucket e, em seguida, verifique a conta antes de fazer o upload:
export AWS_PROFILE=default
export AWS_DEFAULT_REGION=us-east-1
aws sts get-caller-identity --query Account --output text
Substitua default e us-east-1 pelos seus valores.
Se as credenciais estiverem expiradas, renove sua sessão autorizada antes de continuar.
As credenciais do ambiente têm precedência sobre os perfis, portanto verifique se há um conjunto
de credenciais exportado por engano quando a conta não for a esperada. Nunca coloque chaves de
acesso no arquivo PHP. Em aplicações hospedadas na AWS, prefira uma função IAM para a carga de
trabalho em vez de chaves de longa duração.
Criar um bucket S3 simples
O script usa um bucket S3 existente, privado e de uso geral. Se você precisar de um, peça ao
administrador do seu bucket que siga o guia de criação de buckets da AWS.
Mantenha Block Public Access ativado e Object Ownership definido como Bucket owner enforced.
Use a região real do bucket em AWS_DEFAULT_REGION; a administração do bucket
é separada do script de exportação.
Para os comandos abaixo, defina um nome de bucket e uma nova chave de objeto sob um prefixo seu:
export S3_BUCKET=your-private-bucket
export S3_KEY=php-export-test/sample.bin
Substitua os dois valores de exemplo. Fazer upload para uma chave existente substitui seu objeto atual ou cria uma nova versão se o versionamento estiver ativado. Use chaves únicas nesses testes e execute uma exportação por vez.
Desenvolver um script PHP para exportar arquivos
Salve este programa completo como export.php, ao lado de
composer.json. Seus argumentos são o arquivo local, o bucket, a chave do objeto
e um modo opcional, que pode ser put ou multipart:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Aws\Exception\AwsException;
use Aws\Exception\MultipartUploadException;
use Aws\S3\MultipartUploader;
use Aws\S3\S3Client;
$mode = $argv[4] ?? 'put';
if ($argc < 4 || $argc > 5 || !in_array($mode, ['put', 'multipart'], true)) {
fwrite(STDERR, "Usage: php export.php LOCAL_FILE BUCKET KEY [put|multipart]\n");
exit(2);
}
[$script, $sourceFile, $bucket, $key] = $argv;
$region = getenv('AWS_DEFAULT_REGION');
if (!$region || $bucket === '' || $key === '') {
fwrite(STDERR, "Set AWS_DEFAULT_REGION and provide a nonempty bucket and key.\n");
exit(2);
}
if (!is_file($sourceFile) || !is_readable($sourceFile)) {
fwrite(STDERR, "Cannot read source file: $sourceFile\n");
exit(2);
}
try {
$s3Client = new S3Client([
'version' => '2006-03-01',
'region' => $region,
'retries' => 0,
'http' => ['connect_timeout' => 10, 'timeout' => 60],
]);
$options = [
'Bucket' => $bucket,
'Key' => $key,
'ContentType' => 'application/octet-stream',
'Metadata' => [
'original-filename' => rawurlencode(basename($sourceFile)),
'upload-date' => gmdate('c'),
],
'ServerSideEncryption' => 'AES256',
'CacheControl' => 'no-store',
];
if ($mode === 'multipart' && filesize($sourceFile) > 0) {
$uploader = new MultipartUploader($s3Client, $sourceFile, [
'bucket' => $bucket,
'key' => $key,
'part_size' => 5 * 1024 * 1024,
'concurrency' => 1,
'params' => $options,
]);
$uploader->upload();
} else {
$s3Client->putObject($options + ['SourceFile' => $sourceFile]);
}
echo "Export complete: s3://$bucket/$key\n";
} catch (Throwable $e) {
if (isset($uploader)) {
$id = $uploader->getState()->getId();
if (!empty($id['UploadId'])) {
try {
$s3Client->abortMultipartUpload($id);
fwrite(STDERR, "Aborted incomplete multipart upload.\n");
} catch (Throwable $abortError) {
fwrite(STDERR, "Abort failed; clean up upload ID: {$id['UploadId']}\n");
}
}
}
if ($e instanceof MultipartUploadException) {
fwrite(STDERR, "Multipart export failed for: $sourceFile\n");
} elseif ($e instanceof AwsException) {
fwrite(STDERR, 'S3 export failed: ' . ($e->getAwsErrorCode() ?? 'request error') . "\n");
fwrite(STDERR, 'Request ID: ' . ($e->getAwsRequestId() ?? 'unavailable') . "\n");
} else {
fwrite(STDERR, "Export failed; check the local file, SDK installation, and credentials.\n");
}
exit(1);
}
Crie um pequeno arquivo de teste e faça o upload dele:
printf 'S3 export check\n' > sample.bin &&
php export.php ./sample.bin "$S3_BUCKET" "$S3_KEY"
O script imprime Export complete seguido da URI S3 do objeto privado somente depois que o SDK conclui a requisição. A URI identifica o objeto; não é um link público de download. Uma exportação que falha grava informações de diagnóstico em stderr e termina com status diferente de zero. O programa lê seu arquivo local sem excluí-lo; mantenha esse arquivo inalterado até a verificação terminar.
Gerenciar os metadados dos arquivos com eficiência
O array $options aplica os mesmos metadados, tipo de conteúdo, política de
cache e criptografia SSE-S3 aos dois modos de upload. original-filename usa
codificação percentual para que espaços e caracteres não ASCII possam ser representados em um
cabeçalho HTTP de metadados. upload-date registra uma data e hora em UTC.
As chaves S3 são independentes dos nomes de arquivos locais, portanto mantenha a chave completa
para localizar um arquivo exportado.
application/octet-stream é adequado para arquivos binários arbitrários. Defina um
ContentType mais específico quando sua aplicação souber o formato e escolha
CacheControl de acordo com os consumidores do arquivo. O exemplo usa
no-store. AES256 solicita explicitamente
criptografia SSE-S3;
isso não concede acesso público nem substitui os controles de acesso.
Testar e depurar sua integração
Verifique os metadados armazenados e a criptografia e, em seguida, faça o download para um novo diretório local de verificação:
aws s3api head-object --bucket "$S3_BUCKET" --key "$S3_KEY" \
--region "$AWS_DEFAULT_REGION" \
--query '{bytes:ContentLength,type:ContentType,encryption:ServerSideEncryption,metadata:Metadata}'
(
mkdir s3-verify &&
aws s3api get-object --bucket "$S3_BUCKET" --key "$S3_KEY" \
--region "$AWS_DEFAULT_REGION" s3-verify/download.bin &&
cmp -- ./sample.bin s3-verify/download.bin
)
A resposta de metadados deve mostrar AES256, o nome do seu arquivo
e a quantidade esperada de bytes. cmp retorna zero quando todos
os bytes coincidem, inclusive no caso de um arquivo vazio. Use um novo diretório de verificação
em cada execução. Se um download falhar, remova apenas o diretório descartável
s3-verify que você criou e repita o bloco. O bloco recusa um diretório
existente para proteger os downloads anteriores. Não use o ETag do S3 como um hash de arquivo
de uso geral; objetos multipart podem ter uma semântica de ETag diferente.
Teste um arquivo binário não textual seu e um arquivo vazio com chaves distintas antes de integrar o script a uma tarefa. Em uma tarefa agendada, capture stderr e verifique o status de saída do processo. Mantenha credenciais, URLs assinadas e mensagens brutas de exceções do SDK fora dos logs expostos aos usuários.
Casos de uso avançados: uploads multipart
A AWS recomenda uploads multipart para objetos acima de 100 MB.
O script também permite testar esse fluxo com um arquivo de teste menor: escolha um arquivo maior
que 5 MiB para que ele ocupe pelo menos duas partes, salve-o como sample.bin
e selecione uma nova chave:
php export.php ./sample.bin "$S3_BUCKET" "$S3_KEY" multipart
Depois, execute as mesmas verificações de metadados e bytes. Para esse arquivo de teste, o
componente de upload usa partes de 5 MiB, com uma parte em transferência por vez, e aguarda
CompleteMultipartUpload antes de imprimir a mensagem de sucesso. O SDK pode aumentar
o tamanho das partes para arquivos muito maiores. Arquivos vazios usam
putObject mesmo quando você seleciona o modo multipart, pois não têm
partes para montar.
As novas tentativas estão desativadas, e cada requisição HTTP tem um tempo limite de conexão de 10 segundos e um tempo limite de transferência de 60 segundos. Esses limites são por requisição, não um prazo para todo o upload multipart. Uma conexão lenta pode falhar mesmo com um arquivo pequeno. Este exemplo aborta uploads multipart que falham em vez de retomá-los; corrija o problema e execute novamente com o mesmo arquivo de origem local. Se a própria operação de abortar falhar, stderr incluirá o ID do upload para que o administrador do seu bucket possa removê-lo. As partes enviadas geram cobranças de armazenamento até que o upload seja concluído ou abortado, conforme explica a visão geral de uploads multipart da AWS.
A perda de uma resposta de conclusão pode deixar o resultado incerto: o S3 pode ter efetivado o armazenamento do objeto mesmo que o cliente informe uma falha. Inspecione a chave exata e os bytes antes de tentar novamente. Um upload multipart inacabado não é um objeto concluído.
Boas práticas de segurança
Conceda à identidade de exportação s3:PutObject para o prefixo que lhe pertence
e s3:AbortMultipartUpload para a limpeza após falhas. A identidade de verificação também
precisa de s3:GetObject para inspecionar o objeto e fazer seu download.
Restrinja essas permissões ao bucket e às chaves pretendidos; o script não precisa de permissão
para criar buckets nem alterar suas políticas. A AWS lista as permissões relevantes em sua
visão geral de uploads multipart.
O exemplo não envia nenhuma ACL e pressupõe um bucket privado. Ele usa SSE-S3 explicitamente; uma política de bucket que exige SSE-KMS requer uma alteração deliberada na configuração de criptografia e as permissões KMS correspondentes. Mantenha a verificação de certificados TLS ativada.
Solucionar problemas comuns
- AccessDenied: Confirme a conta selecionada, as permissões do prefixo e a política de criptografia do bucket.
- Erros de região ou endpoint: Defina
AWS_DEFAULT_REGIONde acordo com a região do bucket. - Arquivo ausente: Passe um caminho local com permissão de leitura. Coloque nomes de arquivos
com espaços entre aspas; no shell, adicione
./antes de um nome de arquivo que começa com hífen. - Falha no upload multipart: Verifique em stderr se houve uma falha ao abortar. Não trate um upload multipart remanescente como um arquivo armazenado e verifique uma conclusão incerta antes de tentar novamente.
- Bytes diferentes: Confirme se a chave e o arquivo local são os mesmos usados no upload e verifique se outro processo de gravação substituiu o objeto.
Conclusão e próximos passos
Depois que o objeto privado passar na comparação após o download, use o mesmo tratamento de upload e de status de saída na sua tarefa PHP. Mantenha verificações independentes de bytes nos testes de integração e preserve os arquivos de origem locais até confirmar a exportação. Para retomar uma transferência multipart que falhou, comece pela documentação de recuperação do MultipartUploader da AWS e decida como sua aplicação manterá o estado do upload e limpará as partes abandonadas.
