Flysystem FTP

FTP adapter for Flysystem, giving PHP apps a unified filesystem API over standard FTP servers.

Library
Composer
v3.31.0
29stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
25/100Needs Attention
Development Activity4
Maintenance0
Community24
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
71/100Good
Architecture80
Code Quality78
Innovation72
Learning Curve55

league/flysystem-ftp is the official FTP adapter for League\Flysystem, the PHP filesystem abstraction layer used across Laravel, Symfony, and countless standalone apps. It implements Flysystem’s FilesystemAdapter contract on top of PHP’s built-in ext-ftp extension, so any code written against Flysystem’s Filesystem facade can talk to a plain FTP or FTP-over-SSL server without ever calling ftp_* functions directly.

The adapter handles the parts of FTP that make it awkward to work with by hand: connection bootstrapping (host/port/timeout/SSL), authentication, UTF-8 and passive-mode negotiation, root-path prefixing, stale-connection detection and reconnection, and translating both MLSD- and raw-LIST-style directory listings into Flysystem’s typed FileAttributes/DirectoryAttributes objects. Every cross-cutting concern - the connection provider, connectivity checker, visibility converter, and MIME type detector - is a swappable interface, so it can be adapted to unusual FTP servers or tested without a live connection.

What You Get

  • A full implementation of Flysystem’s FilesystemAdapter interface (write, writeStream, read, readStream, delete, deleteDirectory, createDirectory, move, copy, listContents, visibility, mimeType, lastModified, fileSize) backed by PHP’s ext-ftp functions
  • FtpConnectionOptions and FtpConnectionOptions::fromArray() for configuring host, port, root, credentials, SSL, timeout, UTF-8, passive mode, transfer mode, and system type - either fluently or from a config array (the pattern Laravel’s filesystems.php disk config uses)
  • Automatic stale-connection detection and reconnection via a pluggable ConnectivityChecker (NoopCommandConnectivityChecker or RawListFtpConnectivityChecker), so long-running processes don’t die on a dropped FTP session
  • Unix-style visibility mapping (public/private) translated into FTP permission (chmod-equivalent) calls through a swappable VisibilityConverter
  • Typed, named exceptions for every failure mode (UnableToConnectToFtpHost, UnableToAuthenticate, UnableToEnableUtf8Mode, UnableToMakeConnectionPassive, UnableToResolveConnectionRoot) instead of generic errors, each built via a descriptive static factory method

Common Use Cases

  • Deploying to or syncing files with legacy shared-hosting environments where FTP is the only available transfer protocol
  • Letting a Laravel or Symfony app write generated files, backups, or user uploads to a remote FTP host through the same Storage/Filesystem API used for local and cloud disks
  • Building multi-backend file-storage abstractions where FTP is one of several interchangeable adapters (local, S3, SFTP, FTP) selected by configuration rather than code
  • Migrating or archiving content to third-party FTP endpoints (print vendors, EDI partners, legacy CMS hosts) that only expose FTP access

Under The Hood

Architecture FtpAdapter implements League\Flysystem\FilesystemAdapter, lazily establishing a raw FTP resource via a pluggable ConnectionProvider (default FtpConnectionProvider), which wraps ftp_connect/ftp_ssl_connect, login, UTF-8 negotiation, and passive-mode setup in typed exceptions such as UnableToConnectToFtpHost and UnableToAuthenticate. The adapter resolves and caches a root directory once per connection (resolveConnectionRoot) behind a PathPrefixer, and re-issues ftp_chdir on every connection() call to guard against relative-path drift. Connection staleness is checked through a pluggable ConnectivityChecker (NoopCommandConnectivityChecker by default, or RawListFtpConnectivityChecker), with a goto-based reconnect loop inside connection(). Reads and writes stream through PHP temp streams via ftp_fget/ftp_fput rather than buffering whole files, and listContents() parses either MLSD or raw LIST output depending on the detected server type before emitting Flysystem’s FileAttributes/DirectoryAttributes value objects.

Tech Stack Pure PHP 8.0.2+ requiring the ext-ftp extension for the raw ftp_* function surface, with no third-party FTP client dependency - all wire communication goes through PHP’s built-in FTP support. It depends on league/flysystem ^3.0 for the FilesystemAdapter contract, PathPrefixer, Config, and the Unable* exception types, and league/mime-type-detection ^1.0 for finfo-based MIME sniffing via FinfoMimeTypeDetector. This repository is a Composer “sub-split” auto-published from the thephpleague/flysystem monorepo, so its psr-4 autoload maps League\Flysystem\Ftp\ directly to the repo root rather than a src/ directory.

Code Quality Tests use attribute-driven PHPUnit (#[Test]) across FtpAdapterTest, the shared FtpAdapterTestCase (also exercised against a real vsftpd server in FtpdAdapterTest), FtpConnectionProviderTest, RawListFtpConnectivityCheckerTest, and NoopCommandConnectivityCheckerTest, covering both adapter behavior and connection-provider auth/UTF-8/passive-mode branches in isolation. Failure modes use named, factory-constructed exception classes rather than generic throws, and the code consistently uses PHP 8 constructor property promotion and declare(strict_types=1). Many FTP calls are intentionally error-suppressed (@ftp_*) with error_get_last() recovered for exception messages - a defensible pattern given how noisily the FTP extension emits warnings, but one that requires care when extending the adapter.

API Design Because FtpAdapter conforms to Flysystem’s own FilesystemAdapter interface, the day-to-day developer-facing API is Flysystem’s Filesystem::write()/read()/listContents() rather than anything package-specific - this library’s entire job is to be constructed once (new FtpAdapter(new FtpConnectionOptions(…))) and handed to new Filesystem($adapter). FtpConnectionOptions::fromArray() gives a convenient associative-array constructor path that matches how frameworks like Laravel define disk configuration, and every cross-cutting concern (ConnectionProvider, ConnectivityChecker, VisibilityConverter, MimeTypeDetector) is constructor-injectable, so custom servers or unit tests can swap in fakes without subclassing the adapter.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search