PHP: получить расширение файла не работает при загрузке в S3

Я использую API Amazon S3 для загрузки файлов и меняю имя файла при каждой загрузке.

Так, например:

Dog.png> 3Sf5f.png

Теперь я получил случайную часть, работающую так:

function rand_string( $length ) {
            $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";  

            $size = strlen( $chars );
            for( $i = 0; $i < $length; $i++ ) {
                $str .= $chars[ rand( 0, $size - 1 ) ];
            }

            return $str;
        }   

Поэтому я установил random_string для параметра name следующим образом:

$params->key = rand_string(5);

Теперь моя проблема в том, что это не будет показывать никаких расширений. Таким образом, файл будет загружен как 3Sf5f вместо 3Sf5f.png,

Переменная $filename дает мне полное имя файла с его расширением.

Если я использую $params->key = rand_string(5).'${filename}'; Я получил:

3Sf5fDog.png

Поэтому я попытался получить расширение $filename и применить его. Я перепробовал более 30 методов без какого-либо положительного.

Например, я пробовал $path_info(), я пробовал substr(strrchr($file_name,'.'),1); еще много. Все они дают мне либо 3Sf5fDog.png или просто 3Sf5f,

Пример того, что я попробовал:

// As @jcinacio pointed out. Change this to: 
//
//   $file_name = "${filename}";
//
$file_name = '${filename}'  // Is is wrong, since '..' does not evaluate 

$params->key = rand_string(5).$file_name;
=
3Sf5fDog.png

,

$file_name = substr(strrchr('${filename}', '.'), 1);

$params->key = rand_string(5).$file_name;
=
3Sf5f

,

$filename = "example.png"   // If I declare my own the filename it works.
$file_name = substr(strrchr('${filename}', '.'), 1);

$params->key = rand_string(5).$file_name;
=
3Sf5f.png

Весь файл класса: http://pastebin.com/QAwJphmW (других файлов для всего сценария нет).

Что я делаю не так? Это действительно расстраивает.

12 ответов

Переменная $filename (и, следовательно, "${filename}") НЕ находится в области видимости в строке 1053 вашего кода (нумерация строк на основе необработанной вставки из pastebin).

Таким образом, независимо от того, что вы делаете, вы никогда не найдете расширение переменной, которая не существует.


И я наконец-то понял, что ты делаешь. Я предполагаю, что это расширение PHP: переименовать файл перед загрузкой

Простой ответ: вы не можете сделать это так, как представляете. Почему - $ filename не анализируется во время создания URL, но переменная передается в Amazon S3 и обрабатывается там.

Решение

Таким образом, единственная опция, о которой я могу подумать, - это использовать параметр successRedirect, чтобы указывать на другой URL. Этот URL получит "корзину" и "ключ" в качестве параметров запроса от Amazon ( http://doc.s3.amazonaws.com/proposals/post.html). Укажите это на PHP-скрипт, который переименовывает файл в Amazon S3 (копирование + удаление), а затем перенаправляет пользователя на другой экран успеха.

Так,

в вашем коде строка 34,

  1. добавьте полный URL-адрес в новый файл сценария php, который вы собираетесь написать.
  2. скрипт php получит ведро и ключ
  3. Создайте новое имя файла из "ключа"
  4. используйте функцию "публичная статическая функция copyObject($srcBucket, $srcUri, $bucket, $uri)", чтобы скопировать загруженный файл с новым именем
  5. затем удалите оригинал (используя deleteObject($bucket, $uri))
  6. затем перенаправить пользователя туда, куда вы хотите отправить его

Это будет делать именно то, что вы хотите.


В ответ на ваши комментарии "Это единственный способ - как насчет затрат, которые Amazon взимает за запрос?"

Удалить запросы бесплатно. Никаких затрат на передачу данных при перемещении в одном сегменте (или даже в одном регионе). Таким образом, это решение (которое является единственным способом без перехода на промежуточный сервер, переименования и загрузки) удваивает стоимость загрузки с 1с на 1000 загрузок до 2с на 1000 загрузок. Мне понадобилось 10 минут @ 200 долларов в час, чтобы выяснить это и ответить = 33 доллара = 1 666 666 загрузок! Затраты немного бледнеют, когда вы делаете математику:)

Сравните с другим решением: сделайте сообщение на веб-сервере, переименуйте файл и затем загрузите с веб-сервера: вы перемещаете всю полосу пропускания из клиники прямо на себя - дважды. И это также представляет риск и увеличивает возможные точки отказа.


В ответ на "Не работает. Если вы загружаете файл, то старый удаляется"

Я бы предположил, что это не проблема, поскольку вы загружаете файл, а затем переименовываете его в течение секунды или двух. Но если вы хотите гарантировать, что каждый файл будет загружен, тогда вам нужно сделать гораздо больше, чем просто создать случайное имя файла:

  1. есть ваше "последнее" ведро
  2. для каждой загрузки создайте временное ведро (это 1 с на 1000 ведер, если вы беспокоитесь о затратах)
  3. загрузить во временное ведро
  4. создать случайное имя, проверить, если не существует в конечном сегменте (это 1c на 1000 проверок)
  5. скопировать файл в окончательный вариант (с новым именем)
  6. удалить загруженный файл, а также ведро.
  7. периодически очищайте корзины, где загрузка файлов не была завершена.
$fileSplit = explode('.',$filename);
$extension = '.'.$fileSplit[count($fileSplit) - 1];

Эта функция explode() делит имя файла на массив с точками в качестве разделителей, а затем захватывает последний фрагмент массива (в случае, если имя файла foo.bar.jpg) и ставит точку перед ним. Это должно дать вам желаемое расширение для добавления его в rand_string(5).

$params->key = rand_string(5).$extension;

Если вы загружаете изображения попробуйте это

$dim = getimagesize($file);
$type = $dim[2];
if( $type == IMAGETYPE_JPEG ) $ext=".jpg"; 
if( $type == IMAGETYPE_GIF ) $ext=".gif"; 
if( $type == IMAGETYPE_PNG ) $ext=".png";

$params->key = rand_string(5).$ext;

Я думаю, что что-то простое, как показано ниже, должно работать, чтобы извлечь расширение файла из имени файла:

function getFileExtension($fileName)
{
    $ext = '';
    if(strpos($fileName, ".") !== false)
    {
        $ext = end(explode(".", $fileName));
    }
    return $ext;
}

Что произойдет, если вы:

$filename = "${filename}";
echo $filename;
die();

Вы получаете что-то вроде "Dog.png"? Если вы не знаете, что-то не так в том, как вы получаете имя файла. Если вы получите что-то вроде "Dog.png", вот что я использую, чтобы получить расширение файла.

$pieces = explode('.',trim($filename));
$extension = end($pieces);

Тогда вы должны быть в состоянии сделать это:

$params->key = rand_string(5).'.'.$extension;

Я использую это на своих сайтах (и отлично работает в течение многих лет):

$file = 'yourOriginalfile.png';

//get the file extension
$fileExt = substr(strrchr($file, '.'), 1);

//create a random name and add the original extension
$fileUniqueName = md5(uniqid(mktime())) . '.' . $fileExt;

rename($file,$fileUniqueName); 

Ваша функция генерирует слишком короткие имена файлов (5 символов), таким образом создаются более длинные имена файлов, избегая столкновения имен файлов.

пример вывода: aff5a25e84311485d4eedea7e5f24a4f.png

Хорошо, вот еще одна попытка, которую я использовал, когда у меня были проблемы с получением расширения на стороне сервера. Я использовал javascript, чтобы извлечь расширение файла и отправить его по почте.

<script type="text/javascript" >
function fileinfo() {
var file = document.form.file.value;
document.getElementById("ext").value = file.split('.').pop();
document.myform.submit();   
}
</script>
<form name="myform" enctype="multipart/form-data" onsubmit="fileinfo();">
<input type="file" name="file">
<input type="hidden" name="ext">
//rest of the form
</form>

в следующем php-файле вы можете напрямую использовать $_POST['ext'] в качестве расширения. надеюсь, что это помогло. дайте мне знать, если у вас есть какие-либо проблемы в реализации этого

Похоже, что на самом деле происходит не полное создание имени файла прямо сейчас, вы фактически пропускаете очень маленькую "программу" через интерфейс, чтобы потом можно было создать имя файла позже (когда переменная $ filename существует и находится в области видимости.). Другая сторона интерфейса в конечном итоге выполняет ту "программу", которую вы передаете, которая производит измененное имя файла. (Конечно, передача "программы" во что-то другое для последующего выполнения не всегда облегчает отладку:-)

(Конечно, вам решать, хотите ли вы "сделать это" или "сделать это по-другому". "Разные способы" обычно включают переименование или копирование файла самостоятельно, прежде чем вы даже попытаетесь вызвать интерфейс загрузки, и описаны в других ответах.)

Если вы решите, что хотите "заставить его работать", тогда весь параметр имени файла должен быть программой, а не только его частью. Эта несколько необычная функциональность обычно включает в себя заключение всей строки в одинарные кавычки. (Вам также нужно что-то сделать с существующими одинарными кавычками внутри строки, чтобы они не заканчивали строку в кавычках слишком рано. Один из способов - заключить каждый из них в обратный слеш. Другой способ, который может выглядеть чище и обычно работает, состоит в том, чтобы замените их двойными кавычками.) Другими словами, я считаю, что приведенный ниже код будет работать для вас (у меня нет подходящей среды для его тестирования, поэтому я не уверен).

$file_extension = 'substr(strrchr(${filename}, "."), 1)';

$params->key = rand_string(5).$file_extension;

(Как только вы заработаете, вы, возможно, захотите пересмотреть свою схему именования. Возможно, имя должно быть немного длиннее. Или, возможно, оно должно включать некоторую идентифицируемую информацию {например, сегодняшнюю дату или оригинальное имя файла}. Вы можете ударил что-то вроде $file_base.rand_string(7).$file_extension.

Сначала нужно выяснить, что является исходным расширением, а не переименовывать весь файл. Так что сохраните расширение и переименуйте имя файла.

Предполагая, что у вас есть имя изображения в $image_name:

$image_name = "image.png";
$random_string = "random";

list($filename,$fileext) = explode(".",$image_name);
$new_name = $random_string.'.'.$fileext;
rename($image_name,$new_name);  

Как насчет этого?

$temp = rand_string(5).'${filename}';         //should be 3Sf5fDog.png
$ext = pathinfo($temp, PATHINFO_EXTENSION); //should be .png
$temp2 = rand_string(5) . $ext;             //should be 4D47a.png

Простое решение переименовать файл и вычислить расширение:

$fileName = 'myRandomFile.jpg';

// separate the '.'-separated parts of the file name
$parts = explode( '.', $fileName );

// Solution will not work, if no extension is present
assert( 1 < count( $parts ) );

// keep the extension and drop the last part
$extension = $parts[ count( $parts ) - 1 ];
unset( $parts[ count( $parts ) - 1 ] );

// finally, form the new file name
$newFileName = md5( 'someSeedHere' + implode( '.', $parts )) . '.' . $extension;

echo $extension             // outputs jpg
   . ' - ' 
   . $newFileName           // outputs cfcd208495d565ef66e7dff9f98764da.jpg
   ;

Обратите внимание, что md5() всегда имеет длину 32 байта и не является уникальной в отношении вычисленного значения. Для многих практических случаев это достаточно уникально.

добавление

Кроме того, вы можете использовать это решение для отслеживания изменений переменных:

abstract class CSTReportDelegate {

    abstract public function emitVariableChange( $variableName, $oldValue, $newValue );
    abstract public function emitVariableSetNew( $variableName, $newValue );

}

class CSTSimpleReportDelegate extends CSTReportDelegate {

    public function emitVariableChange( $variableName, $oldValue, $newValue ) {
        echo '<br />[global/change] '. $variableName . ' : ' . print_r( $oldValue, true ) . ' &rarr; ' . print_r( $newValue, true );
    }

    public function emitVariableSetNew( $variableName, $newValue ) {
        echo '<br />[global/init] '. $variableName . '   &rarr; ' . print_r( $newValue, TRUE );
    }

}


class CSysTracer {

    static protected 
        $reportDelegate;

    static private 
        $globalState = array();

    static private  
        $traceableGlobals = array();

    static private 
        $globalTraceEnabled = FALSE;

    const 
        DEFAULT_TICK_AMOUNT = 1;

    static public 
    function setReportDelegate( CSTReportDelegate $aDelegate ) {
        self::$reportDelegate = $aDelegate;
    }


    static public 
    function start( $tickAmount = self::DEFAULT_TICK_AMOUNT ) {

        register_tick_function ( array( 'CSysTracer', 'handleTick' ) );

    }


    static public 
    function stop() {

        unregister_tick_function( array( 'CSysTracer', 'handleTick' ) );

    }

    static public 
    function evalAndTrace( $someStatement ) {

        declare( ticks = 1 ); {
            self::start();
            eval( $someStatement );
            self::stop();
        }
    }

    static public 
    function addTraceableGlobal( $varName ) {

        if ( is_array( $varName )) {
            foreach( $varName as $singleName ) {
                self::addTraceableGlobal( $singleName ); 
            }
            return;
        }

        self::$traceableGlobals[ $varName ] = $varName;

    }

    static public 
    function removeTraceableGlobal( $varName ) {
        unset( self::$traceableGlobals[ $varName ] );   
    }

    /**
     * Main function called at each tick. Calls those functions, which
     * really perform the checks.
     * 
     */
    static public 
    function handleTick( ) {

        if ( TRUE === self::$globalTraceEnabled ) { 
            self::traceGlobalVariable();
        }

    }

    static public 
    function enableGlobalsTrace() {
        self::$globalTraceEnabled = TRUE;   
    }


    static public 
    function disableGlobalsTrace() {
        self::$globalTraceEnabled = FALSE;  
    }

    static public 
    function traceGlobalVariable( ) {

        foreach( self::$traceableGlobals as $aVarname ) {

            if ( ! isset( $GLOBALS[ $aVarname ] )) {
                continue;
            }

            if ( ! isset( self::$globalState[ $aVarname ] ) ) {

                self::$reportDelegate->emitVariableSetNew( $aVarname, $GLOBALS[ $aVarname ] );
                self::$globalState[ $aVarname ] = $GLOBALS[ $aVarname ];
                continue;
            }

           if ( self::$globalState[ $aVarname ] !== $GLOBALS[ $aVarname ]) {

             self::$reportDelegate->emitVariableChange( $aVarname, self::$globalState[ $aVarname ], $GLOBALS[ $aVarname ] );

           }

           self::$globalState[ $aVarname ] = $GLOBALS[ $aVarname ];

        }

    }

}

Пример использования:

ini_set("display_errors", TRUE);
error_reporting(E_ALL);

require_once( dirname( __FILE__ ) . '/CStatementTracer.inc.php' );

/* Ticks make it easy to have a function called for every line of PHP
 *  code. We can use this to track the state of a variable throughout
 * the execution of a script.
 */



CSysTracer::addTraceableGlobal( array( 'foo', 'bar' ));

CSysTracer::setReportDelegate( new CSTSimpleReportDelegate() ); 
CSysTracer::enableGlobalsTrace();

CSysTracer::start(); 
declare( ticks = 1 );

   //
   // At this point, tracing is enabled. 
   // Add your code or call your functions/methods here
   //

CSysTracer::stop();

Непроверенный, но достаточно простой для работы:

$ext = pathinfo($filename, PATHINFO_EXTENSION);

вернет часть расширения (без '.')

Другие вопросы по тегам