JavaScriptからPHPに移って躓いたポイントまとめ

こんにちは！
「自分のサービスを自分で作りたい」という思いから、ビジネスからエンジニアリングの世界に飛び込んだtaketo45です。

当初JavaScriptを使ってプログラミング学習と開発をしていましたが、サーバーサイドの言語であるPHPの学習を始めた際に、いろいろとつまづく場面に遭遇しました。同じように JavaScript から PHP に挑戦する方の参考になればと思い、ポイントをまとめてみました。

## 0. 書き方のお作法が異なる。
phpコードは、`<?php`と`?>`で挟む必要があるとか、変数の頭には、`$`をつける必要があるとか、行末に`;`を必ずつけるなど、php特有のお作法があります。

```php
<?php
$hello = 'hello!';
echo $hello;
?>
```
ちなみに、javascriptにおける console.logの代わりに、echo 又は var_dump() を使います。


## 1. 文字列連結は、(+)ではなく(.)を使う！

JavaScript では
```javascript
let greeting = "Hello";
greeting += " World";
```

PHPでは
```php
$greeting = "Hello";
$greeting .= " World";  // . を使う！
```

## 2. 配列操作が違う！

PHPとJavaScriptの配列操作メソッドでは、元の配列を変更するかどうかについて違いがあります。

### PHPの配列操作関数の特徴
PHPの多くの配列操作関数（array_map, array_filter, array_reduce など）は元の配列を変更せず、新しい配列を返します。

```php
$numbers = [1, 2, 3];

// array_map は新しい配列を返し、元の配列は変更されない
$doubled = array_map(function($n) { return $n * 2; }, $numbers);
echo implode(',', $numbers);  // 出力: 1,2,3
echo implode(',', $doubled);  // 出力: 2,4,6

// array_filter も新しい配列を返す
$filtered = array_filter($numbers, function($n) { return $n > 1; });
echo implode(',', $numbers);  // 出力: 1,2,3
echo implode(',', $filtered); // 出力: 2,3
```

ただし、元の配列を直接変更する関数もあります。
```php
$numbers = [1, 2, 3];

// array_push は元の配列を変更する
array_push($numbers, 4);
echo implode(',', $numbers);  // 出力: 1,2,3,4

// array_pop も元の配列を変更する
$last = array_pop($numbers);
echo implode(',', $numbers);  // 出力: 1,2,3
```

### JavaScriptの配列操作
JavaScriptでは、元の配列を変更するメソッドが多い傾向です。

元の配列を変更するメソッド:
```javascript
const numbers = [1, 2, 3];

// push は元の配列を変更する
numbers.push(4);
console.log(numbers);  // 出力: [1, 2, 3, 4]

// pop も元の配列を変更する
numbers.pop();
console.log(numbers);  // 出力: [1, 2, 3]

// splice は元の配列を変更する
numbers.splice(1, 1);
console.log(numbers);  // 出力: [1, 3]

// sort は元の配列を変更する
const fruits = ['banana', 'apple', 'orange'];
fruits.sort();
console.log(fruits);  // 出力: ['apple', 'banana', 'orange']

// reverse も元の配列を変更する
numbers.reverse();
console.log(numbers);  // 出力: [3, 1]
```

新しい配列を返すメソッド：
```javascript
const numbers = [1, 2, 3];

// map は新しい配列を返す
const doubled = numbers.map(n => n * 2);
console.log(numbers);   // 出力: [1, 2, 3]
console.log(doubled);   // 出力: [2, 4, 6]

// filter も新しい配列を返す
const filtered = numbers.filter(n => n > 1);
console.log(numbers);   // 出力: [1, 2, 3]
console.log(filtered);  // 出力: [2, 3]

// slice は新しい配列を返す
const sliced = numbers.slice(1, 2);
console.log(numbers);   // 出力: [1, 2, 3]
console.log(sliced);    // 出力: [2]
```

主な違いのまとめ
1. PHPの配列操作は関数として提供され、JavaScriptは配列のメソッドとして提供される
2. PHPでは（Javascriptと比較して）多くの配列操作関数が元の配列を変更せずに、新しい配列を返す傾向がある

配列の操作はバグの原因となりやすい箇所なので、違いをしっかりと意識する必要がありそうです。


## 3. 変数スコープが広い

JavaScript で当たり前のように使っていたブロックスコープ。PHPではちょっと違います。

```php
function test() {
    if (true) {
        $localVar = "ここだけ？";  //Javascriptの場合はifブロックの中だけでした参照できない
    }
    echo $localVar;  // なんと出力される！
}
```

最初はバグかと思いましたが、これがPHPの仕様。関数スコープが基本となります。
Javascriptのvarと動きが近い感じですかね。
（ちょっと調べたら厳密には違いました。このあたり話が長くなるので端折ります。1次情報のリンクを張っておきますので興味がある方は下記を参照ください。）
参考： 
[PHP 変数のスコープ](https://www.php.net/manual/ja/language.variables.scope.php)
[Javascript var](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/var)


## 4. 変数の代入で躓く - `||` と `??` の違い

最初に大きく躓いたのが、デフォルト値の設定です。JavaScriptでは以下のように書いていました：

```javascript
const value = someValue || "デフォルト値";
```

PHPで同じように書いたところ、想定と全く違う動きに...😱
```php
$value = $someValue || "デフォルト値";  // true や false が入ってくる！？
```

正しくは以下のように書く必要があります
```php
$value = $someValue ?? "デフォルト値";
```

それでも、全く同じ動きにはなりません。

JavaScript の || は falsy な値 (false, 0, "", null, undefined, NaN) をチェックします。
PHP の ?? は null のみをチェックします。

このあたりを更に細かく、違い見てみると下記のようになります。
```php
// PHP での || の例
$result = "" || "default";  // true が代入される
$result = 0 || "default";   // true が代入される
$result = false || "default"; // true が代入される

// PHP での ?? の例
$result = "" ?? "default";  // "" （空文字）が代入される
$result = 0 ?? "default";   // 0 が代入される
$result = false ?? "default"; // false が代入される
$result = null ?? "default"; // "default" が代入される
```

```javascript
// JavaScript での || の例
const result = "" || "default";  // "default" が代入される
const result = 0 || "default";   // "default" が代入される
const result = false || "default"; // "default" が代入される
```

擬似的にJavascriptの||と同じような挙動をempty()を使って再現できるのですが、これも厳密には一緒の動きにはなりません。詳しくは６.に追記しました。



## 5. 非同期処理

JavaScript では当たり前のように使っていた Promise や async/await。PHPではそもそも考え方が違います。

JavaScript：非同期処理を意識して実装する必要がある。
```javascript
async function getData() {
    const response = await fetch('api/data');
    const data = await response.json();
    return data;
}
```

PHP：同期的に処理されるので非同期処理を意識せずに実装が可能。
```php
function getData() {
    // file_get_contents は同期的に処理されます
    $response = file_get_contents('http://api.example.com');
    // レスポンスが返ってくるまでここで処理がブロックされます
    $data = json_decode($response, true);
    return $data;
}
```
PHPのfile_get_contentsは、同期的に処理されます。PHP上で非同期的に処理するには、cURLを利用して処理を記載するか、ReactPHPやSwooleのようなフレームワークを利用したり、ポーリングの処理を実装したりする必要があるようです。（この先は並列処理して処理速度を上げたり、ユーザー体験を良くするなど非同期にする目的によっていろいろ変わりそうです。別途タイミング見つけて調査してまとめたいと思います。）

参考：
[PHP file_get_contents](https://www.php.net/manual/ja/function.file-get-contents.php)
[PHP cURL](https://www.php.net/manual/ja/ref.curl.php)


## 6.空データの判定

１．の続きです。更に真偽値の判定に特化して見てみます。


```javascript
// JavaScript
!"";             // true
!"0";            // false
!0;              // true
!"false";        // false
!false;          // true
![].length;      // true
```
```php
// PHP
empty("");        // true
empty("0");       // true
empty(0);         // true
empty("false");   // false
empty(false);     // true
empty([]);        // true
```

empty()を使えば、javascriptの||と同じ挙動をするユーザー関数を作れるかもと思って、上記を調べましたが、厳密にはempty()を使っても挙動は一緒になりませんでした。

## 7.エラーハンドリング

エラーが発生した際の記載の仕方が異なります。

```javascript
// JavaScript
try {
    throw new Error("Error");
} catch (e) {
    console.error(e.message);
} finally {
    // クリーンアップ処理
}
```
```php
// PHP
try {
    throw new Exception("Error");
} catch (Exception $e) {
    echo $e->getMessage();
} finally {
    // クリーンアップ処理
}
```
PHPの変数は型宣言しないのにエラー処理の場合、Exceptionと宣言するのが不思議です。

## 8. 文字列の定義方法

Javascriptでは、バッククォート(`)を使用して文字列を定義できますが、

```javascript
const geminiprompt =`
# ビジネス日報作成支援AI
...
`
```
PHPだとこれは正しい文字列定義方法ではなく、変数はnullとなります。
PHPでは以下のいずれかの方法を使用する必要があります：
```php5
// 1. シングルクォート
$geminiprompt = '
# ビジネス日報作成支援AI
...
';

// 2. ダブルクォート（変数展開が必要な場合）
$geminiprompt = "
# ビジネス日報作成支援AI
...
";

// 3. Heredoc構文（推奨）
$geminiprompt = <<<EOD
# ビジネス日報作成支援AI
...
EOD;
```

## 9. よく使う標準関数が違う
よく使われる関数を比較表にまとめました。

### 文字列操作関数

| 処理内容 | JavaScript | PHP | PHP使用例・補足 |
|:---------|:-----------|:----|:----------------|
| 文字列長取得 | `str.length` | `strlen($str)` | `strlen("Hello")` → 5 |
| 文字列分割 | `str.split()` | `explode()` | `explode(",", "a,b,c")` → ["a","b","c"] |
| 文字列結合 | `arr.join()` | `implode()` | `implode(",", ["a","b"])` → "a,b" |
| 部分文字列 | `str.substring()` | `substr()` | `substr("Hello", 1, 3)` → "ell" |
| 文字列置換 | `str.replace()` | `str_replace()` | `str_replace("old", "new", $str)` |
| 大文字変換 | `str.toUpperCase()` | `strtoupper()` | `strtoupper("hello")` → "HELLO" |
| 小文字変換 | `str.toLowerCase()` | `strtolower()` | `strtolower("HELLO")` → "hello" |
| 前後空白削除 | `str.trim()` | `trim()` | `trim(" hello ")` → "hello" |
| 文字列検索 | `str.indexOf()` | `strpos()` | `strpos("hello", "ll")` → 2 |

### 配列操作関数

| 処理内容 | JavaScript | PHP | PHP使用例・補足 |
|:---------|:-----------|:----|:----------------|
| 配列長取得 | `arr.length` | `count()` | `count([1,2,3])` → 3 |
| 要素追加(末尾) | `arr.push()` | `array_push()` | `array_push($arr, $value)` |
| 要素削除(末尾) | `arr.pop()` | `array_pop()` | `$last = array_pop($arr)` |
| 要素追加(先頭) | `arr.unshift()` | `array_unshift()` | `array_unshift($arr, $value)` |
| 要素削除(先頭) | `arr.shift()` | `array_shift()` | `$first = array_shift($arr)` |
| 配列結合 | `arr.concat()` | `array_merge()` | `array_merge($arr1, $arr2)` |
| 配列スライス | `arr.slice()` | `array_slice()` | `array_slice($arr, 1, 2)` |
| 要素検索 | `arr.indexOf()` | `array_search()` | `array_search($value, $arr)` |
| 配列ソート | `arr.sort()` | `sort()` | `sort($arr)` 元配列を変更 |

### 日付・時間関数

| 処理内容 | JavaScript | PHP | PHP使用例・補足 |
|:---------|:-----------|:----|:----------------|
| 現在日時取得 | `new Date()` | `date()` | `date("Y-m-d H:i:s")` |
| タイムスタンプ取得 | `Date.now()` | `time()` | `time()` → UNIXタイムスタンプ |
| 日付フォーマット | `date.toISOString()` | `date()` | `date("Y年m月d日", $timestamp)` |
| 日付解析 | `Date.parse()` | `strtotime()` | `strtotime("2024-01-01")` |
| 日付加算 | 計算で実装 | `strtotime()` | `strtotime("+1 day", $timestamp)` |

### 数値・計算関数

| 処理内容 | JavaScript | PHP | PHP使用例・補足 |
|:---------|:-----------|:----|:----------------|
| 絶対値 | `Math.abs()` | `abs()` | `abs(-5)` → 5 |
| 四捨五入 | `Math.round()` | `round()` | `round(3.7)` → 4 |
| 切り上げ | `Math.ceil()` | `ceil()` | `ceil(3.1)` → 4 |
| 切り下げ | `Math.floor()` | `floor()` | `floor(3.9)` → 3 |
| 最大値 | `Math.max()` | `max()` | `max(1, 5, 3)` → 5 |
| 最小値 | `Math.min()` | `min()` | `min(1, 5, 3)` → 1 |
| 乱数生成 | `Math.random()` | `rand()` | `rand(1, 10)` 1〜10の乱数 |

### 型・値チェック関数

| 処理内容 | JavaScript | PHP | PHP使用例・補足 |
|:---------|:-----------|:----|:----------------|
| 型判定 | `typeof` | `gettype()` | `gettype($var)` → "string"等 |
| 数値判定 | `isNaN()` | `is_numeric()` | `is_numeric("123")` → true |
| 配列判定 | `Array.isArray()` | `is_array()` | `is_array([1,2,3])` → true |
| 空判定 | `== null` | `empty()` | `empty("")` → true |
| 存在判定 | `!== undefined` | `isset()` | `isset($var)` → true/false |

### 参考リンク

- [PHP Manual - 文字列関数](https://www.php.net/manual/ja/ref.strings.php)
- [PHP Manual - 配列関数](https://www.php.net/manual/ja/ref.array.php)
- [PHP Manual - 日付・時刻関数](https://www.php.net/manual/ja/ref.datetime.php)

## 追記

さらに以下のような相違点、追記してみました。

- PHPはリクエストごとに状態がリセットされる
- セッション管理の考え方がJavaScriptとは全く異なる
- 配列のキーは数値と文字列しか使えない（JavaScriptのようにオブジェクトは使えない）

## 10. PHPはリクエストごとに状態がリセットされる

JavaScriptでフロントエンド開発をしていると、変数やオブジェクトの状態が画面遷移やユーザー操作を通じて保持されることに慣れています。しかし、PHPはサーバーサイド言語として根本的に異なる動作をします。

### JavaScriptの場合
```javascript
let counter = 0;

function incrementCounter() {
    counter++;
    console.log(counter);  // 1, 2, 3, 4... と増加し続ける
}

// ボタンクリックのたびに状態が保持される
document.getElementById('button').addEventListener('click', incrementCounter);
```

### PHPの場合
```php
<?php
$counter = 0;

function incrementCounter() {
    global $counter;
    $counter++;
    echo $counter;  // 常に 1 が出力される！
}

// 各リクエストごとに変数がリセットされる
incrementCounter();
?>
```

PHPでは、ページがリクエストされるたびにスクリプトが最初から実行され、すべての変数が初期化されます。これは「ステートレス」と呼ばれる特性で、サーバーの負荷軽減やスケーラビリティの向上に寄与しますが、JavaScriptから移ってきた初学者には戸惑いの原因となります。

状態を保持したい場合は、セッション、データベース、ファイル等の永続化手段を使用する必要があります。

```php
<?php
session_start();

if (!isset($_SESSION['counter'])) {
    $_SESSION['counter'] = 0;
}

$_SESSION['counter']++;
echo $_SESSION['counter'];  // リクエストごとに増加する
?>
```

## 11. セッション管理の考え方がJavaScriptとは全く異なる

JavaScriptとPHPでは、セッション（ユーザーの状態管理）に対するアプローチが大きく異なります。

### JavaScriptでの状態管理
```javascript
// ブラウザ側で状態を管理
let userInfo = {
    name: "太郎",
    isLoggedIn: true,
    preferences: { theme: "dark" }
};

// localStorage を使った永続化
localStorage.setItem('userInfo', JSON.stringify(userInfo));

// 次回アクセス時も状態が復元される
const savedInfo = JSON.parse(localStorage.getItem('userInfo'));
```

JavaScriptでは、主にブラウザ側（クライアントサイド）でデータを管理し、localStorage、sessionStorage、またはメモリ上の変数を使用します。

### PHPでのセッション管理
```php
<?php
// サーバー側でセッションを管理
session_start();

// セッションに情報を保存
$_SESSION['userInfo'] = [
    'name' => '太郎',
    'isLoggedIn' => true,
    'preferences' => ['theme' => 'dark']
];

// セッションからデータを取得
if (isset($_SESSION['userInfo'])) {
    $userInfo = $_SESSION['userInfo'];
    echo "ようこそ、" . $userInfo['name'] . "さん";
}

// セッションの破棄
session_destroy();
?>
```

### 主な違い

| 項目 | JavaScript | PHP |
|:-----|:-----------|:----|
| 保存場所 | クライアント（ブラウザ） | サーバー |
| セキュリティ | ユーザーが改変可能 | サーバー側で管理、改変困難 |
| 保存期間 | ブラウザに依存 | サーバー設定に依存 |
| データ容量 | 制限あり（localStorage: 5-10MB） | サーバーの容量に依存 |

PHPのセッションは主にサーバー側で管理されるため、セキュリティ面でより安全ですが、セッションIDをクッキーでやり取りする仕組みを理解する必要があります。

```php
<?php
// セッション設定の例
ini_set('session.cookie_lifetime', 3600);  // 1時間
ini_set('session.cookie_secure', true);    // HTTPS時のみ
ini_set('session.cookie_httponly', true);  // JavaScript無効

session_start();
?>
```

## 12. 配列のキーは数値と文字列しか使えない

JavaScriptでは、オブジェクトのキーとして様々な型を使用できますが、PHPの配列では制限があります。

### JavaScriptの場合（柔軟なキー）
```javascript
// JavaScriptでは様々な型をキーにできる
const map = new Map();

// 文字列
map.set('name', '太郎');

// 数値
map.set(123, 'number key');

// オブジェクト
const obj = { id: 1 };
map.set(obj, 'object key');

// 関数
const func = () => {};
map.set(func, 'function key');

console.log(map.get(obj));  // 'object key'
```

```javascript
// 通常のオブジェクトでも
const data = {};
data['string'] = 'value1';
data[123] = 'value2';
data[true] = 'value3';  // boolean は文字列に変換される

console.log(data);  // { '123': 'value2', 'string': 'value1', 'true': 'value3' }
```

### PHPの場合（制限あり）
```php
<?php
// PHPでは数値と文字列のみ
$array = [];

// 文字列キー
$array['name'] = '太郎';

// 数値キー
$array[123] = 'number key';

// 論理値は数値に変換される
$array[true] = 'boolean key';   // キーは 1 になる
$array[false] = 'false key';    // キーは 0 になる

// オブジェクトや配列はキーにできない
// $array[['a', 'b']] = 'error';  // Fatal error!

print_r($array);
/*
Array
(
    [name] => 太郎
    [123] => number key
    [1] => boolean key
    [0] => false key
)
*/
?>
```

### 対処法：オブジェクトをキーにしたい場合

PHPでオブジェクトや複雑なデータをキーとして使いたい場合は、シリアライズやハッシュ化を利用します。

```php
<?php
// シリアライズを使った方法
$object = ['id' => 1, 'name' => 'test'];
$key = serialize($object);
$array[$key] = 'object-like key';

// ハッシュ化を使った方法
$objectHash = md5(json_encode($object));
$array[$objectHash] = 'hashed key';

// SPLObjectStorageを使う方法（オブジェクト専用）
$storage = new SplObjectStorage();
$obj = new stdClass();
$obj->id = 1;
$storage[$obj] = 'object value';

print_r($array);
?>
```

### 注意点

1. **数値文字列の自動変換**
```php
<?php
$array = [];
$array['123'] = 'string key';
$array[123] = 'numeric key';

// 上書きされる！
print_r($array);  // Array ( [123] => numeric key )
?>
```

2. **浮動小数点数は整数に変換される**
```php
<?php
$array = [];
$array[1.5] = 'float key';
$array[1.9] = 'another float';

// どちらも キー 1 になる
print_r($array);  // Array ( [1] => another float )
?>
```

これらの制限を理解しておくことで、JavaScriptからPHPに移行する際の混乱を避けることができます。


## まとめ

当初はPHPの学習を始めて2週間が経った頃に書いた記事なのですが、そこから更にPHPに触れていくなかで気づいたことがいくつかあり、久しぶりに内容を追記してみました。これからも気がついたポイントあれば書き足して行こうと思います。（最近はPHPにふれる機会があまりないため、そこまで積極的に更新できないかもしれませんが）

私と同じように、JavaScriptを学習してからPHPの学習に移る方の参考になれば幸いです！みなさんが躓いた別のポイントなどあれば、コメントでぜひ教えてください！