File Transfer¶
A transport-abstraction layer for remote file operations. The FileTransport port covers
sending, retrieving, and listing files on a remote host. FileTransferService acts as a
named registry when an application needs to talk to multiple remote endpoints.
Application\FileTransfer
├── FileTransferService — Named registry of FileTransport instances
├── Transport\
│ └── FileTransport (interface) — sendFile(), retrieveFileContents(),
│ retrieveFileResource(), readDirectory()
├── Resource\
│ ├── Resource — Immutable file/directory descriptor
│ └── ResourceType (enum: string) — FILE, DIR, LINK, FIFO, CHAR, BLOCK, SOCKET, UNKNOWN
└── Exception\
└── FileTransferException — extends SystemException
Adapter\FileTransfer
├── Sftp\
│ └── SftpFileTransport — FileTransport → phpseclib3 SFTP
├── Ftp\
│ └── FtpFileTransport — FileTransport → PHP FTP extension
├── Logging\
│ └── LoggingFileTransport — Decorator: logs operations then delegates
└── Null\
└── NullFileTransport — No-op (tests / dev)
Table of Contents¶
- FileTransport
- Resource
- ResourceType
- FileTransferService
- Adapters
- Symfony Configuration
- Usage Examples
FileTransport¶
Fight\Common\Application\FileTransfer\Transport\FileTransport
interface FileTransport
{
/** @throws FileTransferException */
public function sendFile(string $path, mixed $contents): void;
/** @throws FileTransferException */
public function retrieveFileContents(string $path): string;
/** @throws FileTransferException */
public function retrieveFileResource(string $path): mixed; // returns resource
/**
* @return iterable<Resource>
* @throws FileTransferException
*/
public function readDirectory(string $directory): iterable;
}
$contents in sendFile() accepts a string or a stream resource. retrieveFileResource()
returns an open stream resource positioned at the start of the file.
Resource¶
Fight\Common\Application\FileTransfer\Resource\Resource
An immutable value object describing a single remote entry returned by readDirectory().
final readonly class Resource implements Stringable
{
public function __construct(
string $path,
int $size,
int $userId,
int $groupId,
int $mode,
DateTimeImmutable $accessTime,
DateTimeImmutable $modifyTime,
ResourceType $type
) {}
}
Accessors¶
| Method | Returns | Description |
|---|---|---|
path() |
string |
Full remote path (trimmed) |
size() |
int |
Size in bytes |
userId() |
int |
Owning user ID |
groupId() |
int |
Owning group ID |
mode() |
int |
Octal permission mode as integer |
permissions() |
string |
4-character octal string e.g. '0644' |
accessTime() |
DateTimeImmutable |
Last access time |
modifyTime() |
DateTimeImmutable |
Last modification time |
type() |
ResourceType |
Entry type |
__toString() |
string |
Alias for path() |
ResourceType¶
Fight\Common\Application\FileTransfer\Resource\ResourceType
enum ResourceType: string
{
case FILE = 'file';
case DIR = 'dir';
case LINK = 'link';
case FIFO = 'fifo';
case CHAR = 'char';
case BLOCK = 'block';
case SOCKET = 'socket';
case UNKNOWN = 'unknown';
}
FileTransferService¶
Fight\Common\Application\FileTransfer\FileTransferService
A named registry for multiple FileTransport instances. Useful when an application
connects to more than one remote host or protocol simultaneously.
$service = new FileTransferService();
$service->addTransport('sftp-primary', $sftpTransport);
$service->addTransport('ftp-backup', $ftpTransport);
$transport = $service->getTransport('sftp-primary');
$transport->sendFile('/uploads/report.csv', $csvData);
Throws FileTransferException if a duplicate key is registered. Throws KeyException if
the requested key is not found.
Adapters¶
SftpFileTransport¶
Fight\Common\Adapter\FileTransfer\Sftp\SftpFileTransport
Wraps a phpseclib3\Net\SFTP connection. The SFTP object is constructed and authenticated
externally then injected:
use phpseclib3\Net\SFTP;
use Fight\Common\Adapter\FileTransfer\Sftp\SftpFileTransport;
$sftp = new SFTP('sftp.example.com');
$sftp->login('user', 'password');
$transport = new SftpFileTransport($sftp);
readDirectory() uses SFTP::rawlist() and maps phpseclib's numeric type codes to
ResourceType values (1 → FILE, 2 → DIR, 3 → LINK, anything else → UNKNOWN).
FtpFileTransport¶
Fight\Common\Adapter\FileTransfer\Ftp\FtpFileTransport
Uses PHP's built-in FTP extension. Manages the connection lifecycle internally — connects on first use and disconnects after each operation.
use Fight\Common\Adapter\FileTransfer\Ftp\FtpFileTransport;
$transport = new FtpFileTransport(
host: 'ftp.example.com',
port: 21,
username: 'ftpuser',
password: 'secret',
ssl: true, // use ftp_ssl_connect (default false)
timeout: 90, // seconds (default 90)
passive: true // PASV mode (default false)
);
sendFile() creates missing parent directories automatically. readDirectory() uses
ftp_mlsd() for structured directory listings. Requires the PHP ftp extension and
libssl for SSL connections.
LoggingFileTransport¶
Fight\Common\Adapter\FileTransfer\Logging\LoggingFileTransport
Decorator that logs the path of each operation via PSR-3 before delegating:
$transport = new LoggingFileTransport(
new SftpFileTransport($sftp),
$logger,
LogLevel::INFO // default DEBUG
);
Log channels:
- sendFile → [FileTransfer]: Sending file with path
- retrieveFileContents / retrieveFileResource → [FileTransfer]: Retrieving file contents/resource with path
- readDirectory → [FileTransfer]: Reading directory with path
NullFileTransport¶
Fight\Common\Adapter\FileTransfer\Null\NullFileTransport
Silent no-op adapter. sendFile() does nothing. retrieveFileContents() returns ''.
retrieveFileResource() returns an empty php://memory stream. readDirectory() returns
an empty array. Useful in tests and local development.
Symfony Configuration¶
# config/packages/common_file_transfer.yaml
services:
_defaults:
autowire: true
autoconfigure: true
# --- SFTP ---
phpseclib3\Net\SFTP:
factory: ['phpseclib3\Net\SFTP', 'new']
arguments: ['%env(SFTP_HOST)%']
calls:
- [login, ['%env(SFTP_USER)%', '%env(SFTP_PASSWORD)%']]
Fight\Common\Adapter\FileTransfer\Sftp\SftpFileTransport:
arguments:
- '@phpseclib3\Net\SFTP'
Fight\Common\Adapter\FileTransfer\Logging\LoggingFileTransport:
decorates: Fight\Common\Adapter\FileTransfer\Sftp\SftpFileTransport
arguments:
- '@.inner'
- '@logger'
- 'info'
# --- Registry ---
Fight\Common\Application\FileTransfer\FileTransferService:
calls:
- [addTransport, ['sftp', '@Fight\Common\Adapter\FileTransfer\Sftp\SftpFileTransport']]
# --- Interface alias ---
Fight\Common\Application\FileTransfer\Transport\FileTransport:
alias: Fight\Common\Adapter\FileTransfer\Sftp\SftpFileTransport
Usage Examples¶
Uploading a File¶
use Fight\Common\Application\FileTransfer\Transport\FileTransport;
class ReportExporter
{
public function __construct(private FileTransport $transport) {}
public function export(Report $report): void
{
$this->transport->sendFile(
'/exports/reports/'.$report->filename(),
$report->toCsv()
);
}
}
Downloading a File¶
$contents = $transport->retrieveFileContents('/reports/2026-06.csv');
// As a stream resource
$stream = $transport->retrieveFileResource('/reports/2026-06.csv');
Listing a Directory¶
foreach ($transport->readDirectory('/uploads') as $resource) {
if ($resource->type() === ResourceType::FILE) {
echo sprintf(
'%s %s %d bytes',
$resource->permissions(),
$resource->path(),
$resource->size()
);
}
}
Using Multiple Transports¶
$service = new FileTransferService();
$service->addTransport('sftp', $sftpTransport);
$service->addTransport('backup', $ftpTransport);
// Route by business logic
$service->getTransport($isPrimary ? 'sftp' : 'backup')
->sendFile('/data/'.$filename, $contents);