こんにちは!今回は、PHPの標準関数であるsubstr()について詳しく解説していきます。文字列から指定した位置・長さの部分を取得できる、最も基本的で重要な関数の一つです!
substr関数とは?
substr()関数は、文字列から指定した位置・長さの部分文字列を取得する関数です。
“substring”の略で、文字列の切り出し、分割、トリミングなど、様々な文字列操作の基礎となる関数です!
基本的な構文
substr(string $string, int $offset, ?int $length = null): string
- $string: 対象の文字列
- $offset: 開始位置(0から始まる)
- $length: 取得する長さ(省略可能)
- 戻り値: 部分文字列、失敗時は
false
基本的な使用例
シンプルな切り出し
$text = "Hello World";
// 位置6から最後まで
echo substr($text, 6) . "\n";
// 出力: World
// 位置0から5文字
echo substr($text, 0, 5) . "\n";
// 出力: Hello
// 位置6から5文字
echo substr($text, 6, 5) . "\n";
// 出力: World
負のオフセット
$text = "Hello World";
// 末尾から5文字
echo substr($text, -5) . "\n";
// 出力: World
// 末尾から5文字目から3文字
echo substr($text, -5, 3) . "\n";
// 出力: Wor
負の長さ
$text = "Hello World";
// 先頭から末尾5文字を除く
echo substr($text, 0, -6) . "\n";
// 出力: Hello
// 位置6から末尾1文字を除く
echo substr($text, 6, -1) . "\n";
// 出力: Worl
範囲外の指定
$text = "Hello";
// 開始位置が文字列長を超える
var_dump(substr($text, 10));
// bool(false)
// 長さが0以下
echo substr($text, 0, 0) . "\n";
// 出力: ""(空文字列)
// 負のオフセットが大きすぎる
echo substr($text, -10, 3) . "\n";
// 出力: Hel(先頭から扱われる)
実践的な使用例
例1: テキストの切り詰め
class TextTruncator {
/**
* 指定長で切り詰め
*/
public static function truncate($text, $maxLength, $suffix = '...') {
if (strlen($text) <= $maxLength) {
return $text;
}
return substr($text, 0, $maxLength - strlen($suffix)) . $suffix;
}
/**
* 単語の途中で切らない
*/
public static function truncateWords($text, $maxLength, $suffix = '...') {
if (strlen($text) <= $maxLength) {
return $text;
}
$truncated = substr($text, 0, $maxLength - strlen($suffix));
// 最後のスペースを探す
$lastSpace = strrpos($truncated, ' ');
if ($lastSpace !== false && $lastSpace > $maxLength * 0.7) {
$truncated = substr($truncated, 0, $lastSpace);
}
return $truncated . $suffix;
}
/**
* 中央を省略
*/
public static function truncateMiddle($text, $maxLength, $separator = '...') {
if (strlen($text) <= $maxLength) {
return $text;
}
$sideLength = floor(($maxLength - strlen($separator)) / 2);
$start = substr($text, 0, $sideLength);
$end = substr($text, -$sideLength);
return $start . $separator . $end;
}
/**
* 文の途中で切らない
*/
public static function truncateSentences($text, $maxLength) {
if (strlen($text) <= $maxLength) {
return $text;
}
$truncated = substr($text, 0, $maxLength);
// 最後の句点を探す
$lastPeriod = max(
strrpos($truncated, '.'),
strrpos($truncated, '。')
);
if ($lastPeriod !== false && $lastPeriod > $maxLength * 0.5) {
return substr($text, 0, $lastPeriod + 1);
}
return self::truncateWords($text, $maxLength);
}
/**
* 先頭を省略
*/
public static function truncateStart($text, $maxLength, $prefix = '...') {
if (strlen($text) <= $maxLength) {
return $text;
}
return $prefix . substr($text, -(($maxLength - strlen($prefix))));
}
}
// 使用例
$longText = "This is a very long text that needs to be truncated for display purposes.";
echo "=== テキスト切り詰め ===\n";
echo TextTruncator::truncate($longText, 30) . "\n";
// This is a very long text...
echo "\n=== 単語の途中で切らない ===\n";
echo TextTruncator::truncateWords($longText, 30) . "\n";
// This is a very long...
echo "\n=== 中央省略 ===\n";
$path = "/very/long/path/to/some/file/document.pdf";
echo TextTruncator::truncateMiddle($path, 25) . "\n";
// /very/long...ument.pdf
echo "\n=== 先頭省略 ===\n";
echo TextTruncator::truncateStart($longText, 30) . "\n";
// ...for display purposes.
例2: ファイル名とパスの処理
class PathHelper {
/**
* 拡張子を取得
*/
public static function getExtension($filename) {
$pos = strrpos($filename, '.');
if ($pos === false) {
return '';
}
return substr($filename, $pos + 1);
}
/**
* ファイル名(拡張子除く)を取得
*/
public static function getBasename($filename) {
$pos = strrpos($filename, '.');
if ($pos === false) {
return $filename;
}
return substr($filename, 0, $pos);
}
/**
* ディレクトリ名を取得
*/
public static function getDirectory($path) {
$pos = max(strrpos($path, '/'), strrpos($path, '\\'));
if ($pos === false) {
return '';
}
return substr($path, 0, $pos);
}
/**
* ファイル名のみを取得
*/
public static function getFilename($path) {
$pos = max(strrpos($path, '/'), strrpos($path, '\\'));
if ($pos === false) {
return $path;
}
return substr($path, $pos + 1);
}
/**
* 最後のn個のディレクトリを取得
*/
public static function getLastDirectories($path, $n) {
$parts = preg_split('#[/\\\\]#', $path);
$parts = array_filter($parts); // 空要素を除去
if (count($parts) <= $n) {
return $path;
}
$lastParts = array_slice($parts, -$n);
return implode('/', $lastParts);
}
}
// 使用例
$files = [
'/var/www/html/index.php',
'C:\\Users\\Documents\\report.pdf',
'image.backup.jpg',
'README'
];
echo "=== パス処理 ===\n";
foreach ($files as $file) {
echo "\nファイル: {$file}\n";
echo " 拡張子: " . PathHelper::getExtension($file) . "\n";
echo " ベース名: " . PathHelper::getBasename($file) . "\n";
echo " ディレクトリ: " . PathHelper::getDirectory($file) . "\n";
echo " ファイル名: " . PathHelper::getFilename($file) . "\n";
}
$path = '/var/www/html/images/2024/january/photo.jpg';
echo "\n=== 最後の3ディレクトリ ===\n";
echo PathHelper::getLastDirectories($path, 3) . "\n";
// 2024/january/photo.jpg
例3: メールアドレスとURL処理
class EmailUrlHelper {
/**
* メールアドレスをマスク
*/
public static function maskEmail($email) {
$atPos = strpos($email, '@');
if ($atPos === false) {
return $email;
}
$local = substr($email, 0, $atPos);
$domain = substr($email, $atPos);
$localLength = strlen($local);
$visibleChars = min(3, $localLength);
$masked = substr($local, 0, $visibleChars) .
str_repeat('*', max(0, $localLength - $visibleChars));
return $masked . $domain;
}
/**
* ドメインの一部をマスク
*/
public static function maskDomain($email) {
$atPos = strpos($email, '@');
if ($atPos === false) {
return $email;
}
$local = substr($email, 0, $atPos + 1);
$domain = substr($email, $atPos + 1);
$dotPos = strpos($domain, '.');
if ($dotPos === false) {
return $email;
}
$domainName = substr($domain, 0, $dotPos);
$tld = substr($domain, $dotPos);
$maskedDomain = substr($domainName, 0, 1) .
str_repeat('*', strlen($domainName) - 1);
return $local . $maskedDomain . $tld;
}
/**
* URLからドメインを抽出
*/
public static function extractDomain($url) {
// プロトコルを除去
$url = preg_replace('#^https?://#', '', $url);
// パスを除去
$slashPos = strpos($url, '/');
if ($slashPos !== false) {
$url = substr($url, 0, $slashPos);
}
return $url;
}
/**
* URLを短縮表示
*/
public static function shortenUrl($url, $maxLength = 40) {
if (strlen($url) <= $maxLength) {
return $url;
}
$protocol = '';
if (preg_match('#^(https?://)#', $url, $matches)) {
$protocol = $matches[1];
$url = substr($url, strlen($protocol));
}
$sideLength = floor(($maxLength - strlen($protocol) - 3) / 2);
$start = substr($url, 0, $sideLength);
$end = substr($url, -$sideLength);
return $protocol . $start . '...' . $end;
}
}
// 使用例
echo "=== メールアドレスマスク ===\n";
$emails = [
'john.doe@example.com',
'admin@company.co.jp',
'a@test.org'
];
foreach ($emails as $email) {
echo "{$email} → " . EmailUrlHelper::maskEmail($email) . "\n";
}
echo "\n=== ドメインマスク ===\n";
foreach ($emails as $email) {
echo "{$email} → " . EmailUrlHelper::maskDomain($email) . "\n";
}
echo "\n=== URL短縮表示 ===\n";
$url = 'https://example.com/very/long/path/to/some/page.html';
echo EmailUrlHelper::shortenUrl($url) . "\n";
例4: データ抽出
class DataExtractor {
/**
* 固定長フォーマットからデータを抽出
*/
public static function extractFixedWidth($line, $positions) {
$data = [];
foreach ($positions as $field => $info) {
$start = $info['start'];
$length = $info['length'];
$value = substr($line, $start, $length);
$data[$field] = trim($value);
}
return $data;
}
/**
* 特定の位置の文字を抽出
*/
public static function extractCharAt($text, $position) {
if ($position < 0 || $position >= strlen($text)) {
return null;
}
return substr($text, $position, 1);
}
/**
* 範囲を指定してデータを抽出
*/
public static function extractRange($text, $start, $end) {
if ($start < 0 || $end > strlen($text) || $start >= $end) {
return '';
}
return substr($text, $start, $end - $start);
}
/**
* 文字列の先頭n文字を取得
*/
public static function first($text, $n) {
return substr($text, 0, $n);
}
/**
* 文字列の最後n文字を取得
*/
public static function last($text, $n) {
return substr($text, -$n);
}
/**
* 文字列から中央部分を取得
*/
public static function middle($text, $length) {
$totalLength = strlen($text);
if ($totalLength <= $length) {
return $text;
}
$start = floor(($totalLength - $length) / 2);
return substr($text, $start, $length);
}
}
// 使用例
echo "=== 固定長フォーマット ===\n";
$line = "John 30 Developer ";
$positions = [
'name' => ['start' => 0, 'length' => 10],
'age' => ['start' => 10, 'length' => 6],
'role' => ['start' => 16, 'length' => 18]
];
$data = DataExtractor::extractFixedWidth($line, $positions);
print_r($data);
echo "\n=== 文字抽出 ===\n";
$text = "Hello World";
echo "先頭5文字: " . DataExtractor::first($text, 5) . "\n";
echo "最後5文字: " . DataExtractor::last($text, 5) . "\n";
echo "中央6文字: " . DataExtractor::middle($text, 6) . "\n";
echo "範囲(3-8): " . DataExtractor::extractRange($text, 3, 8) . "\n";
例5: 文字列の分割
class StringSplitter {
/**
* 指定長で分割
*/
public static function splitByLength($text, $length) {
$chunks = [];
$textLength = strlen($text);
for ($i = 0; $i < $textLength; $i += $length) {
$chunks[] = substr($text, $i, $length);
}
return $chunks;
}
/**
* 先頭と残りに分割
*/
public static function splitFirst($text, $length) {
return [
'first' => substr($text, 0, $length),
'rest' => substr($text, $length)
];
}
/**
* 最後と残りに分割
*/
public static function splitLast($text, $length) {
return [
'rest' => substr($text, 0, -$length),
'last' => substr($text, -$length)
];
}
/**
* 前後を分割
*/
public static function splitAround($text, $position) {
return [
'before' => substr($text, 0, $position),
'after' => substr($text, $position)
];
}
/**
* 3分割(前・中・後)
*/
public static function splitThree($text, $start, $end) {
return [
'before' => substr($text, 0, $start),
'middle' => substr($text, $start, $end - $start),
'after' => substr($text, $end)
];
}
}
// 使用例
echo "=== 指定長で分割 ===\n";
$text = "1234567890ABCDEFGHIJ";
$chunks = StringSplitter::splitByLength($text, 5);
print_r($chunks);
// ['12345', '67890', 'ABCDE', 'FGHIJ']
echo "\n=== 先頭と残り ===\n";
$result = StringSplitter::splitFirst("Hello World", 5);
print_r($result);
// ['first' => 'Hello', 'rest' => ' World']
echo "\n=== 3分割 ===\n";
$text = "Before[CONTENT]After";
$result = StringSplitter::splitThree($text, 6, 15);
print_r($result);
// ['before' => 'Before', 'middle' => '[CONTENT]', 'after' => 'After']
例6: バリデーション
class StringValidator {
/**
* プレフィックスをチェック
*/
public static function hasPrefix($text, $prefix) {
$length = strlen($prefix);
return substr($text, 0, $length) === $prefix;
}
/**
* サフィックスをチェック
*/
public static function hasSuffix($text, $suffix) {
$length = strlen($suffix);
return substr($text, -$length) === $suffix;
}
/**
* 特定の位置に特定の文字列があるかチェック
*/
public static function hasAtPosition($text, $substring, $position) {
$length = strlen($substring);
return substr($text, $position, $length) === $substring;
}
/**
* ファイル名が特定の拡張子か
*/
public static function hasExtension($filename, $extension) {
$ext = '.' . ltrim($extension, '.');
return self::hasSuffix($filename, $ext);
}
/**
* 最初のn文字が同じかチェック
*/
public static function samePrefix($str1, $str2, $length) {
return substr($str1, 0, $length) === substr($str2, 0, $length);
}
}
// 使用例
echo "=== プレフィックスチェック ===\n";
var_dump(StringValidator::hasPrefix("Hello World", "Hello")); // true
var_dump(StringValidator::hasPrefix("Hello World", "World")); // false
echo "\n=== サフィックスチェック ===\n";
var_dump(StringValidator::hasSuffix("document.pdf", ".pdf")); // true
var_dump(StringValidator::hasSuffix("document.pdf", ".doc")); // false
echo "\n=== 拡張子チェック ===\n";
var_dump(StringValidator::hasExtension("file.jpg", "jpg")); // true
var_dump(StringValidator::hasExtension("file.jpg", ".jpg")); // true
echo "\n=== プレフィックス比較 ===\n";
var_dump(StringValidator::samePrefix("apple", "application", 3)); // true
例7: フォーマット処理
class StringFormatter {
/**
* クレジットカード番号をマスク
*/
public static function maskCreditCard($number) {
$clean = preg_replace('/[^0-9]/', '', $number);
$length = strlen($clean);
if ($length < 4) {
return str_repeat('*', $length);
}
$masked = str_repeat('*', $length - 4);
$last4 = substr($clean, -4);
return $masked . $last4;
}
/**
* 電話番号をフォーマット
*/
public static function formatPhoneNumber($phone, $format = '###-####-####') {
$clean = preg_replace('/[^0-9]/', '', $phone);
// 最初の3桁、次の4桁、最後の4桁
$part1 = substr($clean, 0, 3);
$part2 = substr($clean, 3, 4);
$part3 = substr($clean, 7, 4);
return "{$part1}-{$part2}-{$part3}";
}
/**
* 郵便番号をフォーマット
*/
public static function formatZipCode($zip) {
$clean = preg_replace('/[^0-9]/', '', $zip);
$part1 = substr($clean, 0, 3);
$part2 = substr($clean, 3, 4);
return "{$part1}-{$part2}";
}
/**
* 社会保障番号をマスク
*/
public static function maskSSN($ssn) {
$clean = preg_replace('/[^0-9]/', '', $ssn);
$masked = '***-**-' . substr($clean, -4);
return $masked;
}
}
// 使用例
echo "=== クレジットカードマスク ===\n";
echo StringFormatter::maskCreditCard('1234567812345678') . "\n";
// ************5678
echo "\n=== 電話番号フォーマット ===\n";
echo StringFormatter::formatPhoneNumber('09012345678') . "\n";
// 090-1234-5678
echo "\n=== 郵便番号フォーマット ===\n";
echo StringFormatter::formatZipCode('1234567') . "\n";
// 123-4567
echo "\n=== SSNマスク ===\n";
echo StringFormatter::maskSSN('123456789') . "\n";
// ***-**-6789
mb_substr()との違い
// ASCII文字列
$text = "Hello World";
echo substr($text, 0, 5) . "\n"; // Hello
echo mb_substr($text, 0, 5) . "\n"; // Hello(同じ)
// マルチバイト文字列
$japanese = "こんにちは";
// substr(): バイト単位
echo substr($japanese, 0, 6) . "\n";
// こん(UTF-8で6バイト = 2文字)
// mb_substr(): 文字単位
echo mb_substr($japanese, 0, 2) . "\n";
// こん(2文字)
// 日本語などのマルチバイト文字にはmb_substr()を使用
パフォーマンスの考慮
// 大量の部分文字列抽出
$text = str_repeat("abcdefghijklmnopqrstuvwxyz", 10000);
// substr()
$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
substr($text, 100, 50);
}
$time1 = microtime(true) - $start;
// 手動抽出
$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$result = '';
for ($j = 100; $j < 150; $j++) {
$result .= $text[$j];
}
}
$time2 = microtime(true) - $start;
echo "substr(): {$time1}秒\n";
echo "手動抽出: {$time2}秒\n";
// substr()は高度に最適化されており非常に高速
まとめ
substr()関数の特徴をまとめると:
できること:
- 部分文字列の取得
- 文字列の切り詰め
- 先頭・末尾の取得
引数の使い方:
- 正のオフセット: 先頭から数える
- 負のオフセット: 末尾から数える
- 正の長さ: 取得する文字数
- 負の長さ: 末尾から除外する文字数
推奨される使用場面:
- テキストの切り詰め
- ファイルパスの処理
- データのマスキング
- 固定長フォーマットの解析
- 文字列の分割
注意点:
- バイト単位で処理(マルチバイト文字注意)
- 範囲外の指定は
falseを返す - 負のインデックスが便利
関連関数:
mb_substr(): マルチバイト対応版str_split(): 文字列を配列に分割substr_replace(): 部分文字列を置換substr_count(): 部分文字列の出現回数
よく使うパターン:
// 先頭n文字
substr($str, 0, $n)
// 最後n文字
substr($str, -$n)
// 先頭n文字を除く
substr($str, $n)
// 最後n文字を除く
substr($str, 0, -$n)
// 中央部分
substr($str, $start, $length)
substr()は、文字列操作の基本中の基本です。オフセットと長さの指定方法を理解して、マルチバイト文字に注意しながら、適切に使いこなしましょう!
