こんにちは!今回は、PHPの標準関数であるsymlink()について詳しく解説していきます。シンボリックリンク(ソフトリンク)を作成できる、ファイルシステム管理に便利な関数です!
symlink関数とは?
symlink()関数は、シンボリックリンク(symbolic link)を作成する関数です。
シンボリックリンクは、別のファイルやディレクトリを指す特殊なファイルで、ショートカットのような役割を果たします。デプロイメント、バージョン管理、パス管理など、様々な場面で活用されます!
基本的な構文
symlink(string $target, string $link): bool
- $target: リンク先のパス(リンクが参照するファイルやディレクトリ)
- $link: 作成するシンボリックリンクのパス
- 戻り値: 成功時は
true、失敗時はfalse
シンボリックリンクとは?
// シンボリックリンクの概念
//
// 元のファイル: /var/www/app/releases/v1.0.0/index.php
// シンボリックリンク: /var/www/app/current → v1.0.0
//
// currentにアクセスすると、実際にはv1.0.0にアクセスされる
// 特徴:
// - リンク先が削除されても、リンク自体は残る(デッドリンク)
// - ディレクトリへのリンクも作成可能
// - 絶対パスでも相対パスでも指定可能
// - Windows ではサポートが制限的
基本的な使用例
シンプルなリンク作成
// ファイルへのシンボリックリンク
$target = '/tmp/original.txt';
$link = '/tmp/link.txt';
// 元のファイルを作成
file_put_contents($target, 'Hello, World!');
// シンボリックリンクを作成
if (symlink($target, $link)) {
echo "シンボリックリンクを作成しました\n";
} else {
echo "シンボリックリンクの作成に失敗しました\n";
}
// リンクを通してファイルを読む
echo file_get_contents($link); // "Hello, World!"
ディレクトリへのリンク
// ディレクトリへのシンボリックリンク
$targetDir = '/var/www/app/releases/v1.0.0';
$linkDir = '/var/www/app/current';
if (symlink($targetDir, $linkDir)) {
echo "ディレクトリのシンボリックリンクを作成しました\n";
}
// リンクを通してディレクトリにアクセス
$files = scandir($linkDir);
print_r($files);
相対パスでのリンク
// 相対パスを使用
$target = '../data/config.php'; // 相対パス
$link = '/tmp/config-link.php';
if (symlink($target, $link)) {
echo "相対パスでシンボリックリンクを作成しました\n";
}
// リンク先の実際のパスを取得
$realPath = realpath($link);
echo "実際のパス: {$realPath}\n";
リンクの確認
$link = '/tmp/link.txt';
// シンボリックリンクかどうか確認
if (is_link($link)) {
echo "{$link} はシンボリックリンクです\n";
// リンク先を取得
$target = readlink($link);
echo "リンク先: {$target}\n";
}
// ファイルとして存在するか確認
if (file_exists($link)) {
echo "リンク先のファイルが存在します\n";
}
リンクの削除
$link = '/tmp/link.txt';
// シンボリックリンクを削除(リンク先は削除されない)
if (unlink($link)) {
echo "シンボリックリンクを削除しました\n";
}
// 注意: unlinkはリンク自体を削除し、リンク先には影響しない
エラーハンドリング
$target = '/tmp/original.txt';
$link = '/tmp/link.txt';
// リンクが既に存在する場合
if (file_exists($link)) {
echo "リンクが既に存在します\n";
unlink($link); // 既存のリンクを削除
}
// リンクを作成
if (symlink($target, $link)) {
echo "成功\n";
} else {
$error = error_get_last();
echo "失敗: {$error['message']}\n";
}
実践的な使用例
例1: リンク管理システム
class SymlinkManager {
/**
* シンボリックリンクを作成
*/
public static function create($target, $link, $overwrite = false) {
// リンクが既に存在する場合
if (file_exists($link) || is_link($link)) {
if (!$overwrite) {
return [
'success' => false,
'error' => 'Link already exists'
];
}
// 既存のリンクを削除
unlink($link);
}
// ターゲットの存在確認
if (!file_exists($target)) {
return [
'success' => false,
'error' => 'Target does not exist'
];
}
// シンボリックリンクを作成
if (symlink($target, $link)) {
return [
'success' => true,
'target' => $target,
'link' => $link
];
}
return [
'success' => false,
'error' => error_get_last()['message'] ?? 'Unknown error'
];
}
/**
* リンク先を取得
*/
public static function getTarget($link) {
if (!is_link($link)) {
return null;
}
return readlink($link);
}
/**
* リンクを更新(新しいターゲットに変更)
*/
public static function update($link, $newTarget) {
if (!is_link($link)) {
return [
'success' => false,
'error' => 'Not a symlink'
];
}
// 既存のリンクを削除
unlink($link);
// 新しいリンクを作成
return self::create($newTarget, $link);
}
/**
* リンクを削除
*/
public static function remove($link) {
if (!is_link($link)) {
return [
'success' => false,
'error' => 'Not a symlink'
];
}
if (unlink($link)) {
return [
'success' => true,
'removed' => $link
];
}
return [
'success' => false,
'error' => 'Failed to remove symlink'
];
}
/**
* リンク情報を取得
*/
public static function getInfo($link) {
if (!is_link($link)) {
return null;
}
$target = readlink($link);
$stat = lstat($link);
return [
'link' => $link,
'target' => $target,
'target_exists' => file_exists($target),
'real_path' => realpath($link),
'created' => $stat['ctime'],
'modified' => $stat['mtime']
];
}
/**
* リンクが有効か確認(リンク先が存在するか)
*/
public static function isValid($link) {
if (!is_link($link)) {
return false;
}
$target = readlink($link);
return file_exists($target);
}
/**
* デッドリンクを検出
*/
public static function isDeadLink($link) {
return is_link($link) && !file_exists($link);
}
}
// 使用例
echo "=== シンボリックリンク管理 ===\n";
$target = '/tmp/original.txt';
$link = '/tmp/mylink.txt';
// ファイルを作成
file_put_contents($target, 'Sample content');
// リンクを作成
$result = SymlinkManager::create($target, $link);
if ($result['success']) {
echo "リンクを作成しました\n";
} else {
echo "エラー: {$result['error']}\n";
}
// リンク情報を取得
$info = SymlinkManager::getInfo($link);
if ($info) {
echo "\nリンク情報:\n";
echo " リンク: {$info['link']}\n";
echo " ターゲット: {$info['target']}\n";
echo " 有効: " . ($info['target_exists'] ? 'Yes' : 'No') . "\n";
echo " 実パス: {$info['real_path']}\n";
}
// リンクの有効性チェック
echo "\n有効なリンク: " . (SymlinkManager::isValid($link) ? 'Yes' : 'No') . "\n";
// ターゲットを削除してデッドリンクをテスト
unlink($target);
echo "デッドリンク: " . (SymlinkManager::isDeadLink($link) ? 'Yes' : 'No') . "\n";
// クリーンアップ
SymlinkManager::remove($link);
例2: デプロイメントシステム
class DeploymentManager {
private $baseDir;
private $releasesDir;
private $currentLink;
public function __construct($baseDir) {
$this->baseDir = $baseDir;
$this->releasesDir = $baseDir . '/releases';
$this->currentLink = $baseDir . '/current';
// ディレクトリ構造を作成
if (!is_dir($this->releasesDir)) {
mkdir($this->releasesDir, 0755, true);
}
}
/**
* 新しいリリースをデプロイ
*/
public function deploy($version, $files) {
$releaseDir = $this->releasesDir . '/' . $version;
// リリースディレクトリを作成
if (!mkdir($releaseDir, 0755, true)) {
return [
'success' => false,
'error' => 'Failed to create release directory'
];
}
// ファイルをコピー
foreach ($files as $source => $destination) {
$targetPath = $releaseDir . '/' . $destination;
$targetDir = dirname($targetPath);
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
copy($source, $targetPath);
}
// currentリンクを更新
$this->switchVersion($version);
return [
'success' => true,
'version' => $version,
'path' => $releaseDir
];
}
/**
* バージョンを切り替え
*/
public function switchVersion($version) {
$releaseDir = $this->releasesDir . '/' . $version;
if (!is_dir($releaseDir)) {
return [
'success' => false,
'error' => 'Release does not exist'
];
}
// 既存のcurrentリンクを削除
if (is_link($this->currentLink)) {
unlink($this->currentLink);
}
// 新しいリンクを作成
if (symlink($releaseDir, $this->currentLink)) {
return [
'success' => true,
'version' => $version,
'current' => $this->currentLink
];
}
return [
'success' => false,
'error' => 'Failed to create symlink'
];
}
/**
* 現在のバージョンを取得
*/
public function getCurrentVersion() {
if (!is_link($this->currentLink)) {
return null;
}
$target = readlink($this->currentLink);
return basename($target);
}
/**
* 利用可能なバージョン一覧
*/
public function listVersions() {
if (!is_dir($this->releasesDir)) {
return [];
}
$versions = [];
$items = scandir($this->releasesDir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $this->releasesDir . '/' . $item;
if (is_dir($path)) {
$versions[] = [
'version' => $item,
'path' => $path,
'is_current' => ($item === $this->getCurrentVersion())
];
}
}
return $versions;
}
/**
* 古いリリースを削除
*/
public function cleanOldReleases($keep = 3) {
$versions = $this->listVersions();
// 現在のバージョンを除外
$versions = array_filter($versions, function($v) {
return !$v['is_current'];
});
// 日付でソート(新しい順)
usort($versions, function($a, $b) {
return filemtime($b['path']) <=> filemtime($a['path']);
});
// 保持する数を超えたリリースを削除
$removed = [];
for ($i = $keep; $i < count($versions); $i++) {
$this->removeDirectory($versions[$i]['path']);
$removed[] = $versions[$i]['version'];
}
return [
'removed' => $removed,
'kept' => array_slice(array_column($versions, 'version'), 0, $keep)
];
}
/**
* ディレクトリを再帰的に削除
*/
private function removeDirectory($dir) {
if (!is_dir($dir)) {
return;
}
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $dir . '/' . $item;
if (is_dir($path)) {
$this->removeDirectory($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
/**
* ロールバック(前のバージョンに戻す)
*/
public function rollback() {
$versions = $this->listVersions();
$current = $this->getCurrentVersion();
// 現在のバージョンを除外
$versions = array_filter($versions, function($v) use ($current) {
return $v['version'] !== $current;
});
// 日付でソート(新しい順)
usort($versions, function($a, $b) {
return filemtime($b['path']) <=> filemtime($a['path']);
});
if (empty($versions)) {
return [
'success' => false,
'error' => 'No previous version available'
];
}
// 最新の前バージョンに切り替え
$previousVersion = $versions[0]['version'];
return $this->switchVersion($previousVersion);
}
}
// 使用例
echo "=== デプロイメントシステム ===\n";
$deployment = new DeploymentManager('/tmp/app');
// バージョン1.0.0をデプロイ
$files = [
'/tmp/source/index.php' => 'index.php',
'/tmp/source/config.php' => 'config.php'
];
// ファイルを準備(テスト用)
@mkdir('/tmp/source', 0755, true);
file_put_contents('/tmp/source/index.php', '<?php echo "v1.0.0";');
file_put_contents('/tmp/source/config.php', '<?php return ["version" => "1.0.0"];');
$result = $deployment->deploy('v1.0.0', $files);
if ($result['success']) {
echo "v1.0.0をデプロイしました\n";
}
// 現在のバージョンを確認
$current = $deployment->getCurrentVersion();
echo "現在のバージョン: {$current}\n";
// バージョン一覧
echo "\n利用可能なバージョン:\n";
foreach ($deployment->listVersions() as $version) {
$marker = $version['is_current'] ? ' (current)' : '';
echo " - {$version['version']}{$marker}\n";
}
// v1.1.0をデプロイ
file_put_contents('/tmp/source/index.php', '<?php echo "v1.1.0";');
$deployment->deploy('v1.1.0', $files);
echo "\nv1.1.0をデプロイしました\n";
echo "現在のバージョン: " . $deployment->getCurrentVersion() . "\n";
// ロールバック
$rollback = $deployment->rollback();
if ($rollback['success']) {
echo "\nロールバックしました\n";
echo "現在のバージョン: " . $deployment->getCurrentVersion() . "\n";
}
例3: バージョン管理システム
class VersionManager {
private $versionsDir;
private $activeLink;
public function __construct($baseDir) {
$this->versionsDir = $baseDir . '/versions';
$this->activeLink = $baseDir . '/active';
if (!is_dir($this->versionsDir)) {
mkdir($this->versionsDir, 0755, true);
}
}
/**
* 新しいバージョンを作成
*/
public function createVersion($version, $content) {
$versionPath = $this->versionsDir . '/' . $version;
if (file_exists($versionPath)) {
return [
'success' => false,
'error' => 'Version already exists'
];
}
if (file_put_contents($versionPath, $content) !== false) {
return [
'success' => true,
'version' => $version,
'path' => $versionPath
];
}
return [
'success' => false,
'error' => 'Failed to create version'
];
}
/**
* バージョンをアクティブに設定
*/
public function setActive($version) {
$versionPath = $this->versionsDir . '/' . $version;
if (!file_exists($versionPath)) {
return [
'success' => false,
'error' => 'Version does not exist'
];
}
// 既存のリンクを削除
if (is_link($this->activeLink)) {
unlink($this->activeLink);
}
// 新しいリンクを作成
if (symlink($versionPath, $this->activeLink)) {
return [
'success' => true,
'version' => $version,
'active' => $this->activeLink
];
}
return [
'success' => false,
'error' => 'Failed to set active version'
];
}
/**
* アクティブなバージョンを取得
*/
public function getActiveVersion() {
if (!is_link($this->activeLink)) {
return null;
}
$target = readlink($this->activeLink);
return basename($target);
}
/**
* アクティブなバージョンの内容を読む
*/
public function readActive() {
if (!is_link($this->activeLink)) {
return null;
}
return file_get_contents($this->activeLink);
}
/**
* すべてのバージョンを一覧表示
*/
public function listVersions() {
if (!is_dir($this->versionsDir)) {
return [];
}
$versions = [];
$items = scandir($this->versionsDir);
$active = $this->getActiveVersion();
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $this->versionsDir . '/' . $item;
if (is_file($path)) {
$versions[] = [
'version' => $item,
'is_active' => ($item === $active),
'size' => filesize($path),
'modified' => filemtime($path)
];
}
}
// バージョン番号でソート
usort($versions, function($a, $b) {
return version_compare($a['version'], $b['version']);
});
return $versions;
}
/**
* バージョンを比較
*/
public function compareVersions($version1, $version2) {
$path1 = $this->versionsDir . '/' . $version1;
$path2 = $this->versionsDir . '/' . $version2;
if (!file_exists($path1) || !file_exists($path2)) {
return null;
}
$content1 = file_get_contents($path1);
$content2 = file_get_contents($path2);
return [
'version1' => $version1,
'version2' => $version2,
'identical' => $content1 === $content2,
'size_diff' => strlen($content1) - strlen($content2)
];
}
}
// 使用例
echo "=== バージョン管理システム ===\n";
$vm = new VersionManager('/tmp/config');
// バージョンを作成
$vm->createVersion('1.0.0', 'setting1=value1');
$vm->createVersion('1.0.1', 'setting1=value1\nsetting2=value2');
$vm->createVersion('1.1.0', 'setting1=value1\nsetting2=value2\nsetting3=value3');
// バージョン一覧
echo "利用可能なバージョン:\n";
foreach ($vm->listVersions() as $version) {
$marker = $version['is_active'] ? ' (active)' : '';
echo " {$version['version']}{$marker} - " .
date('Y-m-d H:i:s', $version['modified']) . "\n";
}
// v1.0.1をアクティブに設定
$vm->setActive('1.0.1');
echo "\nアクティブバージョン: " . $vm->getActiveVersion() . "\n";
echo "内容:\n" . $vm->readActive() . "\n";
// バージョン比較
$comparison = $vm->compareVersions('1.0.0', '1.0.1');
if ($comparison) {
echo "\n{$comparison['version1']} vs {$comparison['version2']}:\n";
echo " 同一: " . ($comparison['identical'] ? 'Yes' : 'No') . "\n";
echo " サイズ差: {$comparison['size_diff']} bytes\n";
}
例4: パス管理ツール
class PathManager {
/**
* 共通ディレクトリへのショートカットを作成
*/
public static function createShortcut($name, $target, $shortcutsDir = '/tmp/shortcuts') {
if (!is_dir($shortcutsDir)) {
mkdir($shortcutsDir, 0755, true);
}
$link = $shortcutsDir . '/' . $name;
// 既存のショートカットを削除
if (is_link($link)) {
unlink($link);
}
if (symlink($target, $link)) {
return [
'success' => true,
'shortcut' => $link,
'target' => $target
];
}
return [
'success' => false,
'error' => 'Failed to create shortcut'
];
}
/**
* ショートカット一覧を取得
*/
public static function listShortcuts($shortcutsDir = '/tmp/shortcuts') {
if (!is_dir($shortcutsDir)) {
return [];
}
$shortcuts = [];
$items = scandir($shortcutsDir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$link = $shortcutsDir . '/' . $item;
if (is_link($link)) {
$target = readlink($link);
$shortcuts[] = [
'name' => $item,
'link' => $link,
'target' => $target,
'valid' => file_exists($target)
];
}
}
return $shortcuts;
}
/**
* ショートカットを削除
*/
public static function removeShortcut($name, $shortcutsDir = '/tmp/shortcuts') {
$link = $shortcutsDir . '/' . $name;
if (!is_link($link)) {
return [
'success' => false,
'error' => 'Shortcut does not exist'
];
}
if (unlink($link)) {
return [
'success' => true,
'removed' => $link
];
}
return [
'success' => false,
'error' => 'Failed to remove shortcut'
];
}
/**
* デッドリンクをクリーンアップ
*/
public static function cleanDeadLinks($shortcutsDir = '/tmp/shortcuts') {
$shortcuts = self::listShortcuts($shortcutsDir);
$removed = [];
foreach ($shortcuts as $shortcut) {
if (!$shortcut['valid']) {
unlink($shortcut['link']);
$removed[] = $shortcut['name'];
}
}
return [
'removed' => $removed,
'count' => count($removed)
];
}
}
// 使用例
echo "=== パス管理ツール ===\n";
// ディレクトリを作成
@mkdir('/tmp/projects/myapp', 0755, true);
@mkdir('/tmp/projects/webapp', 0755, true);
@mkdir('/tmp/logs', 0755, true);
// ショートカットを作成
PathManager::createShortcut('myapp', '/tmp/projects/myapp');
PathManager::createShortcut('webapp', '/tmp/projects/webapp');
PathManager::createShortcut('logs', '/tmp/logs');
// ショートカット一覧
echo "ショートカット一覧:\n";
foreach (PathManager::listShortcuts() as $shortcut) {
$status = $shortcut['valid'] ? '✓' : '✗';
echo " {$status} {$shortcut['name']} → {$shortcut['target']}\n";
}
// ディレクトリを削除してデッドリンクを作成
rmdir('/tmp/logs');
// デッドリンクをクリーンアップ
$cleanup = PathManager::cleanDeadLinks();
echo "\nクリーンアップ: {$cleanup['count']}個のデッドリンクを削除\n";
例5: プラグインシステム
class PluginManager {
private $pluginsDir;
private $enabledDir;
public function __construct($baseDir) {
$this->pluginsDir = $baseDir . '/available';
$this->enabledDir = $baseDir . '/enabled';
if (!is_dir($this->pluginsDir)) {
mkdir($this->pluginsDir, 0755, true);
}
if (!is_dir($this->enabledDir)) {
mkdir($this->enabledDir, 0755, true);
}
}
/**
* プラグインをインストール
*/
public function install($pluginName, $pluginPath) {
$targetPath = $this->pluginsDir . '/' . $pluginName;
if (file_exists($targetPath)) {
return [
'success' => false,
'error' => 'Plugin already installed'
];
}
if (copy($pluginPath, $targetPath)) {
return [
'success' => true,
'plugin' => $pluginName,
'path' => $targetPath
];
}
return [
'success' => false,
'error' => 'Failed to install plugin'
];
}
/**
* プラグインを有効化
*/
public function enable($pluginName) {
$sourcePath = $this->pluginsDir . '/' . $pluginName;
$linkPath = $this->enabledDir . '/' . $pluginName;
if (!file_exists($sourcePath)) {
return [
'success' => false,
'error' => 'Plugin not installed'
];
}
if (is_link($linkPath)) {
return [
'success' => false,
'error' => 'Plugin already enabled'
];
}
if (symlink($sourcePath, $linkPath)) {
return [
'success' => true,
'plugin' => $pluginName,
'enabled' => true
];
}
return [
'success' => false,
'error' => 'Failed to enable plugin'
];
}
/**
* プラグインを無効化
*/
public function disable($pluginName) {
$linkPath = $this->enabledDir . '/' . $pluginName;
if (!is_link($linkPath)) {
return [
'success' => false,
'error' => 'Plugin not enabled'
];
}
if (unlink($linkPath)) {
return [
'success' => true,
'plugin' => $pluginName,
'enabled' => false
];
}
return [
'success' => false,
'error' => 'Failed to disable plugin'
];
}
/**
* インストール済みプラグインの一覧
*/
public function listAvailable() {
$plugins = [];
$items = scandir($this->pluginsDir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $this->pluginsDir . '/' . $item;
if (is_file($path)) {
$plugins[] = [
'name' => $item,
'enabled' => $this->isEnabled($item)
];
}
}
return $plugins;
}
/**
* 有効化されているプラグインの一覧
*/
public function listEnabled() {
$plugins = [];
$items = scandir($this->enabledDir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$link = $this->enabledDir . '/' . $item;
if (is_link($link)) {
$plugins[] = $item;
}
}
return $plugins;
}
/**
* プラグインが有効化されているか確認
*/
public function isEnabled($pluginName) {
$linkPath = $this->enabledDir . '/' . $pluginName;
return is_link($linkPath);
}
/**
* プラグインをアンインストール
*/
public function uninstall($pluginName) {
// 有効化されている場合は無効化
if ($this->isEnabled($pluginName)) {
$this->disable($pluginName);
}
$pluginPath = $this->pluginsDir . '/' . $pluginName;
if (!file_exists($pluginPath)) {
return [
'success' => false,
'error' => 'Plugin not installed'
];
}
if (unlink($pluginPath)) {
return [
'success' => true,
'plugin' => $pluginName
];
}
return [
'success' => false,
'error' => 'Failed to uninstall plugin'
];
}
}
// 使用例
echo "=== プラグインシステム ===\n";
$pm = new PluginManager('/tmp/plugins');
// プラグインファイルを作成(テスト用)
@mkdir('/tmp/plugin-source', 0755, true);
file_put_contents('/tmp/plugin-source/analytics.php', '<?php // Analytics Plugin');
file_put_contents('/tmp/plugin-source/cache.php', '<?php // Cache Plugin');
file_put_contents('/tmp/plugin-source/seo.php', '<?php // SEO Plugin');
// プラグインをインストール
$pm->install('analytics.php', '/tmp/plugin-source/analytics.php');
$pm->install('cache.php', '/tmp/plugin-source/cache.php');
$pm->install('seo.php', '/tmp/plugin-source/seo.php');
echo "インストール済みプラグイン:\n";
foreach ($pm->listAvailable() as $plugin) {
$status = $plugin['enabled'] ? '[有効]' : '[無効]';
echo " {$status} {$plugin['name']}\n";
}
// プラグインを有効化
$pm->enable('analytics.php');
$pm->enable('cache.php');
echo "\n有効なプラグイン:\n";
foreach ($pm->listEnabled() as $plugin) {
echo " - {$plugin}\n";
}
// プラグインを無効化
$pm->disable('cache.php');
echo "\nプラグイン状態:\n";
foreach ($pm->listAvailable() as $plugin) {
$status = $plugin['enabled'] ? '[有効]' : '[無効]';
echo " {$status} {$plugin['name']}\n";
}
例6: キャッシュディレクトリ管理
class CacheDirectoryManager {
private $cacheBaseDir;
private $currentLink;
public function __construct($cacheBaseDir) {
$this->cacheBaseDir = $cacheBaseDir;
$this->currentLink = $cacheBaseDir . '/current';
if (!is_dir($cacheBaseDir)) {
mkdir($cacheBaseDir, 0755, true);
}
}
/**
* 新しいキャッシュディレクトリを作成
*/
public function createCacheDir() {
$timestamp = time();
$cacheDirName = 'cache_' . $timestamp;
$cacheDirPath = $this->cacheBaseDir . '/' . $cacheDirName;
if (!mkdir($cacheDirPath, 0755, true)) {
return [
'success' => false,
'error' => 'Failed to create cache directory'
];
}
return [
'success' => true,
'path' => $cacheDirPath,
'name' => $cacheDirName
];
}
/**
* キャッシュディレクトリをアクティブに設定
*/
public function setActive($cacheDirName) {
$cacheDirPath = $this->cacheBaseDir . '/' . $cacheDirName;
if (!is_dir($cacheDirPath)) {
return [
'success' => false,
'error' => 'Cache directory does not exist'
];
}
// 既存のリンクを削除
if (is_link($this->currentLink)) {
unlink($this->currentLink);
}
// 新しいリンクを作成
if (symlink($cacheDirPath, $this->currentLink)) {
return [
'success' => true,
'active' => $cacheDirName
];
}
return [
'success' => false,
'error' => 'Failed to set active cache directory'
];
}
/**
* キャッシュをアトミックに更新
*/
public function atomicUpdate($dataCallback) {
// 新しいキャッシュディレクトリを作成
$result = $this->createCacheDir();
if (!$result['success']) {
return $result;
}
$newCacheDir = $result['path'];
// データを生成
try {
$dataCallback($newCacheDir);
} catch (Exception $e) {
// エラーが発生したら新しいディレクトリを削除
$this->removeDirectory($newCacheDir);
return [
'success' => false,
'error' => $e->getMessage()
];
}
// アトミックに切り替え
$this->setActive($result['name']);
return [
'success' => true,
'cache_dir' => $newCacheDir
];
}
/**
* 古いキャッシュディレクトリを削除
*/
public function cleanOldCache($keep = 2) {
$items = scandir($this->cacheBaseDir);
$cacheDirs = [];
foreach ($items as $item) {
if ($item === '.' || $item === '..' || $item === 'current') {
continue;
}
$path = $this->cacheBaseDir . '/' . $item;
if (is_dir($path) && strpos($item, 'cache_') === 0) {
$cacheDirs[] = [
'name' => $item,
'path' => $path,
'time' => filemtime($path)
];
}
}
// 時間でソート(新しい順)
usort($cacheDirs, function($a, $b) {
return $b['time'] <=> $a['time'];
});
// 保持する数を超えたディレクトリを削除
$removed = [];
for ($i = $keep; $i < count($cacheDirs); $i++) {
$this->removeDirectory($cacheDirs[$i]['path']);
$removed[] = $cacheDirs[$i]['name'];
}
return [
'removed' => $removed,
'count' => count($removed)
];
}
/**
* ディレクトリを再帰的に削除
*/
private function removeDirectory($dir) {
if (!is_dir($dir)) {
return;
}
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $dir . '/' . $item;
if (is_dir($path)) {
$this->removeDirectory($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
}
// 使用例
echo "=== キャッシュディレクトリ管理 ===\n";
$cache = new CacheDirectoryManager('/tmp/app-cache');
// アトミックにキャッシュを更新
$result = $cache->atomicUpdate(function($cacheDir) {
// キャッシュデータを生成
file_put_contents($cacheDir . '/data1.json', json_encode(['key' => 'value1']));
file_put_contents($cacheDir . '/data2.json', json_encode(['key' => 'value2']));
echo "キャッシュデータを生成しました\n";
});
if ($result['success']) {
echo "キャッシュを更新しました: {$result['cache_dir']}\n";
}
// もう一度更新
sleep(1);
$cache->atomicUpdate(function($cacheDir) {
file_put_contents($cacheDir . '/data1.json', json_encode(['key' => 'updated_value1']));
file_put_contents($cacheDir . '/data2.json', json_encode(['key' => 'updated_value2']));
});
echo "\n2回目の更新完了\n";
// 古いキャッシュをクリーンアップ
$cleanup = $cache->cleanOldCache(1);
echo "\nクリーンアップ: {$cleanup['count']}個の古いキャッシュを削除\n";
link()関数との違い
// symlink() - シンボリックリンク(ソフトリンク)
// link() - ハードリンク
$original = '/tmp/original.txt';
file_put_contents($original, 'Hello');
// シンボリックリンク
$symlink = '/tmp/symlink.txt';
symlink($original, $symlink);
// ハードリンク
$hardlink = '/tmp/hardlink.txt';
link($original, $hardlink);
// 違い:
// 1. シンボリックリンクは別のファイル(リンク先へのポインタ)
// 2. ハードリンクは同じファイルへの別の名前
// 3. 元ファイルを削除した場合:
// - シンボリックリンクはデッドリンクになる
// - ハードリンクは有効なまま
unlink($original);
// シンボリックリンクはデッドリンク
echo is_link($symlink) ? "リンク存在\n" : "リンク消失\n"; // リンク存在
echo file_exists($symlink) ? "ファイル存在\n" : "ファイル消失\n"; // ファイル消失
// ハードリンクは有効
echo file_exists($hardlink) ? "ファイル存在\n" : "ファイル消失\n"; // ファイル存在
まとめ
symlink()関数の特徴をまとめると:
できること:
- シンボリックリンクの作成
- ファイル/ディレクトリへのリンク
- 相対パス/絶対パスの指定
利点:
- デプロイメントの簡素化
- バージョン管理
- パスの抽象化
- アトミックな切り替え
推奨される使用場面:
- デプロイメントシステム
- バージョン管理
- プラグイン管理
- キャッシュ管理
- ショートカット作成
注意点:
- リンク先が削除されるとデッドリンクになる
- Windowsでは制限あり
- パーミッションに注意
- 既存リンクは上書きされない
関連関数:
readlink(): リンク先を取得is_link(): リンクかチェックlink(): ハードリンク作成unlink(): リンク削除realpath(): 実際のパスを取得lstat(): リンク自体の情報取得
よく使うパターン:
// デプロイメント
symlink('/releases/v1.0.0', '/current');
// バージョン切り替え
unlink('/current');
symlink('/releases/v1.0.1', '/current');
// リンクの確認
if (is_link($path)) {
$target = readlink($path);
}
// デッドリンクのチェック
if (is_link($path) && !file_exists($path)) {
echo "デッドリンク\n";
}
symlink()は、デプロイメントやバージョン管理において非常に重要な関数です。アトミックな切り替えや柔軟なパス管理を実現し、システムの信頼性を高めます!
