copy

Copies a file from a source path to a destination path, overwriting the destination if it exists.

Signature
copy(string $from, string $to, ?resource $context = null): bool
  • Added in PHP 4.0

Parameters

ParameterTypeDescription
$fromstringPath to the source file. Any readable stream wrapper the build supports may be used.
$tostringThe destination path. If it already exists it is overwritten without warning.
$contextoptional?resource= nullA stream context created with stream_context_create(), used for wrapper-specific options.

Return value

booltrue on success or false on failure.

Errors

Does not throw. It emits E_WARNING and returns false when the source cannot be read or the destination cannot be written.

copy() streams the source into the destination, so it uses constant memory no matter how large the file is. That makes it the right tool for duplicating a multi-gigabyte archive, where file_put_contents(file_get_contents(...)) would try to hold the whole thing in RAM.

When to reach for it

Snapshot a config or database file before rewriting it. Duplicate a template into a per-tenant directory. Keep the original in place after processing an upload, where rename() would move it away.

If the source and destination are on the same filesystem and you do not need the original, rename() is far cheaper — it is a metadata operation, not a byte-for-byte transfer.

What trips people up

It creates nothing but the destination file. A missing parent directory is a failure, not a prompt to create one, so pair it with mkdir($dir, 0775, true). Passing a directory as $to also fails; give the full target path including the file name.

The copy is not atomic. A reader watching the destination can see a half-written file. Write to a temporary name in the same directory and rename() it into place when you need readers to see all-or-nothing.

Permissions and ownership are not preserved — the new file gets default permissions.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Snapshot settings.ini to settings.ini.bak before an installer rewrites it
  • Duplicate a shared template document into a new per-customer folder
  • Keep an uploaded original alongside a resized derivative instead of moving it
  • Copy a large export to a mounted backup volume without loading it into memory

Frequently asked questions

Does copy overwrite an existing file?

Yes, silently and without a warning. Check file_exists() on the destination first if you need to protect it.

What is the difference between copy and rename?

copy() duplicates the bytes and leaves the source in place; rename() moves the file and, within one filesystem, only updates metadata so it is effectively instant. Use rename() when you do not need the original.

Why does copy return false when the source file clearly exists?

Most often the destination's parent directory does not exist or is not writable — copy() never creates directories. Check that dirname($to) exists and that the PHP process can write there.

More examples

Back up before overwriting

The backup is taken before the rewrite, so the two echoes show debug=0 for the live file and debug=1 for the snapshot. Naming the backup with a date suffix keeps successive runs from clobbering each other — plain .bak would be overwritten silently on the second run.

Example · php
<?php
$file = "settings.ini";
file_put_contents($file, "debug=1\n");

$backup = $file . "." . date("Ymd") . ".bak";
copy($file, $backup);

file_put_contents($file, "debug=0\n");

echo "current: ", file_get_contents($file);
echo "backup:  ", file_get_contents($backup);

unlink($file);
unlink($backup);

The destination directory must already exist

The first copy succeeds because backups/ was created first. The second returns false and would have emitted an E_WARNING — copy() never creates intermediate directories. In real code call mkdir(dirname($to), 0775, true) first, and check its return value too.

Example · php
<?php
file_put_contents("report.csv", "id,name\n1,ana\n");

mkdir("backups");
var_dump(copy("report.csv", "backups/report.csv"));
echo file_get_contents("backups/report.csv");

// No mkdir this time:
var_dump(@copy("report.csv", "missing-dir/report.csv"));

unlink("report.csv");
unlink("backups/report.csv");
rmdir("backups");

Atomic publish with a temp file and rename

copy() is not atomic, so a concurrent reader can observe a half-written destination. Writing to a temporary name in the same directory and then rename()-ing it swaps the file in one step. The output shows the new version live and the previous one preserved as .bak.

Example · php
<?php
$target = "published.json";
file_put_contents($target, '{"v":1}');

$tmp = $target . ".tmp";
copy($target, $target . ".bak");        // keep the old version
file_put_contents($tmp, '{"v":2}');
rename($tmp, $target);                  // readers never see a partial file

echo file_get_contents($target), "\n";
echo file_get_contents($target . ".bak"), "\n";

unlink($target);
unlink($target . ".bak");

Discussion

  • Be the first to comment on this lesson.