Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sftp Bundle Laravel Package

sf2h/sftp-bundle

Symfony bundle that adds SFTP integration, with configurable connections and services for secure file transfers, uploads, downloads, and remote file management in PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the reality check. sf2h/sftp-bundle is a Symfony bundle, not a Laravel package. There is no ServiceProvider, no auto-discovery, no config/ publish, no facade. composer require sf2h/sftp-bundle in a Laravel app gets you a directory of classes whose services are wired through Symfony's Dependency Injection YAML — machinery Laravel doesn't run. So you have two honest paths:

  1. Recommended for Laravel: skip the bundle and use league/flysystem-sftp (wraps phpseclib 3) behind Laravel's Storage facade. You get Storage::disk('partner')->put(...) alongside your local/S3 disks, and the same Filesystem API you already know.
  2. Use the bundle anyway if you're maintaining a Symfony 3/4 app, or you specifically want its tiny phpseclib wrapper and are willing to instantiate it by hand.

Minimal steps if you proceed (Symfony):

composer require sf2h/sftp-bundle
# config/packages/sf2h_sftp.yaml
sf2h_sftp:
    hosts:
        partner_a:
            host: sftp.partner-a.com
            port: 22
            username: acme
            password: "%env(PARTNER_A_PASS)%"
            timeout: 15
        partner_b:
            username: acme
            private_key: "%kernel.project_dir%/var/keys/partner_b.pem"
            passphrase: "%env(PARTNER_B_KEY_PASSPHRASE)%"

Where to look first: DependencyInjection/Configuration.php — this is the authoritative list of host keys (host, port, username, password, private_key, passphrase, timeout, dir/root). Then Resources/config/services.yml to find the service id and the class that exposes getConnection($name); that method returns a raw phpseclib\Net\SFTP object, and that object is where all real capability lives (put, get, nlist, rename, chmod, mkdir).

First use case: the classic one — a nightly job pushing a CSV/XML export to a partner's drop directory, and pulling their acknowledgement file back. Thin wrappers like this are perfectly adequate for "connect, put a file, disconnect."

Implementation Patterns

Config-driven multi-host registry. The bundle's one genuine idea is named connections. Keep that shape in Laravel even if you drop the bundle:

// config/sftp.php
return ['hosts' => [
    'partner_a' => ['host' => env('PARTNER_A_HOST'), 'username' => env('PARTNER_A_USER'), ...],
]];

Resolve a client by name in a singleton, one connection per alias, reused within a job — SFTP handshakes are expensive (RSA key exchange per connect).

Wrap it behind your own interface. Never let the bundle's class leak into your domain code. Define App\Contracts\SftpClient with upload(string $local, string $remote), download(string $remote, string $local), listDir(string $path), delete(string $path), exists(string $path). One implementation delegates to the bundle's getConnection(), another to Flysystem. That swap is the whole point: with 3 stars, 0 dependents, and a 2020 release, expect to replace this package.

Always queue transfers. SFTP is slow and flaky; never in a request cycle.

class PushExportToPartner implements ShouldQueue
{
    public int $tries = 4;
    public array $backoff = [30, 120, 300];
    public int $timeout = 300;
}

Write atomically. Upload to invoice-123.csv.part, then rename() to invoice-123.csv. SFTP rename is atomic on the server; partners watching the directory never pick up a half-written file. This is the single highest-value pattern for EDI-style integrations.

Stream, don't buffer. Use put($remote, $localPath) / get($remote, $localPath) so phpseclib reads from disk. file_get_contents() on a 2 GB export will blow your memory limit, and a queued worker dying mid-transfer is a pain to diagnose.

Log the useful things: host alias, remote path, bytes, duration, attempt number. Never the password or passphrase. A php artisan sftp:ping partner_a command on a schedule catches rotated keys before the 2 a.m. job fails.

Test at the boundary. A FakeSftpClient backed by Storage::disk('local') gives you full coverage without a network; keep one manual smoke test against a real server.

Gotchas and Tips

  • No Laravel integration, and no amount of composer require fixes that. You cannot app(Sftp::class) — the service definitions live in Symfony YAML and are consumed by Symfony\Component\DependencyInjection. Manual instantiation is possible but you're then reimplementing the bundle's only feature.
  • It's stale. Last release 2020-12-22, 3 stars, 0 dependents. Check composer.json for the phpseclib/phpseclib constraint: bundles of this vintage pin ~2.0, whose classes are phpseclib\Net\SFTP. phpseclib 3 uses phpseclib3\Net\SFTP and has a different API. On PHP 8.1+/Laravel 10/11 you will likely hit unsatisfiable constraints. Pin the exact version, commit composer.lock, and assume no upstream fixes.
  • Key format. phpseclib 2 chokes on modern OpenSSH defaults (ed25519, and sometimes the openssh-key-v1 container). Convert first: ssh-keygen -p -m PEM -f key.pem. chmod 600 the file, and remember passphrase protects the key, not the account.
  • Host key verification. Thin wrappers of this era frequently ignore server_public_key, so you have no protection against MITM. If the bundle exposes the fingerprint, pin it. If not, treat the network path as trusted-only.
  • Timeouts hang the worker. Set an explicit timeout in config and wrap every call in try/catch. A dead socket can otherwise sit there until your queue worker's timeout kills it — and you lose the exception.
  • Absolute paths lie. SFTP users are usually chrooted; /upload may really be /home/acme/upload. Run pwd() / realpath('.') once and log it rather than guessing leading-slash semantics.
  • Permissions and umask. Uploaded files inherit the client umask; partners getting Permission denied usually need a chmod($remote, 0644) after put.
  • mkdir isn't recursive by default; create each parent explicitly before uploading into a dated folder.
  • Retries can duplicate. Make remote filenames deterministic (report-2024-06-01.csv) so a retry overwrites instead of creating report (1).csv. Use the .part + rename pattern to make this safe.
  • Debugging: reproduce outside PHP first — sftp -v user@host separates DNS/firewall/auth from library problems. Permission denied (publickey) is almost always key format or file mode; Connection closed by remote is usually an IP allowlist or too many auth attempts. For in-process detail, wire a PSR-3 logger into phpseclib if the bundle's version exposes it.
  • Extension points: the bundle's intended ones are Symfony compiler passes and service decoration. In Laravel you get none of them. Your extension point is your own adapter — wrap, don't subclass.
  • Migration path, plan it now: league/flysystem-sftp + a Laravel filesystem disk gives you multi-host config, Storage facade ergonomics, maintained phpseclib 3, and no Symfony baggage. For almost every Laravel use case, that's the answer, and this bundle is only worth it if you're already inside Symfony.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums