Google Drive API - Nahrávání souborů v PHP

(aktualizováno 31.1.2017) PHP, Databáze, Google

Jednoduchý návod, jak programově nahrávat libovolné soubory v PHP na Google Drive. Se zařazením do složek i mazání starých nepotřebných souborů. Hlavní cíl skriptu je ukládání záloh mimo server s webem.

Google Drive API - Nahrávání souborů v PHP

AKTUALIZACE 31.1.2017 - Jak je zmíněno v komentářích, API se mění. Článek jsem aktualizoval pro fungování s APIv3.
Pokud přecházíte z APIv2, prvně si přečtěte článek Přechod z GApi v2 na v3, případně Migration to Google Drive APIv3 na stránkách Google Developers.

V předchozích článcích jsem ukázal, jak lze vytvořit zálohu MySQL pomocí PHP skriptu. Sice si vytvořím zálohu, ale ta je stále uložena na stejném serveru a v případě výpadku jsem přišel pravděpodobně o obojí. Proto jsem se rozhodl napsat skript, který zálohu nahraje na Google Drive.

OAuth2 Service account

Stejně jako v minulém článku při stahování dat z Google Kalendáře, musíme vytvořit servisní účet, abychom mohli přistupovat k účtu bez autorizace. V Google Consoli vytvoříme nový účet, a postupujte podle návodu v minulém článku o Kalendáři.

Nezapomeňte stáhnout privátní klíč ve formátu JSON, v APIv2 to byl formát P12. Na závěr je nutné přidat v levém menu Library povolit u účtu Drive API. Po přidání může trvat až 10 minut, než se API stane aktivní a budete se moci připojit ze skriptu.

Sdílení složky a její ID pro service account

Service account ale nemá nic společného s našim účtem, proto je potřeba soubory a složky nasdílet. Servisní účet je emailová adresa ve tvaru user@ucet-xx.iam.gserviceaccount.com. Na Google Drive si vytvoříme složku a nasdílíme ji s tímto emailem.

V našem skriptu budeme potřebovat znát také ID složky pro nahrávání, kterou můžeme zjistit pomocí API nebo jednodušeji v URL. Stačí ale nasdílet pouze rodičovskou složku, a nově vytvořené vnořené složky se také nasdílí. Můžeme tedy zálohy různých webů ukládat zvlášť do složek.

ID složky pro nahrávání

Skript pro nahrávání

Skript jsem psal kvůli zálohám DB, které mohou mít desítky či stovky MB. Proto je ve skriptu využito dělené nahrávání. Velikost chunku si určíme a poté se do paměti načte pouze menší část. Pokud ale víte, že nikdy nebudete nahrávat velké soubory, využijte jednodušší skript níže.

Může se stát, že API bude vyhazovat výjimku o chybě SSL certifikátu, jak se zbavit této chyby je pospáno v článku Přechod z GApi v2 na v3.

Úpravy oproti APIv2

Hlavní změnou je využití autoloaderu od Composeru a nová inicializace třídy Google_Client více o tom také v článk o Přechodu z APIv2 na APIv3. U třídy pro práci se souborem se metoda změnila z setTitle na setName, rodičovská složka se nastavuje přímo pomocí ID složky, nikoli přes instanci Google_Service_Drive_ParentReference, soubor se vytváří pomocí metody create a ne insert. V metodě listFiles je potřeba pro získání více informací o souborech vyplnit pole fields a samotné získání souborů se provádí přes metodu getFiles, nikoli getItems.

// Využít autoloader od Composeru
// require_once 'Google/autoload.php';

// Soubor pro nahrání
$fileToUpload = "backup.zip";
// Název souboru na GDrive
$uploadedName = "backup - ".date("Y-m-d H:i:s").'.zip';
// Soubory starší 30 dnů se smažou z Drive
$olderThan = time() -  60 * 60 * 24 * 30; 
// Minimálně 20 souborů po smazání musí na Drive zůstat, i když jsou starší
$minFiles = 20;

// U APIv3 se nepoužívá - Emailová adresa service účtu
// $emailAddress = 'yyy@xxx.iam.gserviceaccount.com';
// Cesta k privátnímu klíči - APIv2 používalo klíč ve formátu P12
$keyFileLocation = 'aaa.json';
// ID složky, do které nahráváme zálohy, musí být sdílena se servisním účtem
$folderId = '0*************************g';
// Počet bytů po kolika soubor odesíláme
$chunkSizeBytes = 5 * 1024 * 1024; 

$client = new Google_Client();    
$client->setApplicationName("BackupDrive");

try{
    // ############################
    // Inicializace Google Clienta
    // ############################
    // Nový způsob - porovnání v článku Přechod z GApi v2 na v3
    $client->setAuthConfig($keyFileLocation);
    $client->useApplicationDefaultCredentials();
    $client->addScope([
        \Google_Service_Drive::DRIVE,
        \Google_Service_Drive::DRIVE_METADATA
    ]);
    $service = new \Google_Service_Drive($client);

    // ###################
    // Nahrávání souboru
    // ###################
    $file = new Google_Service_Drive_DriveFile();
    $file->setName($uploadedName); // V APIv2 setTitle
    $client->setDefer(true); // Neprovádíme operaci ihned

    // Přiřadíme rodiče k souboru, aby se zařadil do požadované složky
    // V APIv2 bylo nutné přiřazovat rodiče pomocí
    // instancí třídy Google_Service_Drive_ParentReference
    $file->setParents(array($folderId));

    // Vytvoříme soubor a nahrajeme
    // V APIv2 se používala metoda insert
    $createdFile = $service->files->create($file);
    $contentType = mime_content_type($fileToUpload);
    $media = new Google_Http_MediaFileUpload(
        $client,
        $createdFile,
        $contentType,
        null, // data
        true, // resumable
        $chunkSizeBytes
    );
    $media->setFileSize(filesize($fileToUpload));

    // Nahráváme postupně po menších částech
    $status = false;
    $result = false;
    $handle = fopen($fileToUpload, "rb");
    while (!$status && !feof($handle)) {
        $chunk = fread($handle, $chunkSizeBytes);
        $status = $media->nextChunk($chunk);
    }
    if($status != false) {
        $result = $status;
    }
    fclose($handle);
    $client->setDefer(false);
}catch(Exception $e){
    file_put_contents("error.log", date("Y-m-d H:i:s").": ".$e->getMessage()."\n\n", FILE_APPEND);
    die();
}

// #######################
// Mazání starých souborů
// #######################
$newer = 0;
$toDelete = array();
// Pouze soubory v požadované složce
$files = array();
try{
    $files = $service->files->listFiles(array(
        'q' => "'{$folderId}' in parents",
        // Nutné v APIv3 specifikovat, jaké informace o souboru chceme stáhnout
        'fields' => 'files(id, name, createdTime)'
    ))->getFiles(); // V APIv2 metoda getItems()
}catch(Exception $e){
    file_put_contents("error.log", date("Y-m-d H:i:s").": ".$e->getMessage()."\n\n", FILE_APPEND);
    die();
}
foreach ($files as $fileObject) {
    $creationTime = strtotime($fileObject->createdDate);
    if ($creationTime <= $olderThan) {
        $toDelete[] = $fileObject->id;
    }else{
        $newer++;
    }
}
if ($newer > $minFiles) {
    foreach ($toDelete as $delId) {
        try{
            $service->files->delete($delId);
        }catch(Exception $e){
            file_put_contents("error.log", date("Y-m-d H:i:s").": ".$e->getMessage()."\n\n", FILE_APPEND);
        }
    }
}

Skript pro nahrávání menších souborů

V tomto jednodušším skriptu se celý soubor nahraje do paměti a poté se odesílá. Pokud víme, že nikdy nebudeme nahrávat extrémně velké soubory, můžeme si skript zjednodušit. Nahraďte celou část Nahrávání souboru až po konec try bloku.

$file = new Google_Service_Drive_DriveFile();
$file->setName($uploadedName); // V APIv2 setTitle()
$mimeType = mime_content_type($backup_filename);
$file->setMimeType($mimeType);

// Přiřadíme rodiče k souboru, aby se zařadil do požadované složky
// V APIv2 bylo nutné přiřazovat rodiče pomocí
// instancí třídy Google_Service_Drive_ParentReference
$file->setParents(array($folderId));

// V APIv2 se volala metoda insert
$createdFile = $service->files->create($file, array(
    'data' => file_get_contents($backup_filename),
    'mimeType' => $mimeType,
    'uploadType' => "multipart"
));

Komplikace a nejasnosti

  • Pokud soubor nezařadíme do složky, kterou sdílíme se svým účtem, k souborům se nedostaneme jinak než přes API
  • Soubory se nezapočítávají do úložiště našeho účtu ale účtu Service account. Servisní účet má také 15GB a využití případně maximální velikost lze zjistit pomocí příkazu:
// APIv2
$info = $service->about->get();
echo $info->quotaBytesTotal;
echo $info->quotaBytesUsed;

// APIv3 - dokumentace velmi vázne v tomto
$items = "limit,usage,usageInDrive,usageInDriveTrash";
$info = $service->about->get([
    'fields' => "storageQuota({$items})",
]);
print_r($info->getStorageQuota());
  • Vlastník souboru je Service account, pokud soubor vymažeme přes klienta GDrive, smaže se jen od nás, ale zůstane pro Service account stále viditelný. Opravdové smazání lze provést pouze přes API opět.
  • API vyhodí chybu, pokud se pokusíme změnit vlastníka na sebe. Odpověď je, že tato možnost není implementována, ale pracuje se na ni. Jenže tuto hlášku to vrací již pár let, a pracují na APIv3, takže to už asi implementované nikdy nebude.
  • APIv3 je tady, a prozatím změnit vlastníka stále nelze, vrací stejnou chybu. Metoda zatím není naimplementována...

Máte zkušenosti s Google Drive API nebo Vám tento článek přišel vhod? Podělte se s námi v komentářích

Přidat komentář

Právě odpovídáte na existující komentář. Zrušit

Komentáře

Jan Jindra

10.10.2016 09:07

Moc děkuji za návod - velice mi pomohl.

Jinak problémy se kterými jsem se setkal já:
- require_once 'Google/autoload.php'; - je google/google-api-php-client z GitHubu
- Dále byl proveden nějaký refactor nad metodami v nové Google API:
$file->setTitle je nově $file->setName,
místo:
$parent = new Google_Service_Drive_ParentReference();
$parent->setId($folderId);
$file->setParents(array($parent));
se uvádí, že stačí:
$file->setParents(array($folderId));

Odpovědět

Novinky z blogu

Algoritmizace geometrických úloh

Výjimečně napíšu o předmětu z mého studia na VŠB. A to proto, že výstupem je pár videí, vysvětlujících některé probírané algoritmy, které následně skončily na YouTube...

Další články